mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-09 05:22:24 +00:00
Compare commits
6 Commits
ximinez/re
...
copilot/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcce21b835 | ||
|
|
7856198e4e | ||
|
|
81555d5456 | ||
|
|
6b55c4cdc8 | ||
|
|
3414a1776b | ||
|
|
6d9ed125f3 |
8
.github/scripts/rename/copyright.sh
vendored
8
.github/scripts/rename/copyright.sh
vendored
@@ -76,11 +76,11 @@ fi
|
||||
if ! grep -q 'Dev Null' src/test/rpc/ValidatorInfo_test.cpp; then
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ValidatorInfo_test.cpp)" > src/test/rpc/ValidatorInfo_test.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/DoManifest.cpp; then
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/DoManifest.cpp)" > src/xrpld/rpc/handlers/DoManifest.cpp
|
||||
if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/server_info/Manifest.cpp; then
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/server_info/Manifest.cpp)" > src/xrpld/rpc/handlers/server_info/Manifest.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/ValidatorInfo.cpp; then
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/ValidatorInfo.cpp)" > src/xrpld/rpc/handlers/ValidatorInfo.cpp
|
||||
if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp; then
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp)" > src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp
|
||||
fi
|
||||
if ! grep -q 'Bougalis' include/xrpl/basics/SlabAllocator.h; then
|
||||
echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/SlabAllocator.h)" > include/xrpl/basics/SlabAllocator.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
|
||||
2
.github/workflows/check-pr-commits.yml
vendored
2
.github/workflows/check-pr-commits.yml
vendored
@@ -10,4 +10,4 @@ permissions:
|
||||
|
||||
jobs:
|
||||
check_commits:
|
||||
uses: XRPLF/actions/.github/workflows/check-pr-commits.yml@481048b78b94ac3343d1292b4ef125a813879f2b
|
||||
uses: XRPLF/actions/.github/workflows/check-pr-commits.yml@e2c7f400d1e85ae65dad552fd425169fbacca4a3
|
||||
|
||||
2
.github/workflows/check-pr-title.yml
vendored
2
.github/workflows/check-pr-title.yml
vendored
@@ -11,4 +11,4 @@ on:
|
||||
jobs:
|
||||
check_title:
|
||||
if: ${{ github.event.pull_request.draft != true }}
|
||||
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@e2c7f400d1e85ae65dad552fd425169fbacca4a3
|
||||
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@a5d8dd35be543365e90a11358447130c8763871d
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -14,7 +14,7 @@ on:
|
||||
jobs:
|
||||
# Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks.
|
||||
run-hooks:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@e7896f15cc60d0da1a272c77ee5c4026b424f9c7
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@9307df762265e15c745ddcdb38a581c989f7f349
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }'
|
||||
|
||||
2
.github/workflows/publish-docs.yml
vendored
2
.github/workflows/publish-docs.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@2bbc2dc1abeec7bfaa886804ab86871ac201764e
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
18
.github/workflows/reusable-build-test-config.yml
vendored
18
.github/workflows/reusable-build-test-config.yml
vendored
@@ -76,7 +76,7 @@ jobs:
|
||||
name: ${{ inputs.config_name }}
|
||||
runs-on: ${{ fromJSON(inputs.runs_on) }}
|
||||
container: ${{ inputs.image != '' && inputs.image || null }}
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: ${{ inputs.sanitizers != '' && 360 || 60 }}
|
||||
env:
|
||||
# Use a namespace to keep the objects separate for each configuration.
|
||||
CCACHE_NAMESPACE: ${{ inputs.config_name }}
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@2bbc2dc1abeec7bfaa886804ab86871ac201764e
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
with:
|
||||
enable_ccache: ${{ inputs.ccache_enabled }}
|
||||
|
||||
@@ -227,11 +227,17 @@ jobs:
|
||||
|
||||
- name: Set sanitizer options
|
||||
if: ${{ !inputs.build_only && env.SANITIZERS_ENABLED == 'true' }}
|
||||
env:
|
||||
CONFIG_NAME: ${{ inputs.config_name }}
|
||||
run: |
|
||||
echo "ASAN_OPTIONS=print_stacktrace=1:detect_container_overflow=0:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp" >> ${GITHUB_ENV}
|
||||
echo "TSAN_OPTIONS=second_deadlock_stack=1:halt_on_error=0:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >> ${GITHUB_ENV}
|
||||
echo "UBSAN_OPTIONS=suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/ubsan.supp" >> ${GITHUB_ENV}
|
||||
echo "LSAN_OPTIONS=suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/lsan.supp" >> ${GITHUB_ENV}
|
||||
ASAN_OPTS="include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-asan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp"
|
||||
if [[ "${CONFIG_NAME}" == *gcc* ]]; then
|
||||
ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0"
|
||||
fi
|
||||
echo "ASAN_OPTIONS=${ASAN_OPTS}" >> ${GITHUB_ENV}
|
||||
echo "TSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-tsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >> ${GITHUB_ENV}
|
||||
echo "UBSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-ubsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/ubsan.supp" >> ${GITHUB_ENV}
|
||||
echo "LSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-lsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/lsan.supp" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Run the separate tests
|
||||
if: ${{ !inputs.build_only }}
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@2bbc2dc1abeec7bfaa886804ab86871ac201764e
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
2
.github/workflows/upload-conan-deps.yml
vendored
2
.github/workflows/upload-conan-deps.yml
vendored
@@ -70,7 +70,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@2bbc2dc1abeec7bfaa886804ab86871ac201764e
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
@@ -6,6 +6,13 @@ For info about how [API versioning](https://xrpl.org/request-formatting.html#api
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### Modifications to `tx` and `account_tx`
|
||||
|
||||
In API version 2, the `tx_json` field in `tx` and `account_tx` responses includes server-added lower-case fields (`date`, `ledger_index`, and `ctid`) that are not part of the canonical signed transaction. In API version 3, these fields are removed from `tx_json` and are only present at the top-level result object.
|
||||
|
||||
- **Before (API v2)**: The `tx_json` object in the response contained `date`, `ledger_index`, and `ctid` fields alongside the canonical PascalCase transaction fields.
|
||||
- **After (API v3)**: The `tx_json` object contains only the canonical signed transaction fields. The `date`, `ledger_index`, and `ctid` fields appear exclusively at the top-level result object.
|
||||
|
||||
### Modifications to `amm_info`
|
||||
|
||||
The order of error checks has been changed to provide more specific error messages. ([#4924](https://github.com/XRPLF/rippled/pull/4924))
|
||||
|
||||
@@ -23,7 +23,7 @@ target_compile_definitions(
|
||||
BOOST_FILESYSTEM_NO_DEPRECATED
|
||||
>
|
||||
$<$<NOT:$<BOOL:${boost_show_deprecated}>>:
|
||||
BOOST_COROUTINES_NO_DEPRECATION_WARNING
|
||||
BOOST_COROUTINES2_NO_DEPRECATION_WARNING
|
||||
BOOST_BEAST_ALLOW_DEPRECATED
|
||||
BOOST_FILESYSTEM_DEPRECATED
|
||||
>
|
||||
|
||||
@@ -7,7 +7,7 @@ find_package(
|
||||
COMPONENTS
|
||||
chrono
|
||||
container
|
||||
coroutine
|
||||
context
|
||||
date_time
|
||||
filesystem
|
||||
json
|
||||
@@ -26,7 +26,7 @@ target_link_libraries(
|
||||
Boost::headers
|
||||
Boost::chrono
|
||||
Boost::container
|
||||
Boost::coroutine
|
||||
Boost::context
|
||||
Boost::date_time
|
||||
Boost::filesystem
|
||||
Boost::json
|
||||
@@ -38,23 +38,26 @@ target_link_libraries(
|
||||
if(Boost_COMPILER)
|
||||
target_link_libraries(xrpl_boost INTERFACE Boost::disable_autolinking)
|
||||
endif()
|
||||
if(SANITIZERS_ENABLED AND is_clang)
|
||||
# TODO: gcc does not support -fsanitize-blacklist...can we do something else for gcc ?
|
||||
if(NOT Boost_INCLUDE_DIRS AND TARGET Boost::headers)
|
||||
get_target_property(
|
||||
Boost_INCLUDE_DIRS
|
||||
Boost::headers
|
||||
INTERFACE_INCLUDE_DIRECTORIES
|
||||
)
|
||||
endif()
|
||||
message(STATUS "Adding [${Boost_INCLUDE_DIRS}] to sanitizer blacklist")
|
||||
file(
|
||||
WRITE ${CMAKE_CURRENT_BINARY_DIR}/san_bl.txt
|
||||
"src:${Boost_INCLUDE_DIRS}/*"
|
||||
)
|
||||
target_compile_options(
|
||||
opts
|
||||
INTERFACE # ignore boost headers for sanitizing
|
||||
-fsanitize-blacklist=${CMAKE_CURRENT_BINARY_DIR}/san_bl.txt
|
||||
|
||||
# GCC 14+ has a false positive -Wuninitialized warning in Boost.Coroutine2's
|
||||
# state.hpp when compiled with -O3. This is due to GCC's intentional behavior
|
||||
# change (Bug #98871, #119388) where warnings from inlined system header code
|
||||
# are no longer suppressed by -isystem. The warning occurs in operator|= in
|
||||
# boost/coroutine2/detail/state.hpp when inlined from push_control_block::destroy().
|
||||
# See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119388
|
||||
if(is_gcc AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14)
|
||||
target_compile_options(xrpl_boost INTERFACE -Wno-uninitialized)
|
||||
endif()
|
||||
|
||||
# Boost.Context's ucontext backend has ASAN fiber-switching annotations
|
||||
# (start/finish_switch_fiber) that are compiled in when BOOST_USE_ASAN is defined.
|
||||
# This tells ASAN about coroutine stack switches, preventing false positive
|
||||
# stack-use-after-scope errors. BOOST_USE_UCONTEXT ensures the ucontext backend
|
||||
# is selected (fcontext does not support ASAN annotations).
|
||||
# These defines must match what Boost was compiled with (see conan/profiles/sanitizers).
|
||||
if(enable_asan)
|
||||
target_compile_definitions(
|
||||
xrpl_boost
|
||||
INTERFACE BOOST_USE_ASAN BOOST_USE_UCONTEXT
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -7,16 +7,21 @@ include(default)
|
||||
{% if compiler == "gcc" %}
|
||||
{% if "address" in sanitizers or "thread" in sanitizers or "undefinedbehavior" in sanitizers %}
|
||||
{% set sanitizer_list = [] %}
|
||||
{% set defines = [] %}
|
||||
{% set model_code = "" %}
|
||||
{% set extra_cxxflags = ["-fno-omit-frame-pointer", "-O1", "-Wno-stringop-overflow"] %}
|
||||
|
||||
{% if "address" in sanitizers %}
|
||||
{% set _ = sanitizer_list.append("address") %}
|
||||
{% set model_code = "-mcmodel=large" %}
|
||||
{% set _ = defines.append("BOOST_USE_ASAN")%}
|
||||
{% set _ = defines.append("BOOST_USE_UCONTEXT")%}
|
||||
{% elif "thread" in sanitizers %}
|
||||
{% set _ = sanitizer_list.append("thread") %}
|
||||
{% set model_code = "-mcmodel=medium" %}
|
||||
{% set _ = extra_cxxflags.append("-Wno-tsan") %}
|
||||
{% set _ = defines.append("BOOST_USE_TSAN")%}
|
||||
{% set _ = defines.append("BOOST_USE_UCONTEXT")%}
|
||||
{% endif %}
|
||||
|
||||
{% if "undefinedbehavior" in sanitizers %}
|
||||
@@ -29,16 +34,22 @@ include(default)
|
||||
tools.build:cxxflags+=['{{sanitizer_flags}} {{" ".join(extra_cxxflags)}}']
|
||||
tools.build:sharedlinkflags+=['{{sanitizer_flags}}']
|
||||
tools.build:exelinkflags+=['{{sanitizer_flags}}']
|
||||
tools.build:defines+={{defines}}
|
||||
{% endif %}
|
||||
{% elif compiler == "apple-clang" or compiler == "clang" %}
|
||||
{% if "address" in sanitizers or "thread" in sanitizers or "undefinedbehavior" in sanitizers %}
|
||||
{% set sanitizer_list = [] %}
|
||||
{% set defines = [] %}
|
||||
{% set extra_cxxflags = ["-fno-omit-frame-pointer", "-O1"] %}
|
||||
|
||||
{% if "address" in sanitizers %}
|
||||
{% set _ = sanitizer_list.append("address") %}
|
||||
{% set _ = defines.append("BOOST_USE_ASAN")%}
|
||||
{% set _ = defines.append("BOOST_USE_UCONTEXT")%}
|
||||
{% elif "thread" in sanitizers %}
|
||||
{% set _ = sanitizer_list.append("thread") %}
|
||||
{% set _ = defines.append("BOOST_USE_TSAN")%}
|
||||
{% set _ = defines.append("BOOST_USE_UCONTEXT")%}
|
||||
{% endif %}
|
||||
|
||||
{% if "undefinedbehavior" in sanitizers %}
|
||||
@@ -52,8 +63,24 @@ include(default)
|
||||
tools.build:cxxflags+=['{{sanitizer_flags}} {{" ".join(extra_cxxflags)}}']
|
||||
tools.build:sharedlinkflags+=['{{sanitizer_flags}}']
|
||||
tools.build:exelinkflags+=['{{sanitizer_flags}}']
|
||||
tools.build:defines+={{defines}}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
tools.info.package_id:confs+=["tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags"]
|
||||
tools.info.package_id:confs+=["tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags", "tools.build:defines"]
|
||||
|
||||
[options]
|
||||
{% if sanitizers %}
|
||||
{% if "address" in sanitizers %}
|
||||
# Build Boost.Context with ucontext backend (not fcontext) so that
|
||||
# ASAN fiber-switching annotations (__sanitizer_start/finish_switch_fiber)
|
||||
# are compiled into the library. fcontext (assembly) has no ASAN support.
|
||||
# define=BOOST_USE_ASAN=1 is critical: it must be defined when building
|
||||
# Boost.Context itself so the ucontext backend compiles in the ASAN annotations.
|
||||
boost/*:extra_b2_flags=context-impl=ucontext address-sanitizer=on define=BOOST_USE_ASAN=1
|
||||
boost/*:without_context=False
|
||||
# Boost stacktrace fails to build with some sanitizers
|
||||
boost/*:without_stacktrace=True
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
12
conanfile.py
12
conanfile.py
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
|
||||
@@ -56,6 +57,9 @@ class Xrpl(ConanFile):
|
||||
"tests": False,
|
||||
"unity": False,
|
||||
"xrpld": False,
|
||||
"boost/*:without_context": False,
|
||||
"boost/*:without_coroutine": True,
|
||||
"boost/*:without_coroutine2": False,
|
||||
"date/*:header_only": True,
|
||||
"ed25519/*:shared": False,
|
||||
"grpc/*:shared": False,
|
||||
@@ -125,6 +129,12 @@ class Xrpl(ConanFile):
|
||||
if self.settings.compiler in ["clang", "gcc"]:
|
||||
self.options["boost"].without_cobalt = True
|
||||
|
||||
# Check if environment variable exists
|
||||
if "SANITIZERS" in os.environ:
|
||||
sanitizers = os.environ["SANITIZERS"]
|
||||
if "address" in sanitizers.lower():
|
||||
self.default_options["fPIC"] = False
|
||||
|
||||
def requirements(self):
|
||||
self.requires("boost/1.90.0", force=True, transitive_headers=True)
|
||||
self.requires("date/3.0.4", transitive_headers=True)
|
||||
@@ -191,7 +201,7 @@ class Xrpl(ConanFile):
|
||||
"boost::headers",
|
||||
"boost::chrono",
|
||||
"boost::container",
|
||||
"boost::coroutine",
|
||||
"boost::context",
|
||||
"boost::date_time",
|
||||
"boost::filesystem",
|
||||
"boost::json",
|
||||
|
||||
@@ -99,6 +99,7 @@ words:
|
||||
- endmacro
|
||||
- exceptioned
|
||||
- Falco
|
||||
- fcontext
|
||||
- finalizers
|
||||
- firewalled
|
||||
- fmtdur
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <class F>
|
||||
@@ -10,16 +8,18 @@ JobQueue::Coro::Coro(Coro_create_t, JobQueue& jq, JobType type, std::string cons
|
||||
, type_(type)
|
||||
, name_(name)
|
||||
, coro_(
|
||||
// Stack size of 1MB wasn't sufficient for deep calls. ASAN tests flagged the issue. Hence
|
||||
// increasing the size to 1.5MB.
|
||||
boost::context::protected_fixedsize_stack(1536 * 1024),
|
||||
[this, fn = std::forward<F>(f)](
|
||||
boost::coroutines::asymmetric_coroutine<void>::push_type& do_yield) {
|
||||
boost::coroutines2::asymmetric_coroutine<void>::push_type& do_yield) {
|
||||
yield_ = &do_yield;
|
||||
yield();
|
||||
fn(shared_from_this());
|
||||
#ifndef NDEBUG
|
||||
finished_ = true;
|
||||
#endif
|
||||
},
|
||||
boost::coroutines::attributes(megabytes(1)))
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
#include <xrpl/core/detail/Workers.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <boost/coroutine/all.hpp>
|
||||
#include <boost/context/protected_fixedsize_stack.hpp>
|
||||
#include <boost/coroutine2/all.hpp>
|
||||
|
||||
#include <set>
|
||||
|
||||
@@ -48,8 +49,8 @@ public:
|
||||
std::mutex mutex_;
|
||||
std::mutex mutex_run_;
|
||||
std::condition_variable cv_;
|
||||
boost::coroutines::asymmetric_coroutine<void>::pull_type coro_;
|
||||
boost::coroutines::asymmetric_coroutine<void>::push_type* yield_;
|
||||
boost::coroutines2::coroutine<void>::pull_type coro_;
|
||||
boost::coroutines2::coroutine<void>::push_type* yield_;
|
||||
#ifndef NDEBUG
|
||||
bool finished_ = false;
|
||||
#endif
|
||||
|
||||
@@ -23,9 +23,10 @@ struct JsonOptions
|
||||
none = 0b0000'0000,
|
||||
include_date = 0b0000'0001,
|
||||
disable_API_prior_V2 = 0b0000'0010,
|
||||
disable_API_prior_V3 = 0b0000'0100,
|
||||
|
||||
// IMPORTANT `_all` must be union of all of the above; see also operator~
|
||||
_all = 0b0000'0011
|
||||
_all = 0b0000'0111
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
# The idea is to empty this file gradually by fixing the underlying issues and removing suppressions.
|
||||
#
|
||||
# ASAN_OPTIONS="print_stacktrace=1:detect_container_overflow=0:suppressions=sanitizers/suppressions/asan.supp:halt_on_error=0"
|
||||
#
|
||||
# The detect_container_overflow=0 option disables false positives from:
|
||||
# - Boost intrusive containers (slist_iterator.hpp, hashtable.hpp, aged_unordered_container.h)
|
||||
# - Boost context/coroutine stack switching (Workers.cpp, thread.h)
|
||||
#
|
||||
# See: https://github.com/google/sanitizers/wiki/AddressSanitizerContainerOverflow
|
||||
|
||||
# Boost
|
||||
interceptor_name:boost/asio
|
||||
|
||||
# Suppress false positive stack-buffer errors in thread stack allocation
|
||||
# Related to ASan's __asan_handle_no_return warnings (github.com/google/sanitizers/issues/189)
|
||||
|
||||
@@ -363,6 +363,13 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
|
||||
|
||||
auto const balance = (*sle)[sfBalance].xrp();
|
||||
|
||||
// NOTE: Because preclaim evaluates against a static readview, it
|
||||
// does not reflect fee deductions from other transactions paid by
|
||||
// the same account within the current ledger.
|
||||
// As a result, if an account's balance is over-committed across multiple
|
||||
// transactions, this check may pass optimistically.
|
||||
// The fee shortfall will be handled by the Transactor::reset mechanism,
|
||||
// which caps the fee to the remaining actual balance.
|
||||
if (balance < feePaid)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(balance)
|
||||
|
||||
@@ -559,18 +559,23 @@ Payment::doApply()
|
||||
// This is the total reserve in drops.
|
||||
auto const reserve = view().fees().accountReserve(ownerCount);
|
||||
|
||||
// preFeeBalance_ is the balance on the sending account BEFORE the
|
||||
// fees were charged. We want to make sure we have enough reserve
|
||||
// to send. Allow final spend to use reserve for fee.
|
||||
auto const mmm = std::max(reserve, ctx_.tx.getFieldAmount(sfFee).xrp());
|
||||
// In a delegated payment, the fee payer is the delegated account,
|
||||
// not the source account (account_).
|
||||
bool const accountIsPayer = (ctx_.tx.getFeePayer() == account_);
|
||||
|
||||
if (preFeeBalance_ < dstAmount.xrp() + mmm)
|
||||
// preFeeBalance_ is the balance on the source account (account_) BEFORE the fees
|
||||
// were charged. If source account is the fee payer, it must also cover the fee.
|
||||
// The final spend may use the reserve to cover fees.
|
||||
auto const minRequiredFunds =
|
||||
accountIsPayer ? std::max(reserve, ctx_.tx.getFieldAmount(sfFee).xrp()) : reserve;
|
||||
|
||||
if (preFeeBalance_ < dstAmount.xrp() + minRequiredFunds)
|
||||
{
|
||||
// Vote no. However the transaction might succeed, if applied in
|
||||
// a different order.
|
||||
JLOG(j_.trace()) << "Delay transaction: Insufficient funds: " << to_string(preFeeBalance_)
|
||||
<< " / " << to_string(dstAmount.xrp() + mmm) << " (" << to_string(reserve)
|
||||
<< ")";
|
||||
<< " / " << to_string(dstAmount.xrp() + minRequiredFunds) << " ("
|
||||
<< to_string(reserve) << ")";
|
||||
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
}
|
||||
|
||||
@@ -274,37 +274,42 @@ class Delegate_test : public beast::unit_test::suite
|
||||
testcase("test fee");
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const carol{"carol"};
|
||||
env.fund(XRP(10000), alice, carol);
|
||||
env.fund(XRP(1000), bob);
|
||||
env.close();
|
||||
// Common setup: fund alice, bob, carol with 1000 XRP.
|
||||
auto setup = [&](Env& env) {
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const carol{"carol"};
|
||||
env.fund(XRP(1000), alice, bob, carol);
|
||||
env.close();
|
||||
return std::make_tuple(alice, bob, carol);
|
||||
};
|
||||
|
||||
// No fee deduction for terNO_DELEGATE_PERMISSION.
|
||||
{
|
||||
auto aliceBalance = env.balance(alice);
|
||||
auto bobBalance = env.balance(bob);
|
||||
auto carolBalance = env.balance(carol);
|
||||
Env env(*this);
|
||||
auto [alice, bob, carol] = setup(env);
|
||||
|
||||
env(pay(alice, carol, XRP(100)),
|
||||
fee(XRP(2000)),
|
||||
delegate::as(bob),
|
||||
ter(terNO_DELEGATE_PERMISSION));
|
||||
auto const aliceBalance = env.balance(alice);
|
||||
auto const bobBalance = env.balance(bob);
|
||||
auto const carolBalance = env.balance(carol);
|
||||
|
||||
env(pay(alice, carol, XRP(100)), delegate::as(bob), ter(terNO_DELEGATE_PERMISSION));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(alice) == aliceBalance);
|
||||
BEAST_EXPECT(env.balance(bob) == bobBalance);
|
||||
BEAST_EXPECT(env.balance(carol) == carolBalance);
|
||||
}
|
||||
|
||||
env(delegate::set(alice, bob, {"Payment"}));
|
||||
env.close();
|
||||
|
||||
// Delegate pays the fee successfully.
|
||||
{
|
||||
// Delegate pays the fee
|
||||
auto aliceBalance = env.balance(alice);
|
||||
auto bobBalance = env.balance(bob);
|
||||
auto carolBalance = env.balance(carol);
|
||||
Env env(*this);
|
||||
auto [alice, bob, carol] = setup(env);
|
||||
env(delegate::set(alice, bob, {"Payment"}));
|
||||
env.close();
|
||||
|
||||
auto const aliceBalance = env.balance(alice);
|
||||
auto const bobBalance = env.balance(bob);
|
||||
auto const carolBalance = env.balance(carol);
|
||||
|
||||
auto const sendAmt = XRP(100);
|
||||
auto const feeAmt = XRP(10);
|
||||
@@ -315,11 +320,16 @@ class Delegate_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(env.balance(carol) == carolBalance + sendAmt);
|
||||
}
|
||||
|
||||
// Bob has insufficient balance to pay the fee, will get terINSUF_FEE_B.
|
||||
{
|
||||
// insufficient balance to pay fee
|
||||
auto aliceBalance = env.balance(alice);
|
||||
auto bobBalance = env.balance(bob);
|
||||
auto carolBalance = env.balance(carol);
|
||||
Env env(*this);
|
||||
auto [alice, bob, carol] = setup(env);
|
||||
env(delegate::set(alice, bob, {"Payment"}));
|
||||
env.close();
|
||||
|
||||
auto const aliceBalance = env.balance(alice);
|
||||
auto const bobBalance = env.balance(bob);
|
||||
auto const carolBalance = env.balance(carol);
|
||||
|
||||
env(pay(alice, carol, XRP(100)),
|
||||
fee(XRP(2000)),
|
||||
@@ -331,22 +341,143 @@ class Delegate_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(env.balance(carol) == carolBalance);
|
||||
}
|
||||
|
||||
// The delegated account has enough balance to pay and delegator has enough reserve
|
||||
{
|
||||
// fee is paid by Delegate
|
||||
// on context reset (tec error)
|
||||
auto aliceBalance = env.balance(alice);
|
||||
auto bobBalance = env.balance(bob);
|
||||
auto carolBalance = env.balance(carol);
|
||||
auto const feeAmt = XRP(10);
|
||||
// Common setup: fund accounts and grant Bob permission to pay on Alice's behalf.
|
||||
// Alice is funded with exactly (paymentAmount + reserve + baseFee): baseFee covers
|
||||
// the DelegateSet tx cost, leaving Alice with exactly (paymentAmount + reserve).
|
||||
// highFee = reserve + baseFee, strictly greater than reserve, so that
|
||||
// max(reserve, highFee) = highFee — making the direct payment check fail.
|
||||
auto setup = [&](Env& env) {
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
env(pay(alice, carol, XRP(20000)),
|
||||
fee(feeAmt),
|
||||
delegate::as(bob),
|
||||
ter(tecUNFUNDED_PAYMENT));
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
auto const reserve = env.current()->fees().accountReserve(1);
|
||||
auto const paymentAmount = XRP(1);
|
||||
auto const highFee = reserve + baseFee;
|
||||
BEAST_EXPECT(highFee > reserve);
|
||||
|
||||
env.fund(paymentAmount + reserve + baseFee, alice);
|
||||
env.fund(XRP(1000), bob);
|
||||
env.fund(XRP(1000), carol);
|
||||
env.close();
|
||||
|
||||
env(delegate::set(alice, bob, {"Payment"}));
|
||||
env.close();
|
||||
|
||||
env.require(balance(alice, paymentAmount + reserve));
|
||||
|
||||
return std::make_tuple(alice, bob, carol, paymentAmount, highFee, reserve);
|
||||
};
|
||||
|
||||
// Alice's balance (paymentAmount + reserve) is insufficient to cover both
|
||||
// the payment and highFee directly. Even though fees are allowed to dip
|
||||
// below reserve, when Alice pays the fee herself the required funds =
|
||||
// paymentAmount + max(reserve, highFee) = paymentAmount + highFee
|
||||
// (since highFee > reserve), which still exceeds her balance.
|
||||
// tec: highFee is consumed from Alice's balance.
|
||||
{
|
||||
Env env(*this);
|
||||
auto [alice, bob, carol, paymentAmount, highFee, reserve] = setup(env);
|
||||
auto const aliceBalance = env.balance(alice);
|
||||
auto const bobBalance = env.balance(bob);
|
||||
auto const carolBalance = env.balance(carol);
|
||||
|
||||
env(pay(alice, carol, paymentAmount), fee(highFee), ter(tecUNFUNDED_PAYMENT));
|
||||
|
||||
// tec consumes the fee from Alice; carol and bob are unaffected.
|
||||
BEAST_EXPECT(env.balance(alice) == aliceBalance - highFee);
|
||||
BEAST_EXPECT(env.balance(bob) == bobBalance);
|
||||
BEAST_EXPECT(env.balance(carol) == carolBalance);
|
||||
}
|
||||
|
||||
// The payment succeeds because the delegated account pays the fee.
|
||||
// Alice only needs (paymentAmount + reserve).
|
||||
{
|
||||
Env env(*this);
|
||||
auto [alice, bob, carol, paymentAmount, highFee, reserve] = setup(env);
|
||||
|
||||
auto const alicePrePay = env.balance(alice, XRP);
|
||||
auto const bobPrePay = env.balance(bob, XRP);
|
||||
auto const carolPrePay = env.balance(carol, XRP);
|
||||
|
||||
env(pay(alice, carol, paymentAmount), delegate::as(bob), fee(highFee));
|
||||
env.close();
|
||||
|
||||
env.require(balance(alice, alicePrePay - paymentAmount));
|
||||
env.require(balance(bob, bobPrePay - highFee));
|
||||
env.require(balance(carol, carolPrePay + paymentAmount));
|
||||
}
|
||||
}
|
||||
|
||||
// Delegated account can pay the fee even if it dips below reserve.
|
||||
{
|
||||
Env env(*this);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
auto const baseReserve = env.current()->fees().accountReserve(0);
|
||||
|
||||
env.fund(env.current()->fees().accountReserve(1) + baseFee + XRP(1), alice);
|
||||
env.fund(baseReserve, bob);
|
||||
env.fund(XRP(1000), carol);
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(alice) == aliceBalance);
|
||||
BEAST_EXPECT(env.balance(bob) == bobBalance - feeAmt);
|
||||
BEAST_EXPECT(env.balance(carol) == carolBalance);
|
||||
|
||||
env(delegate::set(alice, bob, {"Payment"}));
|
||||
env.close();
|
||||
|
||||
auto const alicePreTx = env.balance(alice, XRP);
|
||||
auto const bobPreTx = env.balance(bob, XRP);
|
||||
|
||||
// After paying for this transaction, bob's balance will
|
||||
// dip below the base reserve
|
||||
env(pay(alice, carol, XRP(1)), delegate::as(bob));
|
||||
env.close();
|
||||
|
||||
// Bob's balance is now less than the base reserve.
|
||||
BEAST_EXPECT(env.balance(bob, XRP) < baseReserve);
|
||||
env.require(balance(bob, bobPreTx - drops(baseFee)));
|
||||
|
||||
// Alice's balance only decreased by the 1.0 XRP she sent.
|
||||
env.require(balance(alice, alicePreTx - XRP(1)));
|
||||
}
|
||||
|
||||
// The delegated account has enough balance for the fee, but delegator
|
||||
// runs into tecUNFUNDED_PAYMENT.
|
||||
{
|
||||
Env env(*this);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
auto const reserve = env.current()->fees().accountReserve(1);
|
||||
|
||||
// Alice is funded with (reserve + baseFee): after DelegateSet she has
|
||||
// exactly 'reserve', which is insufficient to send XRP(10) while keeping
|
||||
// reserve. Bob has plenty to pay the fee.
|
||||
env.fund(reserve + baseFee, alice);
|
||||
env.fund(XRP(1000), bob);
|
||||
env.fund(XRP(1000), carol);
|
||||
env.close();
|
||||
|
||||
env(delegate::set(alice, bob, {"Payment"}));
|
||||
env.close();
|
||||
|
||||
auto const alicePrePay = env.balance(alice, XRP);
|
||||
auto const bobPrePay = env.balance(bob, XRP);
|
||||
auto const carolPrePay = env.balance(carol, XRP);
|
||||
|
||||
// Bob pays the fee, but Alice has insufficient balance to send XRP(10).
|
||||
env(pay(alice, carol, XRP(10)), delegate::as(bob), ter(tecUNFUNDED_PAYMENT));
|
||||
|
||||
env.require(balance(alice, alicePrePay));
|
||||
env.require(balance(bob, bobPrePay - drops(baseFee)));
|
||||
env.require(balance(carol, carolPrePay));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1238,8 +1369,8 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// test MPTokenIssuanceUnlock and MPTokenIssuanceLock permissions
|
||||
{
|
||||
Env env(*this);
|
||||
Account alice{"alice"};
|
||||
Account bob{"bob"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(100000), alice, bob);
|
||||
env.close();
|
||||
|
||||
@@ -1285,8 +1416,8 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// test mix of granular and transaction level permission
|
||||
{
|
||||
Env env(*this);
|
||||
Account alice{"alice"};
|
||||
Account bob{"bob"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(100000), alice, bob);
|
||||
env.close();
|
||||
|
||||
@@ -1332,8 +1463,8 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// tfFullyCanonicalSig won't block delegated transaction
|
||||
{
|
||||
Env env(*this);
|
||||
Account alice{"alice"};
|
||||
Account bob{"bob"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(100000), alice, bob);
|
||||
env.close();
|
||||
|
||||
@@ -1410,11 +1541,9 @@ class Delegate_test : public beast::unit_test::suite
|
||||
|
||||
{
|
||||
Env env(*this);
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
env.fund(XRP(100000), alice, bob, carol);
|
||||
env.close();
|
||||
|
||||
@@ -1448,11 +1577,9 @@ class Delegate_test : public beast::unit_test::suite
|
||||
|
||||
{
|
||||
Env env(*this);
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
env.fund(XRP(100000), alice, bob, carol);
|
||||
env.close();
|
||||
|
||||
@@ -1567,8 +1694,8 @@ class Delegate_test : public beast::unit_test::suite
|
||||
|
||||
Env env(*this, features);
|
||||
|
||||
Account alice{"alice"};
|
||||
Account bob{"bob"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(100000), alice, bob);
|
||||
env.close();
|
||||
|
||||
|
||||
@@ -127,20 +127,52 @@ class AccountTx_test : public beast::unit_test::suite
|
||||
{
|
||||
auto const& payment = j[jss::result][jss::transactions][1u];
|
||||
|
||||
return (payment.isMember(jss::tx_json)) &&
|
||||
(payment[jss::tx_json][jss::TransactionType] == jss::Payment) &&
|
||||
(payment[jss::tx_json][jss::DeliverMax] == "10000000010") &&
|
||||
(!payment[jss::tx_json].isMember(jss::Amount)) &&
|
||||
(!payment[jss::tx_json].isMember(jss::hash)) &&
|
||||
(payment[jss::hash] ==
|
||||
"9F3085D85F472D1CC29627F260DF68EDE59D42D1D0C33E345"
|
||||
"ECF0D4CE981D0A8") &&
|
||||
(payment[jss::validated] == true) &&
|
||||
(payment[jss::ledger_index] == 3) &&
|
||||
(payment[jss::ledger_hash] ==
|
||||
"5476DCD816EA04CBBA57D47BBF1FC58A5217CC93A5ADD79CB"
|
||||
"580A5AFDD727E33") &&
|
||||
(payment[jss::close_time_iso] == "2000-01-01T00:00:10Z");
|
||||
if (apiVersion >= 3)
|
||||
{
|
||||
// In API v3, server-added lower-case fields must
|
||||
// not be in tx_json, but must be at result level
|
||||
return (payment.isMember(jss::tx_json)) &&
|
||||
(payment[jss::tx_json][jss::TransactionType] == jss::Payment) &&
|
||||
(payment[jss::tx_json][jss::DeliverMax] == "10000000010") &&
|
||||
(!payment[jss::tx_json].isMember(jss::Amount)) &&
|
||||
(!payment[jss::tx_json].isMember(jss::hash)) &&
|
||||
(!payment[jss::tx_json].isMember(jss::date)) &&
|
||||
(!payment[jss::tx_json].isMember(jss::ledger_index)) &&
|
||||
(!payment[jss::tx_json].isMember(jss::ctid)) &&
|
||||
// date and ctid must be at the transaction
|
||||
// object level (outside tx_json) in API v3
|
||||
(payment.isMember(jss::date)) && (payment.isMember(jss::ctid)) &&
|
||||
(payment[jss::hash] ==
|
||||
"9F3085D85F472D1CC29627F260DF68EDE59D42D1D0C33E345"
|
||||
"ECF0D4CE981D0A8") &&
|
||||
(payment[jss::validated] == true) &&
|
||||
(payment[jss::ledger_index] == 3) &&
|
||||
(payment[jss::ledger_hash] ==
|
||||
"5476DCD816EA04CBBA57D47BBF1FC58A5217CC93A5ADD79CB"
|
||||
"580A5AFDD727E33") &&
|
||||
(payment[jss::close_time_iso] == "2000-01-01T00:00:10Z");
|
||||
}
|
||||
else
|
||||
{
|
||||
// In API v2, date and ledger_index are still in
|
||||
// tx_json for backwards compatibility
|
||||
return (payment.isMember(jss::tx_json)) &&
|
||||
(payment[jss::tx_json][jss::TransactionType] == jss::Payment) &&
|
||||
(payment[jss::tx_json][jss::DeliverMax] == "10000000010") &&
|
||||
(!payment[jss::tx_json].isMember(jss::Amount)) &&
|
||||
(!payment[jss::tx_json].isMember(jss::hash)) &&
|
||||
(payment[jss::tx_json].isMember(jss::date)) &&
|
||||
(payment[jss::tx_json].isMember(jss::ledger_index)) &&
|
||||
(payment[jss::hash] ==
|
||||
"9F3085D85F472D1CC29627F260DF68EDE59D42D1D0C33E345"
|
||||
"ECF0D4CE981D0A8") &&
|
||||
(payment[jss::validated] == true) &&
|
||||
(payment[jss::ledger_index] == 3) &&
|
||||
(payment[jss::ledger_hash] ==
|
||||
"5476DCD816EA04CBBA57D47BBF1FC58A5217CC93A5ADD79CB"
|
||||
"580A5AFDD727E33") &&
|
||||
(payment[jss::close_time_iso] == "2000-01-01T00:00:10Z");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <test/jtx/TestSuite.h>
|
||||
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/handlers/WalletPropose.h>
|
||||
#include <xrpld/rpc/handlers/admin/keygen/WalletPropose.h>
|
||||
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/json_writer.h>
|
||||
|
||||
@@ -775,6 +775,25 @@ class Transaction_test : public beast::unit_test::suite
|
||||
result[jss::result][jss::ledger_hash] ==
|
||||
"B41882E20F0EC6228417D28B9AE0F33833645D35F6799DFB782AC97FC4BB51"
|
||||
"D2");
|
||||
|
||||
auto const& tx_json = result[jss::result][jss::tx_json];
|
||||
if (apiVersion >= 3)
|
||||
{
|
||||
// In API v3, server-added lower-case fields must not appear
|
||||
// inside tx_json; they are at the result level.
|
||||
BEAST_EXPECT(!tx_json.isMember(jss::date));
|
||||
BEAST_EXPECT(!tx_json.isMember(jss::ledger_index));
|
||||
BEAST_EXPECT(!tx_json.isMember(jss::ctid));
|
||||
// date must be at result level in API v3
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::date));
|
||||
}
|
||||
else
|
||||
{
|
||||
// In API v2, date and ledger_index are still included in
|
||||
// tx_json for backwards compatibility.
|
||||
BEAST_EXPECT(tx_json.isMember(jss::date));
|
||||
BEAST_EXPECT(tx_json.isMember(jss::ledger_index));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto memberIt = expected.begin(); memberIt != expected.end(); memberIt++)
|
||||
|
||||
@@ -141,28 +141,30 @@ Transaction::getJson(JsonOptions options, bool binary) const
|
||||
ret[jss::inLedger] = mLedgerIndex;
|
||||
}
|
||||
|
||||
// TODO: disable_API_prior_V3 to disable output of both `date` and
|
||||
// `ledger_index` elements (taking precedence over include_date)
|
||||
ret[jss::ledger_index] = mLedgerIndex;
|
||||
|
||||
if (options & JsonOptions::include_date)
|
||||
if (!(options & JsonOptions::disable_API_prior_V3))
|
||||
{
|
||||
auto ct = mApp.getLedgerMaster().getCloseTimeBySeq(mLedgerIndex);
|
||||
if (ct)
|
||||
ret[jss::date] = ct->time_since_epoch().count();
|
||||
}
|
||||
ret[jss::ledger_index] = mLedgerIndex;
|
||||
|
||||
// compute outgoing CTID
|
||||
// override local network id if it's explicitly in the txn
|
||||
std::optional netID = mNetworkID;
|
||||
if (mTransaction->isFieldPresent(sfNetworkID))
|
||||
netID = mTransaction->getFieldU32(sfNetworkID);
|
||||
if (options & JsonOptions::include_date)
|
||||
{
|
||||
auto ct = mApp.getLedgerMaster().getCloseTimeBySeq(mLedgerIndex);
|
||||
if (ct)
|
||||
ret[jss::date] = ct->time_since_epoch().count();
|
||||
}
|
||||
|
||||
if (mTxnSeq && netID)
|
||||
{
|
||||
std::optional<std::string> const ctid = RPC::encodeCTID(mLedgerIndex, *mTxnSeq, *netID);
|
||||
if (ctid)
|
||||
ret[jss::ctid] = *ctid;
|
||||
// compute outgoing CTID
|
||||
// override local network id if it's explicitly in the txn
|
||||
std::optional netID = mNetworkID;
|
||||
if (mTransaction->isFieldPresent(sfNetworkID))
|
||||
netID = mTransaction->getFieldU32(sfNetworkID);
|
||||
|
||||
if (mTxnSeq && netID)
|
||||
{
|
||||
std::optional<std::string> const ctid =
|
||||
RPC::encodeCTID(mLedgerIndex, *mTxnSeq, *netID);
|
||||
if (ctid)
|
||||
ret[jss::ctid] = *ctid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <xrpld/overlay/detail/Tuning.h>
|
||||
#include <xrpld/overlay/predicates.h>
|
||||
#include <xrpld/peerfinder/make_Manager.h>
|
||||
#include <xrpld/rpc/handlers/GetCounts.h>
|
||||
#include <xrpld/rpc/handlers/admin/status/GetCounts.h>
|
||||
#include <xrpld/rpc/json_body.h>
|
||||
|
||||
#include <xrpl/basics/base64.h>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <xrpld/rpc/detail/Handler.h>
|
||||
#include <xrpld/rpc/handlers/Handlers.h>
|
||||
#include <xrpld/rpc/handlers/Version.h>
|
||||
#include <xrpld/rpc/handlers/server_info/Version.h>
|
||||
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
@@ -75,7 +75,7 @@ Handler const handlerArray[]{
|
||||
{"account_nfts", byRef(&doAccountNFTs), Role::USER, NO_CONDITION},
|
||||
{"account_objects", byRef(&doAccountObjects), Role::USER, NO_CONDITION},
|
||||
{"account_offers", byRef(&doAccountOffers), Role::USER, NO_CONDITION},
|
||||
{"account_tx", byRef(&doAccountTxJson), Role::USER, NO_CONDITION},
|
||||
{"account_tx", byRef(&doAccountTx), Role::USER, NO_CONDITION},
|
||||
{"amm_info", byRef(&doAMMInfo), Role::USER, NO_CONDITION},
|
||||
{"blacklist", byRef(&doBlackList), Role::ADMIN, NO_CONDITION},
|
||||
{"book_changes", byRef(&doBookChanges), Role::USER, NO_CONDITION},
|
||||
|
||||
71
src/xrpld/rpc/handlers/ChannelVerify.cpp
Normal file
71
src/xrpld/rpc/handlers/ChannelVerify.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/PayChan.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// {
|
||||
// public_key: <public_key>
|
||||
// channel_id: 256-bit channel id
|
||||
// drops: 64-bit uint (as string)
|
||||
// signature: signature to verify
|
||||
// }
|
||||
Json::Value
|
||||
doChannelVerify(RPC::JsonContext& context)
|
||||
{
|
||||
auto const& params(context.params);
|
||||
for (auto const& p : {jss::public_key, jss::channel_id, jss::amount, jss::signature})
|
||||
{
|
||||
if (!params.isMember(p))
|
||||
return RPC::missing_field_error(p);
|
||||
}
|
||||
|
||||
std::optional<PublicKey> pk;
|
||||
{
|
||||
std::string const strPk = params[jss::public_key].asString();
|
||||
pk = parseBase58<PublicKey>(TokenType::AccountPublic, strPk);
|
||||
|
||||
if (!pk)
|
||||
{
|
||||
auto pkHex = strUnHex(strPk);
|
||||
if (!pkHex)
|
||||
return rpcError(rpcPUBLIC_MALFORMED);
|
||||
auto const pkType = publicKeyType(makeSlice(*pkHex));
|
||||
if (!pkType)
|
||||
return rpcError(rpcPUBLIC_MALFORMED);
|
||||
pk.emplace(makeSlice(*pkHex));
|
||||
}
|
||||
}
|
||||
|
||||
uint256 channelId;
|
||||
if (!channelId.parseHex(params[jss::channel_id].asString()))
|
||||
return rpcError(rpcCHANNEL_MALFORMED);
|
||||
|
||||
std::optional<std::uint64_t> const optDrops =
|
||||
params[jss::amount].isString() ? to_uint64(params[jss::amount].asString()) : std::nullopt;
|
||||
|
||||
if (!optDrops)
|
||||
return rpcError(rpcCHANNEL_AMT_MALFORMED);
|
||||
|
||||
std::uint64_t const drops = *optDrops;
|
||||
|
||||
auto sig = strUnHex(params[jss::signature].asString());
|
||||
if (!sig || sig->empty())
|
||||
return rpcError(rpcINVALID_PARAMS);
|
||||
|
||||
Serializer msg;
|
||||
serializePayChanAuthorization(msg, channelId, XRPAmount(drops));
|
||||
|
||||
Json::Value result;
|
||||
result[jss::signature_verified] = verify(*pk, msg.slice(), makeSlice(*sig));
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpld/rpc/handlers/LedgerHandler.h>
|
||||
#include <xrpld/rpc/handlers/ledger/Ledger.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -19,7 +19,7 @@ doAccountObjects(RPC::JsonContext&);
|
||||
Json::Value
|
||||
doAccountOffers(RPC::JsonContext&);
|
||||
Json::Value
|
||||
doAccountTxJson(RPC::JsonContext&);
|
||||
doAccountTx(RPC::JsonContext&);
|
||||
Json::Value
|
||||
doAMMInfo(RPC::JsonContext&);
|
||||
Json::Value
|
||||
|
||||
160
src/xrpld/rpc/handlers/account/AccountNFTs.cpp
Normal file
160
src/xrpld/rpc/handlers/account/AccountNFTs.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
#include <xrpld/rpc/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol/nftPageMask.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/** General RPC command that can retrieve objects in the account root.
|
||||
{
|
||||
account: <account>
|
||||
ledger_hash: <string> // optional
|
||||
ledger_index: <string | unsigned integer> // optional
|
||||
type: <string> // optional, defaults to all account objects types
|
||||
limit: <integer> // optional
|
||||
marker: <opaque> // optional, resume previous query
|
||||
}
|
||||
*/
|
||||
Json::Value
|
||||
doAccountNFTs(RPC::JsonContext& context)
|
||||
{
|
||||
auto const& params = context.params;
|
||||
if (!params.isMember(jss::account))
|
||||
return RPC::missing_field_error(jss::account);
|
||||
|
||||
if (!params[jss::account].isString())
|
||||
return RPC::invalid_field_error(jss::account);
|
||||
|
||||
auto id = parseBase58<AccountID>(params[jss::account].asString());
|
||||
if (!id)
|
||||
{
|
||||
return rpcError(rpcACT_MALFORMED);
|
||||
}
|
||||
|
||||
std::shared_ptr<ReadView const> ledger;
|
||||
auto result = RPC::lookupLedger(ledger, context);
|
||||
if (ledger == nullptr)
|
||||
return result;
|
||||
auto const accountID{id.value()};
|
||||
|
||||
if (!ledger->exists(keylet::account(accountID)))
|
||||
return rpcError(rpcACT_NOT_FOUND);
|
||||
|
||||
unsigned int limit = 0;
|
||||
if (auto err = readLimitField(limit, RPC::Tuning::accountNFTokens, context))
|
||||
return *err;
|
||||
|
||||
uint256 marker;
|
||||
bool const markerSet = params.isMember(jss::marker);
|
||||
|
||||
if (markerSet)
|
||||
{
|
||||
auto const& m = params[jss::marker];
|
||||
if (!m.isString())
|
||||
return RPC::expected_field_error(jss::marker, "string");
|
||||
|
||||
if (!marker.parseHex(m.asString()))
|
||||
return RPC::invalid_field_error(jss::marker);
|
||||
}
|
||||
|
||||
auto const first = keylet::nftpage(keylet::nftpage_min(accountID), marker);
|
||||
auto const last = keylet::nftpage_max(accountID);
|
||||
|
||||
auto cp = ledger->read(
|
||||
Keylet(ltNFTOKEN_PAGE, ledger->succ(first.key, last.key.next()).value_or(last.key)));
|
||||
|
||||
std::uint32_t cnt = 0;
|
||||
auto& nfts = (result[jss::account_nfts] = Json::arrayValue);
|
||||
|
||||
// Continue iteration from the current page:
|
||||
bool pastMarker = marker.isZero();
|
||||
bool markerFound = false;
|
||||
uint256 const maskedMarker = marker & nft::pageMask;
|
||||
while (cp)
|
||||
{
|
||||
auto arr = cp->getFieldArray(sfNFTokens);
|
||||
|
||||
for (auto const& o : arr)
|
||||
{
|
||||
// Scrolling past the marker gets weird. We need to look at
|
||||
// a couple of conditions.
|
||||
//
|
||||
// 1. If the low 96-bits don't match, then we compare only
|
||||
// against the low 96-bits, since that's what determines
|
||||
// the sort order of the pages.
|
||||
//
|
||||
// 2. However, within one page there can be a number of
|
||||
// NFTokenIDs that all have the same low 96 bits. If we're
|
||||
// in that case then we need to compare against the full
|
||||
// 256 bits.
|
||||
uint256 const nftokenID = o[sfNFTokenID];
|
||||
uint256 const maskedNftokenID = nftokenID & nft::pageMask;
|
||||
|
||||
if (!pastMarker)
|
||||
{
|
||||
if (maskedNftokenID < maskedMarker)
|
||||
continue;
|
||||
|
||||
if (maskedNftokenID == maskedMarker && nftokenID < marker)
|
||||
continue;
|
||||
|
||||
if (nftokenID == marker)
|
||||
{
|
||||
markerFound = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (markerSet && !markerFound)
|
||||
return RPC::invalid_field_error(jss::marker);
|
||||
|
||||
pastMarker = true;
|
||||
|
||||
{
|
||||
Json::Value& obj = nfts.append(o.getJson(JsonOptions::none));
|
||||
|
||||
// Pull out the components of the nft ID.
|
||||
obj[sfFlags.jsonName] = nft::getFlags(nftokenID);
|
||||
obj[sfIssuer.jsonName] = to_string(nft::getIssuer(nftokenID));
|
||||
obj[sfNFTokenTaxon.jsonName] = nft::toUInt32(nft::getTaxon(nftokenID));
|
||||
obj[jss::nft_serial] = nft::getSerial(nftokenID);
|
||||
if (std::uint16_t const xferFee = {nft::getTransferFee(nftokenID)})
|
||||
obj[sfTransferFee.jsonName] = xferFee;
|
||||
}
|
||||
|
||||
if (++cnt == limit)
|
||||
{
|
||||
result[jss::limit] = limit;
|
||||
result[jss::marker] = to_string(o.getFieldH256(sfNFTokenID));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto npm = (*cp)[~sfNextPageMin])
|
||||
{
|
||||
cp = ledger->read(Keylet(ltNFTOKEN_PAGE, *npm));
|
||||
}
|
||||
else
|
||||
{
|
||||
cp = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (markerSet && !markerFound)
|
||||
return RPC::invalid_field_error(jss::marker);
|
||||
|
||||
result[jss::account] = toBase58(accountID);
|
||||
context.loadType = Resource::feeMediumBurdenRPC;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -11,156 +11,11 @@
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol/nftPageMask.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/** General RPC command that can retrieve objects in the account root.
|
||||
{
|
||||
account: <account>
|
||||
ledger_hash: <string> // optional
|
||||
ledger_index: <string | unsigned integer> // optional
|
||||
type: <string> // optional, defaults to all account objects types
|
||||
limit: <integer> // optional
|
||||
marker: <opaque> // optional, resume previous query
|
||||
}
|
||||
*/
|
||||
|
||||
Json::Value
|
||||
doAccountNFTs(RPC::JsonContext& context)
|
||||
{
|
||||
auto const& params = context.params;
|
||||
if (!params.isMember(jss::account))
|
||||
return RPC::missing_field_error(jss::account);
|
||||
|
||||
if (!params[jss::account].isString())
|
||||
return RPC::invalid_field_error(jss::account);
|
||||
|
||||
auto id = parseBase58<AccountID>(params[jss::account].asString());
|
||||
if (!id)
|
||||
{
|
||||
return rpcError(rpcACT_MALFORMED);
|
||||
}
|
||||
|
||||
std::shared_ptr<ReadView const> ledger;
|
||||
auto result = RPC::lookupLedger(ledger, context);
|
||||
if (ledger == nullptr)
|
||||
return result;
|
||||
auto const accountID{id.value()};
|
||||
|
||||
if (!ledger->exists(keylet::account(accountID)))
|
||||
return rpcError(rpcACT_NOT_FOUND);
|
||||
|
||||
unsigned int limit = 0;
|
||||
if (auto err = readLimitField(limit, RPC::Tuning::accountNFTokens, context))
|
||||
return *err;
|
||||
|
||||
uint256 marker;
|
||||
bool const markerSet = params.isMember(jss::marker);
|
||||
|
||||
if (markerSet)
|
||||
{
|
||||
auto const& m = params[jss::marker];
|
||||
if (!m.isString())
|
||||
return RPC::expected_field_error(jss::marker, "string");
|
||||
|
||||
if (!marker.parseHex(m.asString()))
|
||||
return RPC::invalid_field_error(jss::marker);
|
||||
}
|
||||
|
||||
auto const first = keylet::nftpage(keylet::nftpage_min(accountID), marker);
|
||||
auto const last = keylet::nftpage_max(accountID);
|
||||
|
||||
auto cp = ledger->read(
|
||||
Keylet(ltNFTOKEN_PAGE, ledger->succ(first.key, last.key.next()).value_or(last.key)));
|
||||
|
||||
std::uint32_t cnt = 0;
|
||||
auto& nfts = (result[jss::account_nfts] = Json::arrayValue);
|
||||
|
||||
// Continue iteration from the current page:
|
||||
bool pastMarker = marker.isZero();
|
||||
bool markerFound = false;
|
||||
uint256 const maskedMarker = marker & nft::pageMask;
|
||||
while (cp)
|
||||
{
|
||||
auto arr = cp->getFieldArray(sfNFTokens);
|
||||
|
||||
for (auto const& o : arr)
|
||||
{
|
||||
// Scrolling past the marker gets weird. We need to look at
|
||||
// a couple of conditions.
|
||||
//
|
||||
// 1. If the low 96-bits don't match, then we compare only
|
||||
// against the low 96-bits, since that's what determines
|
||||
// the sort order of the pages.
|
||||
//
|
||||
// 2. However, within one page there can be a number of
|
||||
// NFTokenIDs that all have the same low 96 bits. If we're
|
||||
// in that case then we need to compare against the full
|
||||
// 256 bits.
|
||||
uint256 const nftokenID = o[sfNFTokenID];
|
||||
uint256 const maskedNftokenID = nftokenID & nft::pageMask;
|
||||
|
||||
if (!pastMarker)
|
||||
{
|
||||
if (maskedNftokenID < maskedMarker)
|
||||
continue;
|
||||
|
||||
if (maskedNftokenID == maskedMarker && nftokenID < marker)
|
||||
continue;
|
||||
|
||||
if (nftokenID == marker)
|
||||
{
|
||||
markerFound = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (markerSet && !markerFound)
|
||||
return RPC::invalid_field_error(jss::marker);
|
||||
|
||||
pastMarker = true;
|
||||
|
||||
{
|
||||
Json::Value& obj = nfts.append(o.getJson(JsonOptions::none));
|
||||
|
||||
// Pull out the components of the nft ID.
|
||||
obj[sfFlags.jsonName] = nft::getFlags(nftokenID);
|
||||
obj[sfIssuer.jsonName] = to_string(nft::getIssuer(nftokenID));
|
||||
obj[sfNFTokenTaxon.jsonName] = nft::toUInt32(nft::getTaxon(nftokenID));
|
||||
obj[jss::nft_serial] = nft::getSerial(nftokenID);
|
||||
if (std::uint16_t const xferFee = {nft::getTransferFee(nftokenID)})
|
||||
obj[sfTransferFee.jsonName] = xferFee;
|
||||
}
|
||||
|
||||
if (++cnt == limit)
|
||||
{
|
||||
result[jss::limit] = limit;
|
||||
result[jss::marker] = to_string(o.getFieldH256(sfNFTokenID));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto npm = (*cp)[~sfNextPageMin])
|
||||
{
|
||||
cp = ledger->read(Keylet(ltNFTOKEN_PAGE, *npm));
|
||||
}
|
||||
else
|
||||
{
|
||||
cp = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (markerSet && !markerFound)
|
||||
return RPC::invalid_field_error(jss::marker);
|
||||
|
||||
result[jss::account] = toBase58(accountID);
|
||||
context.loadType = Resource::feeMediumBurdenRPC;
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Gathers all objects for an account in a ledger.
|
||||
@param ledger Ledger to search account objects.
|
||||
@param account AccountID to find objects for.
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpld/app/misc/DeliverMax.h>
|
||||
#include <xrpld/app/misc/Transaction.h>
|
||||
#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
|
||||
#include <xrpld/rpc/CTID.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/DeliveredAmount.h>
|
||||
#include <xrpld/rpc/MPTokenIssuanceID.h>
|
||||
@@ -11,6 +12,7 @@
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
#include <xrpld/rpc/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/core/NetworkIDService.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
@@ -289,8 +291,10 @@ populateJsonResponse(
|
||||
auto const json_tx = (context.apiVersion > 1 ? jss::tx_json : jss::tx);
|
||||
if (context.apiVersion > 1)
|
||||
{
|
||||
jvObj[json_tx] = txn->getJson(
|
||||
JsonOptions::include_date | JsonOptions::disable_API_prior_V2, false);
|
||||
auto const opts = context.apiVersion >= 3
|
||||
? JsonOptions::disable_API_prior_V2 | JsonOptions::disable_API_prior_V3
|
||||
: JsonOptions::include_date | JsonOptions::disable_API_prior_V2;
|
||||
jvObj[json_tx] = txn->getJson(opts, false);
|
||||
jvObj[jss::hash] = to_string(txn->getID());
|
||||
jvObj[jss::ledger_index] = txn->getLedger();
|
||||
jvObj[jss::ledger_hash] =
|
||||
@@ -298,7 +302,20 @@ populateJsonResponse(
|
||||
|
||||
if (auto closeTime =
|
||||
context.ledgerMaster.getCloseTimeBySeq(txn->getLedger()))
|
||||
{
|
||||
jvObj[jss::close_time_iso] = to_string_iso(*closeTime);
|
||||
if (context.apiVersion >= 3)
|
||||
jvObj[jss::date] = closeTime->time_since_epoch().count();
|
||||
}
|
||||
|
||||
if (context.apiVersion >= 3 && txnMeta)
|
||||
{
|
||||
uint32_t const lgrSeq = txn->getLedger();
|
||||
uint32_t const txnIdx = txnMeta->getIndex();
|
||||
uint32_t const netID = context.app.getNetworkIDService().getNetworkID();
|
||||
if (auto const ctid = RPC::encodeCTID(lgrSeq, txnIdx, netID))
|
||||
jvObj[jss::ctid] = *ctid;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -364,7 +381,7 @@ populateJsonResponse(
|
||||
// resume previous query
|
||||
// }
|
||||
Json::Value
|
||||
doAccountTxJson(RPC::JsonContext& context)
|
||||
doAccountTx(RPC::JsonContext& context)
|
||||
{
|
||||
if (!context.app.config().useTxTables())
|
||||
return rpcError(rpcNOT_ENABLED);
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/handlers/WalletPropose.h>
|
||||
#include <xrpld/rpc/handlers/admin/keygen/WalletPropose.h>
|
||||
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -66,46 +65,4 @@ doPeerReservationsAdd(RPC::JsonContext& context)
|
||||
return result;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
doPeerReservationsDel(RPC::JsonContext& context)
|
||||
{
|
||||
auto const& params = context.params;
|
||||
|
||||
// We repeat much of the parameter parsing from `doPeerReservationsAdd`.
|
||||
if (!params.isMember(jss::public_key))
|
||||
return RPC::missing_field_error(jss::public_key);
|
||||
if (!params[jss::public_key].isString())
|
||||
return RPC::expected_field_error(jss::public_key, "a string");
|
||||
|
||||
std::optional<PublicKey> optPk =
|
||||
parseBase58<PublicKey>(TokenType::NodePublic, params[jss::public_key].asString());
|
||||
if (!optPk)
|
||||
return rpcError(rpcPUBLIC_MALFORMED);
|
||||
PublicKey const& nodeId = *optPk;
|
||||
|
||||
auto const previous = context.app.getPeerReservations().erase(nodeId);
|
||||
|
||||
Json::Value result{Json::objectValue};
|
||||
if (previous)
|
||||
{
|
||||
result[jss::previous] = previous->toJson();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
doPeerReservationsList(RPC::JsonContext& context)
|
||||
{
|
||||
auto const& reservations = context.app.getPeerReservations().list();
|
||||
// Enumerate the reservations in context.app.getPeerReservations()
|
||||
// as a Json::Value.
|
||||
Json::Value result{Json::objectValue};
|
||||
Json::Value& jaReservations = result[jss::reservations] = Json::arrayValue;
|
||||
for (auto const& reservation : reservations)
|
||||
{
|
||||
jaReservations.append(reservation.toJson());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
41
src/xrpld/rpc/handlers/admin/peer/PeerReservationsDel.cpp
Normal file
41
src/xrpld/rpc/handlers/admin/peer/PeerReservationsDel.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/handlers/Handlers.h>
|
||||
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
Json::Value
|
||||
doPeerReservationsDel(RPC::JsonContext& context)
|
||||
{
|
||||
auto const& params = context.params;
|
||||
|
||||
// We repeat much of the parameter parsing from `doPeerReservationsAdd`.
|
||||
if (!params.isMember(jss::public_key))
|
||||
return RPC::missing_field_error(jss::public_key);
|
||||
if (!params[jss::public_key].isString())
|
||||
return RPC::expected_field_error(jss::public_key, "a string");
|
||||
|
||||
std::optional<PublicKey> optPk =
|
||||
parseBase58<PublicKey>(TokenType::NodePublic, params[jss::public_key].asString());
|
||||
if (!optPk)
|
||||
return rpcError(rpcPUBLIC_MALFORMED);
|
||||
PublicKey const& nodeId = *optPk;
|
||||
|
||||
auto const previous = context.app.getPeerReservations().erase(nodeId);
|
||||
|
||||
Json::Value result{Json::objectValue};
|
||||
if (previous)
|
||||
{
|
||||
result[jss::previous] = previous->toJson();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
24
src/xrpld/rpc/handlers/admin/peer/PeerReservationsList.cpp
Normal file
24
src/xrpld/rpc/handlers/admin/peer/PeerReservationsList.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/handlers/Handlers.h>
|
||||
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
Json::Value
|
||||
doPeerReservationsList(RPC::JsonContext& context)
|
||||
{
|
||||
auto const& reservations = context.app.getPeerReservations().list();
|
||||
// Enumerate the reservations in context.app.getPeerReservations()
|
||||
// as a Json::Value.
|
||||
Json::Value result{Json::objectValue};
|
||||
Json::Value& jaReservations = result[jss::reservations] = Json::arrayValue;
|
||||
for (auto const& reservation : reservations)
|
||||
{
|
||||
jaReservations.append(reservation.toJson());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/PayChan.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
@@ -83,61 +82,4 @@ doChannelAuthorize(RPC::JsonContext& context)
|
||||
return result;
|
||||
}
|
||||
|
||||
// {
|
||||
// public_key: <public_key>
|
||||
// channel_id: 256-bit channel id
|
||||
// drops: 64-bit uint (as string)
|
||||
// signature: signature to verify
|
||||
// }
|
||||
Json::Value
|
||||
doChannelVerify(RPC::JsonContext& context)
|
||||
{
|
||||
auto const& params(context.params);
|
||||
for (auto const& p : {jss::public_key, jss::channel_id, jss::amount, jss::signature})
|
||||
{
|
||||
if (!params.isMember(p))
|
||||
return RPC::missing_field_error(p);
|
||||
}
|
||||
|
||||
std::optional<PublicKey> pk;
|
||||
{
|
||||
std::string const strPk = params[jss::public_key].asString();
|
||||
pk = parseBase58<PublicKey>(TokenType::AccountPublic, strPk);
|
||||
|
||||
if (!pk)
|
||||
{
|
||||
auto pkHex = strUnHex(strPk);
|
||||
if (!pkHex)
|
||||
return rpcError(rpcPUBLIC_MALFORMED);
|
||||
auto const pkType = publicKeyType(makeSlice(*pkHex));
|
||||
if (!pkType)
|
||||
return rpcError(rpcPUBLIC_MALFORMED);
|
||||
pk.emplace(makeSlice(*pkHex));
|
||||
}
|
||||
}
|
||||
|
||||
uint256 channelId;
|
||||
if (!channelId.parseHex(params[jss::channel_id].asString()))
|
||||
return rpcError(rpcCHANNEL_MALFORMED);
|
||||
|
||||
std::optional<std::uint64_t> const optDrops =
|
||||
params[jss::amount].isString() ? to_uint64(params[jss::amount].asString()) : std::nullopt;
|
||||
|
||||
if (!optDrops)
|
||||
return rpcError(rpcCHANNEL_AMT_MALFORMED);
|
||||
|
||||
std::uint64_t const drops = *optDrops;
|
||||
|
||||
auto sig = strUnHex(params[jss::signature].asString());
|
||||
if (!sig || sig->empty())
|
||||
return rpcError(rpcINVALID_PARAMS);
|
||||
|
||||
Serializer msg;
|
||||
serializePayChanAuthorization(msg, channelId, XRPAmount(drops));
|
||||
|
||||
Json::Value result;
|
||||
result[jss::signature_verified] = verify(*pk, msg.slice(), makeSlice(*sig));
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <xrpld/rpc/GRPCHandlers.h>
|
||||
#include <xrpld/rpc/Role.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
#include <xrpld/rpc/handlers/LedgerHandler.h>
|
||||
#include <xrpld/rpc/handlers/ledger/Ledger.h>
|
||||
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/GRPCHandlers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
#include <xrpld/rpc/handlers/LedgerEntryHelpers.h>
|
||||
#include <xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h>
|
||||
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
21
src/xrpld/rpc/handlers/orderbook/BookChanges.cpp
Normal file
21
src/xrpld/rpc/handlers/orderbook/BookChanges.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include <xrpld/rpc/BookChanges.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
Json::Value
|
||||
doBookChanges(RPC::JsonContext& context)
|
||||
{
|
||||
std::shared_ptr<ReadView const> ledger;
|
||||
|
||||
Json::Value result = RPC::lookupLedger(ledger, context);
|
||||
if (ledger == nullptr)
|
||||
return result;
|
||||
|
||||
return RPC::computeBookChanges(ledger);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,5 +1,4 @@
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/rpc/BookChanges.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
@@ -205,16 +204,4 @@ doBookOffers(RPC::JsonContext& context)
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
doBookChanges(RPC::JsonContext& context)
|
||||
{
|
||||
std::shared_ptr<ReadView const> ledger;
|
||||
|
||||
Json::Value result = RPC::lookupLedger(ledger, context);
|
||||
if (ledger == nullptr)
|
||||
return result;
|
||||
|
||||
return RPC::computeBookChanges(ledger);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
24
src/xrpld/rpc/handlers/orderbook/NFTBuyOffers.cpp
Normal file
24
src/xrpld/rpc/handlers/orderbook/NFTBuyOffers.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/handlers/orderbook/NFTOffersHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
Json::Value
|
||||
doNFTBuyOffers(RPC::JsonContext& context)
|
||||
{
|
||||
if (!context.params.isMember(jss::nft_id))
|
||||
return RPC::missing_field_error(jss::nft_id);
|
||||
|
||||
uint256 nftId;
|
||||
|
||||
if (!nftId.parseHex(context.params[jss::nft_id].asString()))
|
||||
return RPC::invalid_field_error(jss::nft_id);
|
||||
|
||||
return enumerateNFTOffers(context, nftId, keylet::nft_buys(nftId));
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
@@ -14,7 +16,7 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
static void
|
||||
inline void
|
||||
appendNftOfferJson(
|
||||
Application const& app,
|
||||
std::shared_ptr<SLE const> const& offer,
|
||||
@@ -42,7 +44,7 @@ appendNftOfferJson(
|
||||
// limit: integer // optional
|
||||
// marker: opaque // optional, resume previous query
|
||||
// }
|
||||
static Json::Value
|
||||
inline Json::Value
|
||||
enumerateNFTOffers(RPC::JsonContext& context, uint256 const& nftId, Keylet const& directory)
|
||||
{
|
||||
unsigned int limit = 0;
|
||||
@@ -127,32 +129,4 @@ enumerateNFTOffers(RPC::JsonContext& context, uint256 const& nftId, Keylet const
|
||||
return result;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
doNFTSellOffers(RPC::JsonContext& context)
|
||||
{
|
||||
if (!context.params.isMember(jss::nft_id))
|
||||
return RPC::missing_field_error(jss::nft_id);
|
||||
|
||||
uint256 nftId;
|
||||
|
||||
if (!nftId.parseHex(context.params[jss::nft_id].asString()))
|
||||
return RPC::invalid_field_error(jss::nft_id);
|
||||
|
||||
return enumerateNFTOffers(context, nftId, keylet::nft_sells(nftId));
|
||||
}
|
||||
|
||||
Json::Value
|
||||
doNFTBuyOffers(RPC::JsonContext& context)
|
||||
{
|
||||
if (!context.params.isMember(jss::nft_id))
|
||||
return RPC::missing_field_error(jss::nft_id);
|
||||
|
||||
uint256 nftId;
|
||||
|
||||
if (!nftId.parseHex(context.params[jss::nft_id].asString()))
|
||||
return RPC::invalid_field_error(jss::nft_id);
|
||||
|
||||
return enumerateNFTOffers(context, nftId, keylet::nft_buys(nftId));
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
24
src/xrpld/rpc/handlers/orderbook/NFTSellOffers.cpp
Normal file
24
src/xrpld/rpc/handlers/orderbook/NFTSellOffers.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/handlers/orderbook/NFTOffersHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
Json::Value
|
||||
doNFTSellOffers(RPC::JsonContext& context)
|
||||
{
|
||||
if (!context.params.isMember(jss::nft_id))
|
||||
return RPC::missing_field_error(jss::nft_id);
|
||||
|
||||
uint256 nftId;
|
||||
|
||||
if (!nftId.parseHex(context.params[jss::nft_id].asString()))
|
||||
return RPC::invalid_field_error(jss::nft_id);
|
||||
|
||||
return enumerateNFTOffers(context, nftId, keylet::nft_sells(nftId));
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user