Compare commits

..

30 Commits

Author SHA1 Message Date
Denis Angell
1ac1d40d06 fix: satisfy clang-tidy include-cleaner and brace style in paychan 2026-08-03 12:26:20 -04:00
Denis Angell
fb10515cff Merge remote-tracking branch 'origin/develop' into token-paychan
# Conflicts:
#	include/xrpl/protocol/detail/features.macro
2026-08-03 11:38:43 -04:00
Denis Angell
3c85a0180f refactor: address review nits 2026-08-02 16:05:25 -04:00
Denis Angell
096fa967da fix: repair bad develop merge resolutions 2026-08-02 16:05:22 -04:00
Alex Kremer
ecdd457f35 chore: Gtest migration followups first pass (#7884) 2026-07-30 23:04:38 +00:00
Ayaz Salikhov
85e73cbd32 ci: Run coverage first in CI (#7917) 2026-07-30 19:28:31 +00:00
Bart
21cd615407 perf: Replace node ID by depth in TMLedgerNode (#6353)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-07-30 15:02:05 +00:00
Olek
3c0659d26b test: Add confidential mpt bulletproof tests (#7816) 2026-07-30 12:18:33 +00:00
Copilot
532506541f fix: Apply asfDisallowIncomingTrustline blocker to OfferCreate (#6307)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Mayukha Vadari <mvadari@gmail.com>
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
2026-07-30 12:17:32 +00:00
Vito Tumas
8a5eded4f1 feat: Implement LoanBroker cash-basis accounting (#7817) 2026-07-30 11:55:39 +00:00
Alex Kremer
6ddad54985 chore: Move lexical cast tests to gtest (#7873) 2026-07-29 22:54:46 +00:00
Alex Kremer
86832edc70 chore: Move semantic version tests to gtest (#7872)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-27 18:48:53 +00:00
Ayaz Salikhov
ff7fff2cf2 style: Make clang-tidy format files using clang-format rules (#7880) 2026-07-27 18:39:53 +00:00
Mayukha Vadari
2db631c915 ci: Change server_definitions upload config name (#7878) 2026-07-27 17:42:07 +00:00
Alex Kremer
6c9c7f0555 chore: Trivial gtest migrations (#7865) 2026-07-27 16:34:53 +00:00
Ayaz Salikhov
9466ecb5c2 ci: Group github-actions dependabot updates (#7876) 2026-07-27 14:32:53 +00:00
Sergey Kuznetsov
e0aa50c4be ci: Update CI image and prepare-runner action (#7874) 2026-07-27 14:18:01 +00:00
Andrzej Budzanowski
29120dfcbd test: Migrate nodestore tests from Beast to GTest (#7292)
Co-authored-by: Marek Foss <marek.foss@neti-soft.com>
Co-authored-by: Alex Kremer <akremer@ripple.com>
2026-07-27 13:00:14 +00:00
Andrzej Budzanowski
20801d98ac test: Improve the server status test to not race and randomly fail (#7304)
Co-authored-by: Alex Kremer <akremer@ripple.com>
2026-07-27 11:58:15 +00:00
dependabot[bot]
b878818e80 ci: [DEPENDABOT] bump actions/checkout from 7.0.0 to 7.0.1 (#7871)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 09:55:01 +00:00
dangell8
1ced7dc8f3 Merge branch 'develop' into token-paychan
Includes post-merge compile fixes (build-verified).
2026-07-17 15:02:03 +00:00
Denis Angell
df1458a136 test: expand token paychan transactor phase coverage 2026-07-13 19:25:43 -04:00
Denis Angell
cdd5ebf4c1 fix: validate paychan asset match in preclaim with tecWRONG_ASSET 2026-07-13 16:33:49 -04:00
Denis Angell
1a9a11fc31 style: remove includes flagged by misc-include-cleaner 2026-07-13 15:28:42 -04:00
Denis Angell
a9430d4778 style: conform doxygen comments to check-doxygen-style hook 2026-07-13 15:11:48 -04:00
Denis Angell
fed5093488 fix: adapt token paychan to develop merge helpers and sponsor reserve 2026-07-13 14:59:54 -04:00
Denis Angell
6c74fc1a7d Merge branch 'develop' into token-paychan 2026-07-13 14:05:56 -04:00
dangell8
939f8b1a49 Merge develop into token-paychan 2026-07-11 04:27:08 +00:00
Denis Angell
1f8c462c42 feat: XLS-93d Token-Enabled Payment Channels 2026-07-10 12:14:12 -04:00
Denis Angell
47df026dcc refactor: Move escrow lock/unlock helpers to EscrowHelpers.h 2026-07-10 11:55:13 -04:00
210 changed files with 12311 additions and 6325 deletions

View File

@@ -75,6 +75,8 @@ Checks: "-*,
# readability-static-accessed-through-instance, # this check is probably unnecessary. It makes the code less readable
# ---
FormatStyle: file
CheckOptions:
bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true
bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc

View File

@@ -174,7 +174,6 @@ words:
- MPTAMM
- MPTDEX
- Merkle
- Metafuncton
- misprediction
- missingok
- mptbalance

View File

@@ -15,3 +15,7 @@ updates:
commit-message:
prefix: "ci: [DEPENDABOT] "
target-branch: develop
groups:
github-actions:
patterns:
- "*"

View File

@@ -130,12 +130,9 @@ test.ledger > xrpl.json
test.ledger > xrpl.ledger
test.ledger > xrpl.protocol
test.nodestore > test.jtx
test.nodestore > test.unit_test
test.nodestore > xrpl.basics
test.nodestore > xrpl.config
test.nodestore > xrpld.core
test.nodestore > xrpl.nodestore
test.nodestore > xrpl.protocol
test.nodestore > xrpl.rdb
test.overlay > test.jtx
test.overlay > test.unit_test

View File

@@ -46,9 +46,6 @@ for DIRECTORY in "${DIRECTORIES[@]}"; do
done
done
# Special case for NuDBFactory that has ripple twice in the test suite name.
${SED_COMMAND} -i -E 's/(BEAST_DEFINE_TESTSUITE.+)ripple(.+)/\1xrpl\2/g' src/test/nodestore/NuDBFactory_test.cpp
DIRECTORY=$1
find "${DIRECTORY}" -type f -name "*.md" | while read -r FILE; do
echo "Processing file: ${FILE}"

View File

@@ -1,13 +1,22 @@
{
"image_tag": "sha-40cdf49",
"image_tag": "sha-fecfc0c",
"configs": {
"ubuntu": [
{
"compiler": ["gcc"],
"build_type": ["Debug"],
"arch": ["amd64"],
"minimal": true,
"suffix": "coverage",
"extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=500 -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0"
},
{
"compiler": ["clang"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": true
},
{
"compiler": ["gcc"],
"build_type": ["Release"],
@@ -29,14 +38,6 @@
"sanitizers": ["address", "undefinedbehavior"]
},
{
"compiler": ["gcc"],
"build_type": ["Debug"],
"arch": ["amd64"],
"minimal": true,
"suffix": "coverage",
"extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=500 -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0"
},
{
"compiler": ["clang"],
"build_type": ["Debug"],

View File

@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Write PR body to file
env:

View File

@@ -47,7 +47,7 @@ jobs:
tag: ${{ steps.tag.outputs.tag }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Read nix image tag
id: tag
@@ -76,10 +76,10 @@ jobs:
container: ${{ !matrix.nix_develop && format('ghcr.io/xrplf/xrpld/nix-ubuntu:{0}', needs.linux-image-tag.outputs.tag) || null }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
with:
enable_ccache: false

View File

@@ -52,7 +52,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Determine changed files
# This step checks whether any files have changed that should
# cause the next jobs to run. We do it this way rather than

View File

@@ -41,13 +41,13 @@ env:
jobs:
build:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-40cdf49
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fecfc0c
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
with:
enable_ccache: false

View File

@@ -110,10 +110,10 @@ jobs:
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
with:
enable_ccache: ${{ inputs.ccache_enabled }}
@@ -266,7 +266,7 @@ jobs:
./xrpld --definitions | python3 -m json.tool >server_definitions.json
- name: Upload server definitions
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-gcc-release-amd64' }}
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'ubuntu-gcc-debug-amd64-coverage' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: server-definitions

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Check levelization
run: python .github/scripts/levelization/generate.py
- name: Check for differences

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Check definitions
run: .github/scripts/rename/definitions.sh .
- name: Check copyright notices

View File

@@ -34,16 +34,16 @@ jobs:
needs: [determine-files]
if: ${{ needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.need_full_run == 'true' }}
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-40cdf49"
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-fecfc0c"
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
with:
enable_ccache: false
@@ -95,7 +95,7 @@ jobs:
TARGETS: ${{ needs.determine-files.outputs.need_full_run != 'true' && needs.determine-files.outputs.cpp_changed_files || 'include src tests' }}
run: |
set -o pipefail
run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -allow-no-checks ${TARGETS} 2>&1 | tee "${OUTPUT_FILE}"
run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -format -allow-no-checks ${TARGETS} 2>&1 | tee "${OUTPUT_FILE}"
- name: Print filtered clang-tidy errors
if: ${{ steps.run_clang_tidy.outcome != 'success' }}

View File

@@ -27,7 +27,7 @@ jobs:
matrix: ${{ steps.generate.outputs.matrix }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
@@ -54,7 +54,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download pre-built binary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1

View File

@@ -23,7 +23,7 @@ jobs:
matrix: ${{ steps.generate.outputs.matrix }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0

View File

@@ -40,14 +40,14 @@ defaults:
jobs:
upload:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-40cdf49
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fecfc0c
env:
REMOTE_NAME: ${{ inputs.remote_name }}
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}
CONAN_PASSWORD_XRPLF: ${{ secrets.remote_password }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Generate build version number
id: version

View File

@@ -65,10 +65,10 @@ jobs:
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
with:
enable_ccache: false

View File

@@ -348,12 +348,14 @@ run-clang-tidy -p build -allow-no-checks src tests
```
This will check all source files in the `src`, `include` and `tests` directories using the compile commands from your `build` directory.
If you wish to automatically fix whatever clang-tidy finds _and_ is capable of fixing, add `-fix` to the above command:
If you wish to automatically fix whatever clang-tidy finds _and_ is capable of fixing, add `-fix -format` to the above command:
```
run-clang-tidy -p build -quiet -fix -allow-no-checks src tests
run-clang-tidy -p build -quiet -fix -format -allow-no-checks src tests
```
`-format` reformats the fixed code with [`.clang-format`](./.clang-format); without it the fixes are inserted in LLVM style and the `clang-format` hook rewrites them afterwards.
## Contracts and instrumentation
We are using [Antithesis](https://antithesis.com/) for continuous fuzzing,

View File

@@ -144,7 +144,11 @@ def main():
+ files
)
canonicalize_fix_paths(Path(fixes_dir))
applied = subprocess.run([clang_apply_replacements, fixes_dir])
# `FormatStyle` in .clang-tidy does not reach this path,
# so ask for the repository style here.
applied = subprocess.run(
[clang_apply_replacements, "--format", "--style=file", fixes_dir]
)
return result.returncode or applied.returncode

View File

@@ -1,3 +1,5 @@
include_guard()
include(isolate_headers)
# Define a benchmark executable for the module `name`.

View File

@@ -11,6 +11,7 @@
#include <limits>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
@@ -251,4 +252,11 @@ makeSlice(std::basic_string<char, Traits, Alloc> const& s)
return Slice(s.data(), s.size());
}
template <class Traits>
Slice
makeSlice(std::basic_string_view<char, Traits> s)
{
return Slice(s.data(), s.size());
}
} // namespace xrpl

View File

@@ -58,7 +58,7 @@ struct LexicalCast<Out, std::string_view>
"beast::LexicalCast can only be used with integral types");
template <class Integral = Out>
bool
constexpr bool
operator()(Integral& out, std::string_view in) const
requires(std::is_integral_v<Integral> && !std::is_same_v<Integral, bool>)
{
@@ -110,7 +110,7 @@ struct LexicalCast<Out, boost::core::basic_string_view<char>>
{
explicit LexicalCast() = default;
bool
constexpr bool
operator()(Out& out, boost::core::basic_string_view<char> in) const
{
return LexicalCast<Out, std::string_view>()(out, in);
@@ -123,7 +123,7 @@ struct LexicalCast<Out, std::string>
{
explicit LexicalCast() = default;
bool
constexpr bool
operator()(Out& out, std::string in) const
{
return LexicalCast<Out, std::string_view>()(out, in);
@@ -136,7 +136,7 @@ struct LexicalCast<Out, char const*>
{
explicit LexicalCast() = default;
bool
constexpr bool
operator()(Out& out, char const* in) const
{
XRPL_ASSERT(in, "beast::detail::LexicalCast(char const*) : non-null input");
@@ -151,7 +151,7 @@ struct LexicalCast<Out, char*>
{
explicit LexicalCast() = default;
bool
constexpr bool
operator()(Out& out, char* in) const
{
XRPL_ASSERT(in, "beast::detail::LexicalCast(char*) : non-null input");
@@ -177,7 +177,7 @@ struct BadLexicalCast : public std::bad_cast
* @return `false` if there was a parsing or range error
*/
template <class Out, class In>
bool
constexpr bool
lexicalCastChecked(Out& out, In in)
{
return detail::LexicalCast<Out, In>()(out, in);
@@ -191,7 +191,7 @@ lexicalCastChecked(Out& out, In in)
* @return The new type.
*/
template <class Out, class In>
Out
constexpr Out
lexicalCastThrow(In in)
{
if (Out out; lexicalCastChecked(out, in))
@@ -207,7 +207,7 @@ lexicalCastThrow(In in)
* @return The new type.
*/
template <class Out, class In>
Out
constexpr Out
lexicalCast(In in, Out defaultValue = Out())
{
if (Out out; lexicalCastChecked(out, in))

View File

@@ -17,14 +17,14 @@ namespace beast {
class SemanticVersion
{
public:
using identifier_list = std::vector<std::string>;
using IdentifierList = std::vector<std::string>;
int majorVersion;
int minorVersion;
int patchVersion;
identifier_list preReleaseIdentifiers;
identifier_list metaData;
IdentifierList preReleaseIdentifiers;
IdentifierList metaData;
SemanticVersion();

View File

@@ -15,9 +15,9 @@
namespace xrpl {
// Forward declarations
namespace NodeStore {
namespace node_store {
class Database;
} // namespace NodeStore
} // namespace node_store
namespace Resource {
class Manager;
} // namespace Resource
@@ -164,7 +164,7 @@ public:
getResourceManager() = 0;
// Storage services
virtual NodeStore::Database&
virtual node_store::Database&
getNodeStore() = 0;
virtual SHAMapStore&

View File

@@ -43,14 +43,7 @@ enum ApplyFlags : std::uint32_t {
// Transaction shouldn't be applied
// Signatures shouldn't be checked
TapDryRun = 0x1000,
// Transaction is being preflighted as the payload of a
// TransactionProposalCreate. Its signatures are collected on-ledger
// afterward, so signature-presence checks (e.g. Batch signer matching)
// are skipped at proposal-creation time (On-Chain Cosigner spec
// §5.3.1.2).
TapProposal = 0x2000
TapDryRun = 0x1000
};
constexpr ApplyFlags

View File

@@ -2,7 +2,9 @@
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
@@ -25,6 +27,291 @@
namespace xrpl {
/**
* Validate that @p account may lock @p amount of a token for later delivery
* to @p dest.
*
* The lock-side counterpart of escrowUnlockPreclaimHelper: every issuer
* control (locking opt-in, authorization, freeze/lock, transferability,
* spendable balance) that gates locking token value lives here, so any
* transactor that locks funds applies the same rules. The signature is
* view-based rather than PreclaimContext-based so it can also run from
* doApply.
*/
template <ValidIssueType T>
TER
escrowLockPreclaimHelper(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j);
template <>
inline TER
escrowLockPreclaimHelper<Issue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j)
{
auto const& issue = amount.get<Issue>();
auto const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecNO_PERMISSION
if (issuer == account)
return tecNO_PERMISSION;
// If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION
auto const sleIssuer = view.read(keylet::account(issuer));
if (!sleIssuer)
return tecNO_ISSUER;
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
return tecNO_PERMISSION;
// If the account does not have a trustline to the issuer, return tecNO_LINE
auto const sleRippleState = view.read(keylet::trustLine(account, issuer, issue.currency));
if (!sleRippleState)
return tecNO_LINE;
STAmount const balance = (*sleRippleState)[sfBalance];
// If balance is positive, issuer must have higher address than account
if (balance > beast::kZero && issuer < account)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If balance is negative, issuer must have lower address than account
if (balance < beast::kZero && issuer > account)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the issuer has requireAuth set, check if the account is authorized
if (auto const ter = requireAuth(view, issue, account); !isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(view, issue, dest); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecFROZEN
if (isFrozen(view, account, issue))
return tecFROZEN;
// If the issuer has frozen the destination, return tecFROZEN
if (isFrozen(view, dest, issue))
return tecFROZEN;
STAmount const spendableAmount =
accountHolds(view, account, issue.currency, issuer, FreezeHandling::IgnoreFreeze, j);
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
// If the amount is not addable to the balance, return tecPRECISION_LOSS
if (!canAdd(spendableAmount, amount))
return tecPRECISION_LOSS;
return tesSUCCESS;
}
template <>
inline TER
escrowLockPreclaimHelper<MPTIssue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j)
{
AccountID const issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecNO_PERMISSION
if (issuer == account)
return tecNO_PERMISSION;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION
if (!sleIssuance->isFlag(lsfMPTCanEscrow))
return tecNO_PERMISSION;
// If the issuer is not the same as the issuer of the mpt, return
// tecNO_PERMISSION
if (sleIssuance->getAccountID(sfIssuer) != issuer)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
if (!view.exists(keylet::mptoken(issuanceKey.key, account)))
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is
// authorized
if (auto const ter = requireAuth(view, mptIssue, dest, AuthType::WeakAuth); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecLOCKED
if (isFrozen(view, account, mptIssue))
return tecLOCKED;
// If the issuer has frozen the destination, return tecLOCKED
if (isFrozen(view, dest, mptIssue))
return tecLOCKED;
// If the mpt cannot be transferred, return tecNO_AUTH
if (auto const ter = canTransfer(view, mptIssue, account, dest); !isTesSuccess(ter))
return ter;
STAmount const spendableAmount = accountHolds(
view,
account,
amount.get<MPTIssue>(),
FreezeHandling::IgnoreFreeze,
AuthHandling::IgnoreAuth,
j);
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
return tesSUCCESS;
}
template <ValidIssueType T>
TER
escrowLockApplyHelper(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal);
template <>
inline TER
escrowLockApplyHelper<Issue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal)
{
// Defensive: Issuer cannot create an escrow
if (issuer == sender)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ter =
directSendNoFee(view, sender, issuer, amount, !amount.holds<MPTIssue>(), journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
return tesSUCCESS;
}
template <>
inline TER
escrowLockApplyHelper<MPTIssue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal)
{
// Defensive: Issuer cannot create an escrow
if (issuer == sender)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ter = lockEscrowMPT(view, sender, amount, journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
return tesSUCCESS;
}
template <ValidIssueType T>
TER
escrowUnlockPreclaimHelper(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze = true);
template <>
inline TER
escrowUnlockPreclaimHelper<Issue>(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze)
{
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tesSUCCESS
if (issuer == account)
return tesSUCCESS;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(view, amount.get<Issue>(), account); !isTesSuccess(ter))
return ter;
// If the issuer has deep frozen the destination, return tecFROZEN
if (checkFreeze &&
isDeepFrozen(view, account, amount.get<Issue>().currency, amount.getIssuer()))
return tecFROZEN;
return tesSUCCESS;
}
template <>
inline TER
escrowUnlockPreclaimHelper<MPTIssue>(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze)
{
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tesSUCCESS
if (issuer == account)
return tesSUCCESS;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecLOCKED
if (checkFreeze && isFrozen(view, account, mptIssue))
return tecLOCKED;
return tesSUCCESS;
}
//------------------------------------------------------------------------------
template <ValidIssueType T>
TER
escrowUnlockApplyHelper(
@@ -53,9 +340,6 @@ escrowUnlockApplyHelper<Issue>(
bool createAsset,
beast::Journal journal)
{
auto const& issue = amount.get<Issue>();
Keylet const trustLineKey = keylet::trustLine(receiver, issue);
bool const recvLow = issuer > receiver;
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
@@ -65,6 +349,10 @@ escrowUnlockApplyHelper<Issue>(
if (receiverIssuer)
return tesSUCCESS;
auto const& issue = amount.get<Issue>();
Keylet const trustLineKey = keylet::trustLine(receiver, issue);
bool const recvLow = issuer > receiver;
if (!ctx.view.exists(trustLineKey) && createAsset)
{
// Can the account cover the trust line's reserve?

View File

@@ -286,6 +286,77 @@ computeFullPaymentInterest(
std::uint32_t startDate,
TenthBips32 closeInterestRate);
// Deltas applied to Vault.AssetsTotal and LoanBroker.DebtTotal at a single
// accounting touch point (origination, payment, impair/unimpair/default).
struct AccountingDeltas
{
Number assetsTotalDelta;
Number debtTotalDelta;
};
// Whole-life (pre-LendingProtocolV1_1) recognition model: interest is
// recognized into AssetsTotal/DebtTotal up front, at origination.
namespace Accrual {
// LoanSet origination: what's added to Vault.AssetsTotal and LoanBroker.DebtTotal
AccountingDeltas
loanOriginationDeltas(Number const& principalRequested, Number const& interestDue);
// LoanSet origination: would recognizing this loan's interest push
// Vault.AssetsTotal past Vault.AssetsMaximum?
bool
loanOriginationExceedsVaultMaximum(
Number const& vaultMaximum,
Number const& vaultTotal,
Number const& interestDue);
// LoanManage impair/unimpair/default: the vault's exposure to this loan
Number
loanVaultExposure(SLE::const_ref loanSle);
// LoanPay: what's added to Vault.AssetsTotal and subtracted from LoanBroker.DebtTotal for a payment
AccountingDeltas
loanPaymentDeltas(LoanPaymentParts const& parts);
} // namespace Accrual
// Cash-basis (LendingProtocolV1_1) recognition model: AssetsTotal/DebtTotal
// are principal-only, interest is recognized only as it's actually paid.
namespace CashBasis {
AccountingDeltas
loanOriginationDeltas(Number const& principalRequested);
Number
loanVaultExposure(SLE::const_ref loanSle);
AccountingDeltas
loanPaymentDeltas(LoanPaymentParts const& parts);
} // namespace CashBasis
// Public dispatchers: pick CashBasis:: if featureLendingProtocolV1_1 is
// enabled AND the Vault's LEVersion (VaultHelpers::getVaultVersion) is
// VaultVersion::CashBasis, else Accrual::. These are the only entry points
// transactors call.
AccountingDeltas
loanOriginationDeltas(
SLE::const_ref vaultSle,
Number const& principalRequested,
Number const& interestDue);
bool
loanOriginationExceedsVaultMaximum(
SLE::const_ref vaultSle,
Number const& vaultTotal,
Number const& interestDue);
Number
loanVaultExposure(SLE::const_ref vaultSle, SLE::const_ref loanSle);
AccountingDeltas
loanPaymentDeltas(SLE::const_ref vaultSle, LoanPaymentParts const& parts);
namespace detail {
// These classes and functions should only be accessed by LendingHelper
// functions and unit tests

View File

@@ -2,28 +2,85 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <cstdint>
#include <optional>
namespace xrpl {
/**
* Validate the token amount of a PaymentChannelCreate or PaymentChannelFund
* transaction during preflight.
*
* @param rules The current ledger rules used to check amendment status.
* @param amount The channel or funding amount from the transaction.
* @return tesSUCCESS if the amount is valid; temBAD_AMOUNT, temBAD_CURRENCY,
* or temDISABLED otherwise.
*/
template <ValidIssueType T>
NotTEC
payChanAmountPreflightHelper(Rules const& rules, STAmount const& amount);
template <>
inline NotTEC
payChanAmountPreflightHelper<Issue>(Rules const&, STAmount const& amount)
{
if (amount.native() || amount <= beast::kZero)
return temBAD_AMOUNT;
if (badCurrency() == amount.get<Issue>().currency)
return temBAD_CURRENCY;
return tesSUCCESS;
}
template <>
inline NotTEC
payChanAmountPreflightHelper<MPTIssue>(Rules const& rules, STAmount const& amount)
{
if (!rules.enabled(fixCleanup3_2_0) && !rules.enabled(featureMPTokensV1))
return temDISABLED;
if (amount.native() || amount.mpt() > MPTAmount{kMaxMpTokenAmount} || amount <= beast::kZero)
return temBAD_AMOUNT;
return tesSUCCESS;
}
/**
* Close a payment channel and return its remaining funds to the channel owner.
*
* @param slep The SLE for the PayChannel object to close.
* @param view The apply view in which ledger state modifications are made.
* @param key The ledger key identifying the PayChannel entry.
* @param j Journal used for fatal-level diagnostic messages.
* @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal
* fails; tefINTERNAL if the source account SLE cannot be found.
* @param slep The SLE for the PayChannel object to close.
* @param ctx The apply view context (view and transaction) in which ledger
* state modifications are made.
* @param key The ledger key identifying the PayChannel entry.
* @param txAccount The account submitting the transaction that closes the
* channel.
* @param j Journal used for fatal-level diagnostic messages.
* @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal
* fails; tefINTERNAL if the source account SLE cannot be found.
*/
TER
closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal j);
closeChannel(
SLE::ref slep,
ApplyViewContext ctx,
uint256 const& key,
AccountID const& txAccount,
beast::Journal j);
/**
* Add two uint32_t values with saturation at UINT32_MAX.

View File

@@ -1,87 +0,0 @@
#pragma once
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/TxFormats.h>
#include <cstdint>
namespace xrpl::proposal {
/**
* Owner-reserve increments held by a proposal of an ordinary transaction.
*/
constexpr std::uint32_t kProposalOwnerCount = 5;
/**
* Owner-reserve increments held by a proposal of a Batch transaction. A
* proposed Batch stores up to eight inner transactions plus multi-account
* signatures, so it reserves more than an ordinary proposed transaction.
*/
constexpr std::uint32_t kBatchProposalOwnerCount = 10;
/**
* Owner-reserve increments held by a proposal of the given transaction.
*/
inline std::uint32_t
proposalOwnerCount(STObject const& proposedTx)
{
return proposedTx.getFieldU16(sfTransactionType) == ttBATCH ? kBatchProposalOwnerCount
: kProposalOwnerCount;
}
/**
* Whether the proposed transaction is itself a proposal transaction, which
* would nest one proposal inside another.
*
* TODO: cover ttTRANSACTION_PROPOSAL_SIGN and ttTRANSACTION_PROPOSAL_CANCEL
* once those transactions exist.
*/
inline bool
isProposalTx(STObject const& proposedTx)
{
return proposedTx.getFieldU16(sfTransactionType) == ttTRANSACTION_PROPOSAL_CREATE;
}
/**
* Whether the proposed transaction is independently submittable through the
* ordinary multi-sign path: not a nested proposal, not a pseudo-transaction,
* not itself flagged as someone else's inner batch transaction, and — if it
* is a Batch — none of its own inner transactions is a nested proposal or a
* pseudo-transaction either. A Batch inner transaction cannot itself be
* pseudo (preflight0 rejects the pseudo/tfInnerBatchTxn combination
* generically), but that guard lives outside this feature, so it is checked
* again here rather than relied upon.
*/
bool
isValidProposal(STObject const& proposedTx);
/**
* Whether the proposed transaction carries any signature field.
*
* A proposal is stored in unsigned canonical form; signatures may only ever
* arrive through TransactionProposalSign. Shared by the create-time check and
* the invariant that guards the stored entry, so the two cannot drift apart.
*/
inline bool
hasSignatureField(STObject const& proposedTx)
{
return proposedTx.isFieldPresent(sfTxnSignature) || proposedTx.isFieldPresent(sfSigners) ||
proposedTx.isFieldPresent(sfBatchSigners) ||
proposedTx.isFieldPresent(sfCounterpartySignature) ||
proposedTx.isFieldPresent(sfSponsorSignature);
}
/**
* Whether the proposed transaction's SigningPubKey is present and empty, as
* unsigned canonical form requires. An absent field is not the same as an
* empty one, and a populated one means the payload was already signed.
*/
inline bool
hasEmptySigningPubKey(STObject const& proposedTx)
{
return proposedTx.isFieldPresent(sfSigningPubKey) &&
proposedTx.getFieldVL(sfSigningPubKey).empty();
}
} // namespace xrpl::proposal

View File

@@ -2,6 +2,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -107,4 +108,19 @@ sharesToAssetsWithdraw(
[[nodiscard]] bool
isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref issuance);
/**
* Resolves a Vault's LEVersion, the single point every accounting touch
* point should call to determine which recognition model (accrual vs.
* cash-basis) a Vault uses. Vaults created before featureLendingProtocolV1_1
* activated never have sfLEVersion set, which resolves here to
* VaultVersion::Legacy.
*
* @param vault The vault SLE.
*
* @return The Vault's LEVersion, or VaultVersion::Legacy if the field is
* absent.
*/
[[nodiscard]] VaultVersion
getVaultVersion(SLE::const_ref vault);
} // namespace xrpl

View File

@@ -13,7 +13,7 @@
#include <stdexcept>
#include <string>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* A backend used for the NodeStore.
@@ -163,4 +163,4 @@ public:
fdRequired() const = 0;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -25,7 +25,7 @@ namespace xrpl {
class Section;
} // namespace xrpl
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Persistency layer for NodeObject
@@ -248,7 +248,7 @@ protected:
void
storeStats(std::uint64_t count, std::uint64_t sz)
{
XRPL_ASSERT(count <= sz, "xrpl::NodeStore::Database::storeStats : valid inputs");
XRPL_ASSERT(count <= sz, "xrpl::node_store::Database::storeStats : valid inputs");
storeCount_ += count;
storeSz_ += sz;
}
@@ -308,4 +308,4 @@ private:
threadEntry();
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -9,7 +9,7 @@
#include <memory>
#include <string>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/* This class has two key-value store Backend objects for persisting SHAMap
* records. This facilitates online deletion of data. New backends are
@@ -38,7 +38,7 @@ public:
*/
virtual void
rotate(
std::unique_ptr<NodeStore::Backend>&& newBackend,
std::unique_ptr<node_store::Backend>&& newBackend,
std::function<void(std::string const& writableName, std::string const& archiveName)> const&
f) = 0;
@@ -56,4 +56,4 @@ public:
setRotationInFlight(bool inFlight) = 0;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -3,7 +3,7 @@
#include <xrpl/nodestore/Scheduler.h>
#include <xrpl/nodestore/Task.h>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Simple NodeStore Scheduler that just performs the tasks synchronously.
@@ -21,4 +21,4 @@ public:
onBatchWrite(BatchWriteReport const& report) override;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -14,7 +14,7 @@ namespace xrpl {
class Section;
} // namespace xrpl
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Base class for backend factories.
@@ -70,4 +70,4 @@ public:
}
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -10,7 +10,7 @@
#include <memory>
#include <string>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Singleton for managing NodeStore factories and back ends.
@@ -98,4 +98,4 @@ public:
beast::Journal journal) = 0;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -8,7 +8,7 @@
#include <cstdint>
#include <memory>
// VFALCO NOTE Intentionally not in the NodeStore namespace
// VFALCO NOTE Intentionally not in the node_store namespace
namespace xrpl {

View File

@@ -4,7 +4,7 @@
#include <chrono>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
enum class FetchType { Synchronous, Async };
@@ -71,4 +71,4 @@ public:
onBatchWrite(BatchWriteReport const& report) = 0;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -1,6 +1,6 @@
#pragma once
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Derived classes perform scheduled tasks.
@@ -17,4 +17,4 @@ struct Task
performScheduledTask() = 0;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -5,7 +5,7 @@
#include <memory>
#include <vector>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
// This is only used to pre-allocate the array for
// batch objects and does not affect the amount written.
@@ -36,4 +36,4 @@ enum class Status {
*/
using Batch = std::vector<std::shared_ptr<NodeObject>>;
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -9,7 +9,7 @@
#include <memory>
#include <mutex>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Batch-writing assist logic.
@@ -86,4 +86,4 @@ private:
Batch writeSet_;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -22,7 +22,7 @@
#include <stdexcept>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
class DatabaseNodeImp : public Database
{
@@ -68,7 +68,7 @@ public:
XRPL_ASSERT(
backend_,
"xrpl::NodeStore::DatabaseNodeImp::DatabaseNodeImp : non-null "
"xrpl::node_store::DatabaseNodeImp::DatabaseNodeImp : non-null "
"backend");
}
@@ -138,4 +138,4 @@ private:
}
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -16,7 +16,7 @@
#include <mutex>
#include <string>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
class DatabaseRotatingImp : public DatabaseRotating
{
@@ -41,7 +41,7 @@ public:
void
rotate(
std::unique_ptr<NodeStore::Backend>&& newBackend,
std::unique_ptr<node_store::Backend>&& newBackend,
std::function<void(std::string const& writableName, std::string const& archiveName)> const&
f) override;
@@ -94,4 +94,4 @@ private:
forEach(std::function<void(std::shared_ptr<NodeObject>)> f) override;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -4,7 +4,7 @@
#include <memory>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Parsed key/value blob into NodeObject components.
@@ -49,4 +49,4 @@ private:
int dataBytes_;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -12,7 +12,7 @@
#include <memory>
#include <stdexcept>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Convert a NodeObject from in-memory to database format.
@@ -68,7 +68,7 @@ class EncodedBlob
public:
explicit EncodedBlob(std::shared_ptr<NodeObject> const& obj)
: size_([&obj]() {
XRPL_ASSERT(obj, "xrpl::NodeStore::EncodedBlob::EncodedBlob : non-null input");
XRPL_ASSERT(obj, "xrpl::node_store::EncodedBlob::EncodedBlob : non-null input");
if (!obj)
throw std::runtime_error("EncodedBlob: unseated std::shared_ptr used.");
@@ -88,7 +88,7 @@ public:
XRPL_ASSERT(
((ptr_ == payload_.data()) && (size_ <= payload_.size())) ||
((ptr_ != payload_.data()) && (size_ > payload_.size())),
"xrpl::NodeStore::EncodedBlob::~EncodedBlob : valid payload "
"xrpl::node_store::EncodedBlob::~EncodedBlob : valid payload "
"pointer");
if (ptr_ != payload_.data())
@@ -114,4 +114,4 @@ public:
}
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -13,7 +13,7 @@
#include <string>
#include <vector>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
class ManagerImp : public Manager
{
@@ -57,4 +57,4 @@ public:
beast::Journal journal) override;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -6,25 +6,25 @@
#include <cstdint>
#include <type_traits>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
// This is a variant of the base128 varint format from
// google protocol buffers:
// https://developers.google.com/protocol-buffers/docs/encoding#varints
// field tag
struct varint;
struct Varint;
// Metafuncton to return largest
// Metafunction to return largest
// possible size of T represented as varint.
// T must be unsigned
template <class T, bool = std::is_unsigned_v<T>>
struct varint_traits;
struct VarintTraits;
template <class T>
struct varint_traits<T, true>
struct VarintTraits<T, true>
{
explicit varint_traits() = default;
explicit VarintTraits() = default;
static constexpr std::size_t kMax = ((8 * sizeof(T)) + 6) / 7;
};
@@ -104,7 +104,7 @@ writeVarint(void* p0, std::size_t v)
template <class T>
void
read(nudb::detail::istream& is, std::size_t& u)
requires(std::is_same_v<T, varint>)
requires(std::is_same_v<T, Varint>)
{
auto p0 = is(1);
auto p1 = p0;
@@ -118,9 +118,9 @@ read(nudb::detail::istream& is, std::size_t& u)
template <class T>
void
write(nudb::detail::ostream& os, std::size_t t)
requires(std::is_same_v<T, varint>)
requires(std::is_same_v<T, Varint>)
{
writeVarint(os.data(sizeVarint(t)), t);
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -10,7 +10,7 @@
#include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/nodestore/NodeObject.h>
#include <xrpl/nodestore/detail/varint.h>
#include <xrpl/nodestore/detail/Varint.h>
#include <xrpl/protocol/HashPrefix.h>
#include <nudb/detail/field.hpp>
@@ -21,7 +21,7 @@
#include <cstring>
#include <string>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
template <class BufferFactory>
std::pair<void const*, std::size_t>
@@ -59,7 +59,7 @@ lz4Compress(void const* in, std::size_t inSize, BufferFactory&& bf)
using std::runtime_error;
using namespace nudb::detail;
std::pair<void const*, std::size_t> result;
std::array<std::uint8_t, varint_traits<std::size_t>::kMax> vi{};
std::array<std::uint8_t, VarintTraits<std::size_t>::kMax> vi{};
auto const n = writeVarint(vi.data(), inSize);
auto const outMax = LZ4_compressBound(inSize);
auto* out = reinterpret_cast<std::uint8_t*>(bf(n + outMax));
@@ -240,7 +240,7 @@ nodeobjectCompress(void const* in, std::size_t inSize, BufferFactory&& bf)
auto* out = reinterpret_cast<std::uint8_t*>(bf(result.second));
result.first = out;
ostream os(out, result.second);
write<varint>(os, type);
write<Varint>(os, type);
write<std::uint16_t>(os, mask);
write(os, vh.data(), n * 32);
return result;
@@ -252,13 +252,13 @@ nodeobjectCompress(void const* in, std::size_t inSize, BufferFactory&& bf)
auto* out = reinterpret_cast<std::uint8_t*>(bf(result.second));
result.first = out;
ostream os(out, result.second);
write<varint>(os, type);
write<Varint>(os, type);
write(os, vh.data(), n * 32);
return result;
}
}
std::array<std::uint8_t, varint_traits<std::size_t>::kMax> vi{};
std::array<std::uint8_t, VarintTraits<std::size_t>::kMax> vi{};
static constexpr std::size_t kCodecType = 1;
auto const vn = writeVarint(vi.data(), kCodecType);
@@ -269,7 +269,7 @@ nodeobjectCompress(void const* in, std::size_t inSize, BufferFactory&& bf)
case 1: // lz4
{
std::uint8_t* p = nullptr;
auto const lzr = NodeStore::lz4Compress(in, inSize, [&p, &vn, &bf](std::size_t n) {
auto const lzr = node_store::lz4Compress(in, inSize, [&p, &vn, &bf](std::size_t n) {
p = reinterpret_cast<std::uint8_t*>(bf(vn + n));
return p + vn;
});
@@ -316,4 +316,4 @@ filterInner(void* in, std::size_t inSize)
}
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -246,7 +246,15 @@ message TMGetObjectByHash {
message TMLedgerNode {
required bytes nodedata = 1;
optional bytes nodeid = 2; // missing for ledger base data
// Used when protocol version <2.3. Not set for ledger base data.
optional bytes nodeid = 2;
// Used when protocol version >=2.3. Neither value is set for ledger base data.
oneof reference {
bytes id = 3; // Set for inner nodes.
uint32 depth = 4; // Set for leaf nodes.
}
}
enum TMLedgerInfoType {

View File

@@ -187,20 +187,6 @@ check(uint256 const& key) noexcept
}
/** @} */
/**
* A TransactionProposal
*/
/** @{ */
Keylet
txProposal(AccountID const& target, std::uint32_t ticketSequence) noexcept;
inline Keylet
txProposal(uint256 const& key) noexcept
{
return {ltTRANSACTION_PROPOSAL, key};
}
/** @} */
/**
* A DepositPreauth
*/

View File

@@ -1,10 +1,20 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
#include <cstdint>
namespace xrpl {
inline void
@@ -15,4 +25,70 @@ serializePayChanAuthorization(Serializer& msg, uint256 const& key, XRPAmount con
msg.add64(amt.drops());
}
inline void
serializePayChanAuthorization(
Serializer& msg,
uint256 const& key,
IOUAmount const& amt,
Currency const& cur,
AccountID const& iss)
{
msg.add32(HashPrefix::PaymentChannelClaim);
msg.addBitString(key);
if (amt == beast::kZero)
{
msg.add64(STAmount::kIssuedCurrency);
}
else if (amt.signum() == -1)
{ // 512 = not native; the sign is encoded by omitting the 256 flag, so
// the mantissa must be serialized as its absolute value
msg.add64(
static_cast<std::uint64_t>(-amt.mantissa()) |
(static_cast<std::uint64_t>(amt.exponent() + 512 + 97) << (64 - 10)));
}
else
{ // 256 = positive
msg.add64(
amt.mantissa() |
(static_cast<std::uint64_t>(amt.exponent() + 512 + 256 + 97) << (64 - 10)));
}
msg.addBitString(cur);
msg.addBitString(iss);
}
inline void
serializePayChanAuthorization(
Serializer& msg,
uint256 const& key,
MPTAmount const& amt,
MPTID const& mptID,
AccountID const& iss)
{
msg.add32(HashPrefix::PaymentChannelClaim);
msg.addBitString(key);
msg.add64(amt.value());
msg.addBitString(mptID);
msg.addBitString(iss);
}
inline void
serializePayChanAuthorization(Serializer& msg, uint256 const& key, STAmount const& amt)
{
if (amt.native())
{
serializePayChanAuthorization(msg, key, amt.xrp());
}
else if (amt.holds<Issue>())
{
serializePayChanAuthorization(
msg, key, amt.iou(), amt.get<Issue>().currency, amt.get<Issue>().account);
}
else if (amt.holds<MPTIssue>())
{
auto const& mpt = amt.get<MPTIssue>();
auto const& mptID = mpt.getMptID();
serializePayChanAuthorization(msg, key, amt.mpt(), mptID, amt.getIssuer());
}
}
} // namespace xrpl

View File

@@ -316,6 +316,17 @@ constexpr std::uint8_t kVaultDefaultIouScale = 6;
*/
constexpr std::uint8_t kVaultMaximumIouScale = 18;
/**
* Vault ledger-entry schema versions. Assigned to newly created
* Vaults once featureLendingProtocolV1_1 is enabled. Vaults created before
* activation are left without LEVersion (implicit legacy version 0,
* accrual-basis accounting).
*/
enum class VaultVersion : uint8_t {
Legacy = 0,
CashBasis,
};
/**
* Maximum recursion depth for vault shares being put as an asset inside
* another vault; counted from 0

View File

@@ -15,8 +15,8 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FEATURE(Cosign, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_4_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(TokenPaychan, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocolV1_1, Supported::No, VoteBehavior::DefaultNo)

View File

@@ -369,6 +369,8 @@ LEDGER_ENTRY(ltPAYCHAN, 0x0078, PayChannel, payment_channel, ({
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfDestinationNode, SoeOptional},
{sfTransferRate, SoeOptional},
{sfIssuerNode, SoeOptional},
}))
/** The ledger object which tracks the AMM.
@@ -505,6 +507,7 @@ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
{sfShareMPTID, SoeRequired},
{sfWithdrawalPolicy, SoeRequired},
{sfScale, SoeDefault},
{sfLEVersion, SoeDefault},
// no SharesTotal ever (use MPTIssuance.sfOutstandingAmount)
// no PermissionedDomainID ever (use MPTIssuance.sfDomainID)
}))
@@ -636,24 +639,5 @@ LEDGER_ENTRY(ltSPONSORSHIP, 0x0090, Sponsorship, sponsorship, ({
{sfSponseeNode, SoeRequired},
}))
/** A ledger object holding a pending transaction proposal.
The proposed transaction is stored unsigned in ProposedTransaction, and
signatures accumulate on it over time. It becomes fully signed either from
a single signature by its own Account (or that account's Delegate), or once
the weight collected in its Signers field meets that account's quorum. At
that point the stored transaction is one that anyone may copy and submit.
\sa keylet::txProposal
*/
LEDGER_ENTRY(ltTRANSACTION_PROPOSAL, 0x0091, TransactionProposal, transaction_proposal, ({
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfOwner, SoeRequired},
{sfProposedTransaction, SoeRequired},
{sfExpiration, SoeRequired},
{sfOwnerNode, SoeRequired},
}))
#undef EXPAND
#undef LEDGER_ENTRY_DUPLICATE

View File

@@ -18,6 +18,7 @@ TYPED_SFIELD(sfMethod, UINT8, 2)
TYPED_SFIELD(sfTransactionResult, UINT8, 3)
TYPED_SFIELD(sfScale, UINT8, 4)
TYPED_SFIELD(sfAssetScale, UINT8, 5)
TYPED_SFIELD(sfLEVersion, UINT8, 6)
// 8-bit integers (uncommon)
TYPED_SFIELD(sfTickSize, UINT8, 16)
@@ -216,7 +217,6 @@ TYPED_SFIELD(sfLoanID, UINT256, 38)
TYPED_SFIELD(sfReferenceHolding, UINT256, 39)
TYPED_SFIELD(sfBlindingFactor, UINT256, 40)
TYPED_SFIELD(sfObjectID, UINT256, 41)
TYPED_SFIELD(sfProposalID, UINT256, 42)
// number (common)
TYPED_SFIELD(sfNumber, NUMBER, 1)
@@ -358,7 +358,6 @@ TYPED_SFIELD(sfHighSponsor, ACCOUNT, 28)
TYPED_SFIELD(sfLowSponsor, ACCOUNT, 29)
TYPED_SFIELD(sfCounterpartySponsor, ACCOUNT, 30)
TYPED_SFIELD(sfSponsee, ACCOUNT, 31)
TYPED_SFIELD(sfSigningFor, ACCOUNT, 32)
// vector of 256-bit
TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::kSmdNever)
@@ -424,7 +423,6 @@ UNTYPED_SFIELD(sfBatchSigner, OBJECT, 35)
UNTYPED_SFIELD(sfBook, OBJECT, 36)
UNTYPED_SFIELD(sfCounterpartySignature, OBJECT, 37, SField::kSmdDefault, SField::kNotSigning)
UNTYPED_SFIELD(sfSponsorSignature, OBJECT, 38, SField::kSmdDefault, SField::kNotSigning)
UNTYPED_SFIELD(sfProposedTransaction, OBJECT, 39)
// array of objects (common)
// ARRAY/1 is reserved for end of array

View File

@@ -191,7 +191,7 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate,
NoPriv,
({
{sfDestination, SoeRequired},
{sfAmount, SoeRequired},
{sfAmount, SoeRequired, SoeMptSupported},
{sfSettleDelay, SoeRequired},
{sfPublicKey, SoeRequired},
{sfCancelAfter, SoeOptional},
@@ -208,7 +208,7 @@ TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund,
NoPriv,
({
{sfChannel, SoeRequired},
{sfAmount, SoeRequired},
{sfAmount, SoeRequired, SoeMptSupported},
{sfExpiration, SoeOptional},
}))
@@ -222,8 +222,8 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim,
NoPriv,
({
{sfChannel, SoeRequired},
{sfAmount, SoeOptional},
{sfBalance, SoeOptional},
{sfAmount, SoeOptional, SoeMptSupported},
{sfBalance, SoeOptional, SoeMptSupported},
{sfSignature, SoeOptional},
{sfPublicKey, SoeOptional},
{sfCredentialIDs, SoeOptional},
@@ -1194,20 +1194,6 @@ TRANSACTION(ttSPONSORSHIP_SET, 91, SponsorshipSet,
{sfRemainingOwnerCount, SoeOptional},
}))
/** This transaction posts an unsigned transaction on-ledger as a
TransactionProposal, pending multi-signature collection. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/proposal/TransactionProposalCreate.h>
#endif
TRANSACTION(ttTRANSACTION_PROPOSAL_CREATE, 92, TransactionProposalCreate,
Delegation::NotDelegable,
featureCosign,
NoPriv,
({
{sfProposedTransaction, SoeRequired},
{sfExpiration, SoeRequired},
}))
/** This system-generated transaction type is used to update the status of the various amendments.
For details, see: https://xrpl.org/amendments.html

View File

@@ -287,6 +287,54 @@ public:
{
return this->sle_->isFieldPresent(sfDestinationNode);
}
/**
* @brief Get sfTransferRate (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getTransferRate() const
{
if (hasTransferRate())
return this->sle_->at(sfTransferRate);
return std::nullopt;
}
/**
* @brief Check if sfTransferRate is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasTransferRate() const
{
return this->sle_->isFieldPresent(sfTransferRate);
}
/**
* @brief Get sfIssuerNode (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT64::type::value_type>
getIssuerNode() const
{
if (hasIssuerNode())
return this->sle_->at(sfIssuerNode);
return std::nullopt;
}
/**
* @brief Check if sfIssuerNode is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasIssuerNode() const
{
return this->sle_->isFieldPresent(sfIssuerNode);
}
};
/**
@@ -508,6 +556,28 @@ public:
return *this;
}
/**
* @brief Set sfTransferRate (SoeOptional)
* @return Reference to this builder for method chaining.
*/
PayChannelBuilder&
setTransferRate(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfTransferRate] = value;
return *this;
}
/**
* @brief Set sfIssuerNode (SoeOptional)
* @return Reference to this builder for method chaining.
*/
PayChannelBuilder&
setIssuerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfIssuerNode] = value;
return *this;
}
/**
* @brief Build and return the completed PayChannel wrapper.
* @param index The ledger entry index.

View File

@@ -1,242 +0,0 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::ledger_entries {
class TransactionProposalBuilder;
/**
* @brief Ledger Entry: TransactionProposal
*
* Type: ltTRANSACTION_PROPOSAL (0x0091)
* RPC Name: transaction_proposal
*
* Immutable wrapper around SLE providing type-safe field access.
* Use TransactionProposalBuilder to construct new ledger entries.
*/
class TransactionProposal : public LedgerEntryBase
{
public:
static constexpr LedgerEntryType entryType = ltTRANSACTION_PROPOSAL;
/**
* @brief Construct a TransactionProposal ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit TransactionProposal(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
if (sle_->getType() != entryType)
{
throw std::runtime_error("Invalid ledger entry type for TransactionProposal");
}
}
// Ledger entry-specific field getters
/**
* @brief Get sfPreviousTxnID (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getPreviousTxnID() const
{
return this->sle_->at(sfPreviousTxnID);
}
/**
* @brief Get sfPreviousTxnLgrSeq (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getPreviousTxnLgrSeq() const
{
return this->sle_->at(sfPreviousTxnLgrSeq);
}
/**
* @brief Get sfOwner (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getOwner() const
{
return this->sle_->at(sfOwner);
}
/**
* @brief Get sfProposedTransaction (SoeRequired)
* @note This is an untyped field (unknown).
* @return The field value.
*/
[[nodiscard]]
STObject
getProposedTransaction() const
{
return this->sle_->getFieldObject(sfProposedTransaction);
}
/**
* @brief Get sfExpiration (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getExpiration() const
{
return this->sle_->at(sfExpiration);
}
/**
* @brief Get sfOwnerNode (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getOwnerNode() const
{
return this->sle_->at(sfOwnerNode);
}
};
/**
* @brief Builder for TransactionProposal ledger entries.
*
* Provides a fluent interface for constructing ledger entries with method chaining.
* Uses STObject internally for flexible ledger entry construction.
* Inherits common field setters from LedgerEntryBuilderBase.
*/
class TransactionProposalBuilder : public LedgerEntryBuilderBase<TransactionProposalBuilder>
{
public:
/**
* @brief Construct a new TransactionProposalBuilder with required fields.
* @param previousTxnID The sfPreviousTxnID field value.
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
* @param owner The sfOwner field value.
* @param proposedTransaction The sfProposedTransaction field value.
* @param expiration The sfExpiration field value.
* @param ownerNode The sfOwnerNode field value.
*/
TransactionProposalBuilder(std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq,std::decay_t<typename SF_ACCOUNT::type::value_type> const& owner,STObject const& proposedTransaction,std::decay_t<typename SF_UINT32::type::value_type> const& expiration,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode)
: LedgerEntryBuilderBase<TransactionProposalBuilder>(ltTRANSACTION_PROPOSAL)
{
setPreviousTxnID(previousTxnID);
setPreviousTxnLgrSeq(previousTxnLgrSeq);
setOwner(owner);
setProposedTransaction(proposedTransaction);
setExpiration(expiration);
setOwnerNode(ownerNode);
}
/**
* @brief Construct a TransactionProposalBuilder from an existing SLE object.
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
TransactionProposalBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltTRANSACTION_PROPOSAL)
{
throw std::runtime_error("Invalid ledger entry type for TransactionProposal");
}
object_ = *sle;
}
/**
* @brief Ledger entry-specific field setters
*/
/**
* @brief Set sfPreviousTxnID (SoeRequired)
* @return Reference to this builder for method chaining.
*/
TransactionProposalBuilder&
setPreviousTxnID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfPreviousTxnID] = value;
return *this;
}
/**
* @brief Set sfPreviousTxnLgrSeq (SoeRequired)
* @return Reference to this builder for method chaining.
*/
TransactionProposalBuilder&
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfPreviousTxnLgrSeq] = value;
return *this;
}
/**
* @brief Set sfOwner (SoeRequired)
* @return Reference to this builder for method chaining.
*/
TransactionProposalBuilder&
setOwner(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfOwner] = value;
return *this;
}
/**
* @brief Set sfProposedTransaction (SoeRequired)
* @return Reference to this builder for method chaining.
*/
TransactionProposalBuilder&
setProposedTransaction(STObject const& value)
{
object_.setFieldObject(sfProposedTransaction, value);
return *this;
}
/**
* @brief Set sfExpiration (SoeRequired)
* @return Reference to this builder for method chaining.
*/
TransactionProposalBuilder&
setExpiration(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfExpiration] = value;
return *this;
}
/**
* @brief Set sfOwnerNode (SoeRequired)
* @return Reference to this builder for method chaining.
*/
TransactionProposalBuilder&
setOwnerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfOwnerNode] = value;
return *this;
}
/**
* @brief Build and return the completed TransactionProposal wrapper.
* @param index The ledger entry index.
* @return The constructed ledger entry wrapper.
*/
TransactionProposal
build(uint256 const& index)
{
return TransactionProposal{std::make_shared<SLE>(std::move(object_), index)};
}
};
} // namespace xrpl::ledger_entries

View File

@@ -287,6 +287,30 @@ public:
{
return this->sle_->isFieldPresent(sfScale);
}
/**
* @brief Get sfLEVersion (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT8::type::value_type>
getLEVersion() const
{
if (hasLEVersion())
return this->sle_->at(sfLEVersion);
return std::nullopt;
}
/**
* @brief Check if sfLEVersion is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasLEVersion() const
{
return this->sle_->isFieldPresent(sfLEVersion);
}
};
/**
@@ -508,6 +532,17 @@ public:
return *this;
}
/**
* @brief Set sfLEVersion (SoeDefault)
* @return Reference to this builder for method chaining.
*/
VaultBuilder&
setLEVersion(std::decay_t<typename SF_UINT8::type::value_type> const& value)
{
object_[sfLEVersion] = value;
return *this;
}
/**
* @brief Build and return the completed Vault wrapper.
* @param index The ledger entry index.

View File

@@ -60,6 +60,7 @@ public:
/**
* @brief Get sfAmount (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -86,6 +87,7 @@ public:
/**
* @brief Get sfBalance (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -246,6 +248,7 @@ public:
/**
* @brief Set sfAmount (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelClaimBuilder&
@@ -257,6 +260,7 @@ public:
/**
* @brief Set sfBalance (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelClaimBuilder&

View File

@@ -60,6 +60,7 @@ public:
/**
* @brief Get sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -207,6 +208,7 @@ public:
/**
* @brief Set sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelCreateBuilder&

View File

@@ -60,6 +60,7 @@ public:
/**
* @brief Get sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -155,6 +156,7 @@ public:
/**
* @brief Set sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelFundBuilder&

View File

@@ -1,155 +0,0 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class TransactionProposalCreateBuilder;
/**
* @brief Transaction: TransactionProposalCreate
*
* Type: ttTRANSACTION_PROPOSAL_CREATE (92)
* Delegable: Delegation::NotDelegable
* Amendment: featureCosign
* Privileges: NoPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use TransactionProposalCreateBuilder to construct new transactions.
*/
class TransactionProposalCreate : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttTRANSACTION_PROPOSAL_CREATE;
/**
* @brief Construct a TransactionProposalCreate transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit TransactionProposalCreate(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for TransactionProposalCreate");
}
}
// Transaction-specific field getters
/**
* @brief Get sfProposedTransaction (SoeRequired)
* @note This is an untyped field.
* @return The field value.
*/
[[nodiscard]]
STObject
getProposedTransaction() const
{
return this->tx_->getFieldObject(sfProposedTransaction);
}
/**
* @brief Get sfExpiration (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getExpiration() const
{
return this->tx_->at(sfExpiration);
}
};
/**
* @brief Builder for TransactionProposalCreate transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses STObject internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class TransactionProposalCreateBuilder : public TransactionBuilderBase<TransactionProposalCreateBuilder>
{
public:
/**
* @brief Construct a new TransactionProposalCreateBuilder with required fields.
* @param account The account initiating the transaction.
* @param proposedTransaction The sfProposedTransaction field value.
* @param expiration The sfExpiration field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
TransactionProposalCreateBuilder(SF_ACCOUNT::type::value_type account,
STObject const& proposedTransaction, std::decay_t<typename SF_UINT32::type::value_type> const& expiration, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<TransactionProposalCreateBuilder>(ttTRANSACTION_PROPOSAL_CREATE, account, sequence, fee)
{
setProposedTransaction(proposedTransaction);
setExpiration(expiration);
}
/**
* @brief Construct a TransactionProposalCreateBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
TransactionProposalCreateBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttTRANSACTION_PROPOSAL_CREATE)
{
throw std::runtime_error("Invalid transaction type for TransactionProposalCreateBuilder");
}
object_ = *tx;
}
/**
* @brief Transaction-specific field setters
*/
/**
* @brief Set sfProposedTransaction (SoeRequired)
* @return Reference to this builder for method chaining.
*/
TransactionProposalCreateBuilder&
setProposedTransaction(STObject const& value)
{
object_.setFieldObject(sfProposedTransaction, value);
return *this;
}
/**
* @brief Set sfExpiration (SoeRequired)
* @return Reference to this builder for method chaining.
*/
TransactionProposalCreateBuilder&
setExpiration(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfExpiration] = value;
return *this;
}
/**
* @brief Build and return the TransactionProposalCreate wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
TransactionProposalCreate
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return TransactionProposalCreate{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -26,10 +26,10 @@ public:
explicit Family() = default;
virtual ~Family() = default;
virtual NodeStore::Database&
virtual node_store::Database&
db() = 0;
[[nodiscard]] virtual NodeStore::Database const&
[[nodiscard]] virtual node_store::Database const&
db() const = 0;
virtual beast::Journal const&

View File

@@ -3,7 +3,6 @@
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/IntrusivePointer.h>
#include <xrpl/basics/SHAMapHash.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
@@ -95,6 +94,21 @@ enum class SHAMapState {
*
* See https://en.wikipedia.org/wiki/Merkle_tree
*/
/**
* Holds a SHAMap node's identity, leaf status, and serialized data. Used by
* getNodeFat to return node data for peer synchronization.
*/
struct SHAMapNodeData
{
SHAMapNodeID nodeID;
// The `data` field (a Blob, 8-byte aligned) needs 4 bytes of padding after the `nodeID` field
// (36 bytes, 4-byte aligned) regardless of what comes between them, so `isLeaf` costs nothing
// extra here. Moving it after `data` would add 8 bytes to the size of this struct instead.
bool isLeaf;
Blob data;
};
class SHAMap
{
private:
@@ -289,10 +303,10 @@ public:
std::vector<std::pair<SHAMapNodeID, uint256>>
getMissingNodes(int maxNodes, SHAMapSyncFilter const* filter);
bool
[[nodiscard]] bool
getNodeFat(
SHAMapNodeID const& wanted,
std::vector<std::pair<SHAMapNodeID, Blob>>& data,
std::vector<SHAMapNodeData>& data,
bool fatLeaves,
std::uint32_t depth) const;
@@ -321,10 +335,45 @@ public:
void
serializeRoot(Serializer& s) const;
/**
* Add a root node to the SHAMap during synchronization.
*
* This function is used when receiving the root node of a SHAMap from a peer during ledger
* synchronization. The node must already have been deserialized.
*
* @param hash The expected hash of the root node.
* @param rootNode A deserialized root node to add.
* @param filter Optional sync filter to track received nodes.
* @return Status indicating whether the node was useful, duplicate, or invalid.
*
* @note This function expects the rootNode to be a valid, deserialized SHAMapTreeNode. The
* caller is responsible for deserialization and basic validation before calling this
* function.
*/
SHAMapAddNode
addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFilter const* filter);
addRootNode(SHAMapHash const& hash, SHAMapTreeNodePtr rootNode, SHAMapSyncFilter const* filter);
/**
* Add a known node at a specific position in the SHAMap during synchronization.
*
* This function is used when receiving nodes from peers during ledger synchronization. The node
* is inserted at the position specified by nodeID. The node must already have been
* deserialized.
*
* @param nodeID The position in the tree where this node belongs.
* @param treeNode A deserialized tree node to add.
* @param filter Optional sync filter to track received nodes.
* @return Status indicating whether the node was useful, duplicate, or invalid.
*
* @note This function expects the treeNode to be a valid, deserialized SHAMapTreeNode. The
* caller is responsible for deserialization and basic validation before calling this
* function. This also means that the nodeID must be consistent with the node's content.
*/
SHAMapAddNode
addKnownNode(SHAMapNodeID const& nodeID, Slice const& rawNode, SHAMapSyncFilter const* filter);
addKnownNode(
SHAMapNodeID const& nodeID,
SHAMapTreeNodePtr treeNode,
SHAMapSyncFilter const* filter);
// status functions
void

View File

@@ -1,6 +1,9 @@
#pragma once
#include <xrpl/basics/SHAMapHash.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/shamap/SHAMapItem.h>
#include <xrpl/shamap/SHAMapNodeID.h>
#include <xrpl/shamap/SHAMapTreeNode.h>
@@ -60,4 +63,16 @@ public:
getString(SHAMapNodeID const&) const final;
};
/**
* Return the key of the item held by a SHAMap leaf node.
*
* @param node a node known to be a leaf (see SHAMapTreeNode::isLeaf).
*/
inline uint256 const&
leafKey(SHAMapTreeNode const& node)
{
XRPL_ASSERT(node.isLeaf(), "xrpl::leafKey : node is a leaf");
return safeDowncast<SHAMapLeafNode const&>(node).peekItem()->key();
}
} // namespace xrpl

View File

@@ -59,10 +59,6 @@ public:
, j(j)
{
XRPL_ASSERT((flags & TapBatch) == TapBatch, "Batch apply flag should be set");
XRPL_ASSERT_IF(
(flags & TapProposal) != TapNone,
(flags & TapDryRun) != TapNone,
"xrpl::PreflightContext : proposal preflight implies dry run");
}
PreflightContext(
@@ -74,10 +70,6 @@ public:
: registry(registry), tx(tx), rules(std::move(rules)), flags(flags), j(j)
{
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set");
XRPL_ASSERT_IF(
(flags & TapProposal) != TapNone,
(flags & TapDryRun) != TapNone,
"xrpl::PreflightContext : proposal preflight implies dry run");
}
PreflightContext&

View File

@@ -23,6 +23,9 @@ public:
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -23,9 +23,15 @@ public:
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;

View File

@@ -1,44 +0,0 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class TransactionProposalCreate : public Transactor
{
public:
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
explicit TransactionProposalCreate(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
void
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -3,11 +3,11 @@
These files capture the output of [`bin/check-tools.sh`](../../bin/check-tools.sh)
— the versions of the development tooling — in each Nix environment:
| File | Environment |
| --------------------- | ----------------------------------- |
| `nix-nixos-amd64.txt` | `nix-nixos` CI image, `linux/amd64` |
| `nix-nixos-arm64.txt` | `nix-nixos` CI image, `linux/arm64` |
| `macos.txt` | macOS, inside `nix develop` |
| File | Environment |
| ---------------------- | ------------------------------------ |
| `nix-ubuntu-amd64.txt` | `nix-ubuntu` CI image, `linux/amd64` |
| `nix-ubuntu-arm64.txt` | `nix-ubuntu` CI image, `linux/arm64` |
| `macos.txt` | macOS, inside `nix develop` |
The [`check-tools`](../../.github/workflows/check-tools.yml) workflow regenerates
each snapshot in its environment and fails if it differs from the committed file.
@@ -23,15 +23,15 @@ with `sed -n '/^Detected OS:/,$p'`.
## Regenerating
The two Linux snapshots come from the `nix-nixos` image (Docker or a compatible
The two Linux snapshots come from the `nix-ubuntu` image (Docker or a compatible
runtime such as Apple `container`). The image tag is pinned in `linux.json`:
```bash
img="ghcr.io/xrplf/xrpld/nix-nixos:$(jq -r .image_tag .github/scripts/strategy-matrix/linux.json)"
img="ghcr.io/xrplf/xrpld/nix-ubuntu:$(jq -r .image_tag .github/scripts/strategy-matrix/linux.json)"
for arch in amd64 arm64; do
container run --rm -i -e CHECK_TOOLS_SKIP_CLONE=1 -a "${arch}" --entrypoint bash "${img}" -s \
<bin/check-tools.sh >"nix/check-tools/nix-nixos-${arch}.txt"
<bin/check-tools.sh >"nix/check-tools/nix-ubuntu-${arch}.txt"
done
```

View File

@@ -192,6 +192,7 @@ unsigned-integer-overflow:rpc/handlers/orderbook/GetAggregatePrice.cpp
# Test-only intentional overflow/underflow in fixture and unit-test arithmetic.
unsigned-integer-overflow:tests/libxrpl/basics/RangeSet.cpp
unsigned-integer-overflow:test/app/Batch_test.cpp
unsigned-integer-overflow:test/app/ConfidentialTransfer_test.cpp
unsigned-integer-overflow:test/app/Invariants_test.cpp
unsigned-integer-overflow:test/app/Loan_test.cpp
unsigned-integer-overflow:test/app/NFToken_test.cpp

View File

@@ -17,24 +17,25 @@
#include <utility>
#include <vector>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
namespace {
constexpr std::size_t kPoolSizes[] = {1000, 10000, 100000};
constexpr int kThreadCounts[] = {1, 4, 8};
constexpr auto kPoolSizes = std::to_array<std::size_t>({1000, 10000, 100000});
constexpr auto kThreadCounts = std::to_array<std::size_t>({1, 4, 8});
constexpr std::size_t kBatchSize = 256;
constexpr std::size_t kMissRatio = 5;
constexpr std::string_view kNamePrefix = "BM_Backend_";
constexpr std::string_view kNameSeparator = "/";
struct RunState
{
std::unique_ptr<BackendHarness> harness;
Batch present; // prefix-1 objects, eligible to be stored
Batch recent; // prefix-1 objects in the "future" key space
std::vector<uint256> missing; // prefix-2 keys that are never stored
std::vector<std::size_t> shuffle; // [0, poolSize) permutation for random-like access
std::size_t avgPayload = 0; // mean getData().size() over `present`
std::unique_ptr<BackendHarness> harness; ///< backend under test, rebuilt per run
Batch present; ///< prefix-1 objects, eligible to be stored
Batch recent; ///< prefix-1 objects in the "future" key space
std::vector<uint256> missing; ///< prefix-2 keys that are never stored
std::vector<std::size_t> shuffle; ///< [0, poolSize) permutation for random-like access
std::size_t avgPayload = 0; ///< mean getData().size() over `present`
void
release()
@@ -85,7 +86,7 @@ Workload const kInsert{
},
.iterate =
[](IterateContext const& ctx) {
auto& [rs, backend, index, poolSize] = ctx;
auto const& [rs, backend, index, poolSize] = ctx;
backend.store(rs.present[index % poolSize]);
},
.reportBytes = true,
@@ -104,7 +105,7 @@ Workload const kFetch{
},
.iterate =
[](IterateContext const& ctx) {
auto& [rs, backend, index, poolSize] = ctx;
auto const& [rs, backend, index, poolSize] = ctx;
std::shared_ptr<NodeObject> result;
backend.fetch(rs.present[index % poolSize]->getHash(), &result);
benchmark::DoNotOptimize(result);
@@ -118,7 +119,7 @@ Workload const kMissing{
.setup = [](SetupContext const& ctx) { ctx.rs.missing = makeMissingKeys(ctx.poolSize); },
.iterate =
[](IterateContext const& ctx) {
auto& [rs, backend, index, poolSize] = ctx;
auto const& [rs, backend, index, poolSize] = ctx;
std::shared_ptr<NodeObject> result;
backend.fetch(rs.missing[index % poolSize], &result);
benchmark::DoNotOptimize(result);
@@ -139,10 +140,10 @@ Workload const kMixed{
},
.iterate =
[](IterateContext const& ctx) {
auto& [rs, backend, index, poolSize] = ctx;
auto const& [rs, backend, index, poolSize] = ctx;
std::shared_ptr<NodeObject> result;
auto const pick = rs.shuffle[index % poolSize];
if (index % 5 == 0)
if (index % kMissRatio == 0)
{
backend.fetch(rs.missing[pick], &result);
}
@@ -170,7 +171,7 @@ Workload const kWork{
},
.iterate =
[](IterateContext const& ctx) {
auto& [rs, backend, index, poolSize] = ctx;
auto const& [rs, backend, index, poolSize] = ctx;
auto const slot = index % poolSize;
auto const pick = rs.shuffle[slot];
@@ -239,7 +240,7 @@ registerWorkload(BackendConfig const& bc, Workload const& w)
{
auto rs = std::make_shared<RunState>();
auto* b = benchmark::RegisterBenchmark(name, makeRunner(w, cfg, rs));
b->RangeMultiplier(10)->Range(kPoolSizes[0], kPoolSizes[std::size(kPoolSizes) - 1]);
b->RangeMultiplier(10)->Range(kPoolSizes.front(), kPoolSizes.back());
b->Threads(1)->Threads(4)->Threads(8)->UseRealTime();
return;
@@ -249,14 +250,14 @@ registerWorkload(BackendConfig const& bc, Workload const& w)
{
for (auto const threads : kThreadCounts)
{
if (poolSize % static_cast<std::size_t>(threads) != 0)
if (poolSize % threads != 0)
continue;
auto rs = std::make_shared<RunState>();
benchmark::RegisterBenchmark(name, makeRunner(w, cfg, rs))
->Arg(poolSize)
->Iterations(poolSize / static_cast<std::size_t>(threads))
->Threads(threads)
->Iterations(poolSize / threads)
->Threads(static_cast<int>(threads))
->UseRealTime();
}
}
@@ -289,7 +290,7 @@ registerStoreBatch(BackendConfig const& bc)
rs->harness = std::make_unique<BackendHarness>(cfg);
rs->present = makePool(1, poolSize);
rs->avgPayload = averagePayload(rs->present);
std::vector<Batch> const batches = sliceBatches(rs->present, kBatchSize);
std::vector<Batch> const batches = sliceFixedBatches(rs->present, kBatchSize);
if (batches.empty())
{
state.SkipWithError("pool smaller than one batch");
@@ -326,4 +327,4 @@ registerStoreBatch(BackendConfig const& bc)
}();
} // namespace
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -18,7 +18,7 @@
#include <utility>
#include <vector>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
namespace {
// Number of distinct objects pre-generated per run.
@@ -240,4 +240,4 @@ registerWorkload(BackendConfig const& bc, Workload const& w)
}();
} // namespace
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -26,13 +26,14 @@
#include <memory>
#include <numeric>
#include <random>
#include <ranges>
#include <string>
#include <utility>
#include <vector>
// Shared helpers for the NodeStore benchmarks.
//
namespace xrpl::NodeStore {
namespace xrpl::node_store {
// Fill `bytes` of memory at `buffer` with random bits drawn from `g`.
template <class Generator>
@@ -40,18 +41,13 @@ inline void
rngcpy(void* buffer, std::size_t bytes, Generator& g)
{
using result_type = typename Generator::result_type;
while (bytes >= sizeof(result_type))
while (bytes > 0)
{
auto const v = g();
std::memcpy(buffer, &v, sizeof(v));
buffer = reinterpret_cast<std::uint8_t*>(buffer) + sizeof(v);
bytes -= sizeof(v);
}
if (bytes > 0)
{
auto const v = g();
std::memcpy(buffer, &v, bytes);
auto const chunk = std::min(bytes, sizeof(result_type));
std::memcpy(buffer, &v, chunk);
buffer = reinterpret_cast<std::uint8_t*>(buffer) + chunk;
bytes -= chunk;
}
}
@@ -145,7 +141,7 @@ makePool(std::uint8_t prefix, std::size_t count, std::size_t start = 0)
Sequence seq(prefix);
Batch pool;
pool.reserve(count);
for (std::size_t i = 0; i < count; ++i)
for (auto i = 0uz; i < count; ++i)
pool.push_back(seq.obj(start + i));
return pool;
}
@@ -158,7 +154,7 @@ makeMissingKeys(std::size_t count)
Sequence seq(2);
std::vector<uint256> keys;
keys.reserve(count);
for (std::size_t i = 0; i < count; ++i)
for (auto i = 0uz; i < count; ++i)
keys.push_back(seq.key(i));
return keys;
}
@@ -206,16 +202,16 @@ inline std::vector<std::size_t>
makeShuffle(std::size_t size, std::uint64_t seed)
{
std::vector<std::size_t> v(size);
std::iota(v.begin(), v.end(), std::size_t{0});
std::ranges::iota(v, 0uz);
beast::xor_shift_engine gen(seed);
std::shuffle(v.begin(), v.end(), gen);
std::ranges::shuffle(v, gen);
return v;
}
// Partition a pool into fixed-size batches. Any trailing remainder shorter than
// `batchSize` is dropped, so every returned batch has exactly `batchSize`.
inline std::vector<Batch>
sliceBatches(Batch const& pool, std::size_t batchSize)
sliceFixedBatches(Batch const& pool, std::size_t batchSize)
{
std::vector<Batch> batches;
if (batchSize == 0)
@@ -228,13 +224,10 @@ sliceBatches(Batch const& pool, std::size_t batchSize)
/**
* @brief RAII owner of a NodeStore Backend opened on a private temporary directory.
*
* Member declaration order matters: `tempDir` is declared first so it is
* destroyed last, after the backend has closed and released its files.
*/
struct BackendHarness
{
beast::TempDir tempDir;
beast::TempDir tempDir; ///< Declared first so it is destroyed last
DummyScheduler scheduler;
beast::Journal journal{beast::Journal::getNullSink()};
std::unique_ptr<Backend> backend;
@@ -315,4 +308,4 @@ backendConfigs()
return kConfigs;
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -15,7 +15,7 @@
namespace beast {
std::string
printIdentifiers(SemanticVersion::identifier_list const& list)
printIdentifiers(SemanticVersion::IdentifierList const& list)
{
std::string ret;
@@ -115,7 +115,7 @@ extractIdentifier(std::string& value, bool allowLeadingZeroes, std::string& inpu
bool
extractIdentifiers(
SemanticVersion::identifier_list& identifiers,
SemanticVersion::IdentifierList& identifiers,
bool allowLeadingZeroes,
std::string& input)
{

View File

@@ -9,6 +9,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -130,6 +131,127 @@ isRounded(Asset const& asset, Number const& value, std::int32_t scale)
roundToAsset(asset, value, scale, Number::RoundingMode::Upward);
}
namespace Accrual {
AccountingDeltas
loanOriginationDeltas(Number const& principalRequested, Number const& interestDue)
{
return {.assetsTotalDelta = interestDue, .debtTotalDelta = principalRequested + interestDue};
}
bool
loanOriginationExceedsVaultMaximum(
Number const& vaultMaximum,
Number const& vaultTotal,
Number const& interestDue)
{
return vaultMaximum != 0 && interestDue > vaultMaximum - vaultTotal;
}
/*
XLS-66 section 3.2.3.2, defines the default amount as
DefaultAmount = (Loan.PrincipalOutstanding + Loan.InterestOutstanding)
Which is equivalent to (Loan.TotalValueOutstanding - Loan.ManagementFeeOutstanding)
*/
Number
loanVaultExposure(SLE::const_ref loanSle)
{
return loanSle->at(sfTotalValueOutstanding) - loanSle->at(sfManagementFeeOutstanding);
}
AccountingDeltas
loanPaymentDeltas(LoanPaymentParts const& parts)
{
return {
.assetsTotalDelta = parts.valueChange,
.debtTotalDelta = (parts.principalPaid + parts.interestPaid) - parts.valueChange};
}
} // namespace Accrual
namespace CashBasis {
AccountingDeltas
loanOriginationDeltas(Number const& principalRequested)
{
return {.assetsTotalDelta = kNumZero, .debtTotalDelta = principalRequested};
}
/*
* Under CashBasis accounting, Loan default amount is:
*
* DefaultAmount = Loan.PrincipalOutstanding
*/
Number
loanVaultExposure(SLE::const_ref loanSle)
{
return loanSle->at(sfPrincipalOutstanding);
}
AccountingDeltas
loanPaymentDeltas(LoanPaymentParts const& parts)
{
return {.assetsTotalDelta = parts.interestPaid, .debtTotalDelta = parts.principalPaid};
}
} // namespace CashBasis
namespace {
// Cash-basis accounting applies only when featureLendingProtocolV1_1 is
// enabled AND the specific Vault was created under it (LEVersion ==
// VaultVersion::CashBasis). Vaults created before activation keep accrual-basis
// accounting forever, even after the amendment later turns on.
bool
cashBasisEnabled(SLE::const_ref vaultSle)
{
return getVaultVersion(vaultSle) == VaultVersion::CashBasis;
}
} // namespace
AccountingDeltas
loanOriginationDeltas(
SLE::const_ref vaultSle,
Number const& principalRequested,
Number const& interestDue)
{
return cashBasisEnabled(vaultSle)
? CashBasis::loanOriginationDeltas(principalRequested)
: Accrual::loanOriginationDeltas(principalRequested, interestDue);
}
bool
loanOriginationExceedsVaultMaximum(
SLE::const_ref vaultSle,
Number const& vaultTotal,
Number const& interestDue)
{
// Cash-basis origination doesn't recognize interest into AssetsTotal, so
// interest due can never push the vault past AssetsMaximum at origination.
if (cashBasisEnabled(vaultSle))
return false;
auto const vaultMaximum = vaultSle->at(sfAssetsMaximum);
return Accrual::loanOriginationExceedsVaultMaximum(vaultMaximum, vaultTotal, interestDue);
}
Number
loanVaultExposure(SLE::const_ref vaultSle, SLE::const_ref loanSle)
{
return cashBasisEnabled(vaultSle) ? CashBasis::loanVaultExposure(loanSle)
: Accrual::loanVaultExposure(loanSle);
}
AccountingDeltas
loanPaymentDeltas(SLE::const_ref vaultSle, LoanPaymentParts const& parts)
{
return cashBasisEnabled(vaultSle) ? CashBasis::loanPaymentDeltas(parts)
: Accrual::loanPaymentDeltas(parts);
}
namespace detail {
void

View File

@@ -3,14 +3,19 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
@@ -18,12 +23,19 @@
#include <cstdint>
#include <limits>
#include <optional>
#include <variant>
namespace xrpl {
TER
closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal j)
closeChannel(
SLE::ref slep,
ApplyViewContext ctx,
uint256 const& key,
AccountID const& txAccount,
beast::Journal j)
{
ApplyView& view = ctx.view;
AccountID const src = (*slep)[sfAccount];
// Remove PayChan from owner directory
{
@@ -38,9 +50,9 @@ closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal
}
// Remove PayChan from recipient's owner directory, if present.
AccountID const dst = (*slep)[sfDestination];
if (auto const page = (*slep)[~sfDestinationNode])
{
auto const dst = (*slep)[sfDestination];
if (!view.dirRemove(keylet::ownerDir(dst), *page, key, true))
{
// LCOV_EXCL_START
@@ -57,7 +69,63 @@ closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal
XRPL_ASSERT(
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
auto const reqDelta = (*slep)[sfAmount] - (*slep)[sfBalance];
auto const& issuer = reqDelta.getIssuer();
// Only update the balance if there is a positive delta.
if (reqDelta > beast::kZero)
{
if (isXRP(reqDelta))
{
(*sle)[sfBalance] = (*sle)[sfBalance] + reqDelta;
}
else
{
if (!view.rules().enabled(featureTokenPaychan))
return temDISABLED;
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowUnlockPreclaimHelper<T>(view, src, reqDelta, false);
},
reqDelta.asset().value());
!isTesSuccess(ret))
return ret;
bool const createAsset = src == txAccount;
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx,
kParityRate,
sle,
STAmount{(*sle)[sfBalance]}.xrp(),
reqDelta,
issuer,
src,
src,
createAsset,
j);
},
reqDelta.asset().value());
!isTesSuccess(ret))
return ret;
}
}
// Remove PayChan from issuer's owner directory, if present.
if (auto const optPage = (*slep)[~sfIssuerNode])
{
if (!view.dirRemove(keylet::ownerDir(issuer), *optPage, key, true))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Could not remove paychan from issuer owner directory";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
decreaseOwnerCountForObject(view, sle, slep, 1, j);
view.update(sle);

View File

@@ -1,39 +0,0 @@
#include <xrpl/ledger/helpers/ProposalHelpers.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/TxFormats.h>
namespace xrpl::proposal {
bool
isValidProposal(STObject const& proposedTx)
{
if (isProposalTx(proposedTx))
return false;
if (isPseudoTx(proposedTx))
return false;
if (proposedTx.isFieldPresent(sfFlags) &&
(proposedTx.getFieldU32(sfFlags) & tfInnerBatchTxn) != 0u)
return false;
if (proposedTx.getFieldU16(sfTransactionType) == ttBATCH &&
proposedTx.isFieldPresent(sfRawTransactions))
{
STArray const& innerTxns = proposedTx.getFieldArray(sfRawTransactions);
for (STObject const& inner : innerTxns)
{
if (isProposalTx(inner) || isPseudoTx(inner))
return false;
}
}
return true;
}
} // namespace xrpl::proposal

View File

@@ -5,7 +5,6 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/OracleHelpers.h>
#include <xrpl/ledger/helpers/ProposalHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -57,7 +56,6 @@ isReserveSponsorAllowed(TxType txType)
ttACCOUNT_SET,
ttREGULAR_KEY_SET,
ttSPONSORSHIP_TRANSFER,
ttTRANSACTION_PROPOSAL_CREATE,
};
return kReserveSponsorAllowed.contains(txType);
}
@@ -257,8 +255,6 @@ isLedgerEntryOwner(ReadView const& view, SLE const& sle, AccountID const& accoun
// to tecNO_PERMISSION.
return false;
}
case ltTRANSACTION_PROPOSAL:
return sle.getAccountID(sfOwner) == account;
default:
// LCOV_EXCL_START
UNREACHABLE("xrpl::isLedgerEntryOwner : object is not supported by sponsorship.");
@@ -282,7 +278,6 @@ isLedgerEntrySupportedBySponsorship(SLE const& sle)
case ltSIGNER_LIST:
case ltCREDENTIAL:
case ltRIPPLE_STATE:
case ltTRANSACTION_PROPOSAL:
return true;
default:
return false;
@@ -309,11 +304,6 @@ getLedgerEntryOwnerCount(SLE const& sle)
return 1;
return 2 + static_cast<std::uint32_t>(sle.getFieldArray(sfSignerEntries).size());
}
case ltTRANSACTION_PROPOSAL:
// Mirror TransactionProposalCreate's own reserve sizing so that
// creation and sponsorship accounting agree: a proposed Batch
// reserves more than an ordinary proposal.
return proposal::proposalOwnerCount(sle.getFieldObject(sfProposedTransaction));
case ltACCOUNT_ROOT:
// LCOV_EXCL_START
UNREACHABLE("AccountRoots are not supported by object sponsorship.");

View File

@@ -6,6 +6,7 @@
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -13,6 +14,7 @@
#include <cstdint>
#include <optional>
#include <utility>
namespace xrpl {
@@ -137,4 +139,22 @@ isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref
return sleToken->getFieldU64(sfMPTAmount) == outstanding;
}
[[nodiscard]] VaultVersion
getVaultVersion(SLE::const_ref vault)
{
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::getVaultVersion : valid Vault sle");
if (!vault->isFieldPresent(sfLEVersion))
return VaultVersion::Legacy;
auto const version = vault->at(sfLEVersion);
if (version > std::to_underlying(VaultVersion::CashBasis))
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::getVaultVersion : invalid vault version");
return VaultVersion::Legacy;
// LCOV_EXCL_STOP
}
return static_cast<VaultVersion>(version);
}
} // namespace xrpl

View File

@@ -11,7 +11,7 @@
#include <mutex>
#include <vector>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
BatchWriter::BatchWriter(Callback& callback, Scheduler& scheduler)
: callback_(callback), scheduler_(scheduler)
@@ -72,7 +72,7 @@ BatchWriter::writeBatch()
writeSet_.swap(set);
XRPL_ASSERT(
writeSet_.empty(), "xrpl::NodeStore::BatchWriter::writeBatch : writes not set");
writeSet_.empty(), "xrpl::node_store::BatchWriter::writeBatch : writes not set");
writeLoad_ = set.size();
if (set.empty())
@@ -107,4 +107,4 @@ BatchWriter::waitForWriting()
writeCondition_.wait(sl);
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -30,7 +30,7 @@
#include <thread>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
Database::Database(
Scheduler& scheduler,
@@ -43,7 +43,7 @@ Database::Database(
, requestBundle_(get<int>(config, Keys::kRqBundle, 4))
, readThreads_(std::max(1, readThreads))
{
XRPL_ASSERT(readThreads, "xrpl::NodeStore::Database::Database : nonzero threads input");
XRPL_ASSERT(readThreads, "xrpl::node_store::Database::Database : nonzero threads input");
if (earliestLedgerSeq_ < 1)
Throw<std::runtime_error>("Invalid earliest_seq");
@@ -89,7 +89,7 @@ Database::Database(
{
XRPL_ASSERT(
!it->second.empty(),
"xrpl::NodeStore::Database::Database : non-empty "
"xrpl::node_store::Database::Database : non-empty "
"data");
auto const& hash = it->first;
@@ -164,7 +164,7 @@ Database::stop()
{
XRPL_ASSERT(
steady_clock::now() - start < 30s,
"xrpl::NodeStore::Database::stop : maximum stop duration");
"xrpl::node_store::Database::stop : maximum stop duration");
std::this_thread::yield();
}
@@ -213,7 +213,7 @@ Database::importInternal(Backend& dstBackend, Database& srcDB)
};
srcDB.forEach([&](std::shared_ptr<NodeObject> nodeObject) {
XRPL_ASSERT(nodeObject, "xrpl::NodeStore::Database::importInternal : non-null node");
XRPL_ASSERT(nodeObject, "xrpl::node_store::Database::importInternal : non-null node");
if (!nodeObject) // This should never happen
return;
@@ -257,7 +257,7 @@ Database::fetchNodeObject(
void
Database::getCountsJson(json::Value& obj)
{
XRPL_ASSERT(obj.isObject(), "xrpl::NodeStore::Database::getCountsJson : valid input type");
XRPL_ASSERT(obj.isObject(), "xrpl::node_store::Database::getCountsJson : valid input type");
{
std::unique_lock<std::mutex> const lock(readLock_);
@@ -276,4 +276,4 @@ Database::getCountsJson(json::Value& obj)
obj[jss::node_reads_duration_us] = std::to_string(fetchDurationUs_);
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -15,7 +15,7 @@
#include <memory>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
void
DatabaseNodeImp::store(NodeObjectType type, Blob&& data, uint256 const& hash, std::uint32_t)
@@ -125,4 +125,4 @@ DatabaseNodeImp::fetchNodeObject(
return nodeObject;
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -22,7 +22,7 @@
#include <string>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
DatabaseRotatingImp::DatabaseRotatingImp(
Scheduler& scheduler,
@@ -43,7 +43,7 @@ DatabaseRotatingImp::DatabaseRotatingImp(
void
DatabaseRotatingImp::rotate(
std::unique_ptr<NodeStore::Backend>&& newBackend,
std::unique_ptr<node_store::Backend>&& newBackend,
std::function<void(std::string const& writableName, std::string const& archiveName)> const& f)
{
// Pass these two names to the callback function
@@ -52,7 +52,7 @@ DatabaseRotatingImp::rotate(
// Hold on to current archive backend pointer until after the
// callback finishes. Only then will the archive directory be
// deleted.
std::shared_ptr<NodeStore::Backend> oldArchiveBackend;
std::shared_ptr<node_store::Backend> oldArchiveBackend;
std::uint64_t copyForwards = 0;
{
std::scoped_lock const lock(mutex_);
@@ -232,4 +232,4 @@ DatabaseRotatingImp::forEach(std::function<void(std::shared_ptr<NodeObject>)> f)
archive->forEach(f);
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -10,7 +10,7 @@
#include <memory>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
DecodedBlob::DecodedBlob(void const* key, void const* value, int valueBytes) : key_(key)
{
@@ -55,7 +55,7 @@ DecodedBlob::DecodedBlob(void const* key, void const* value, int valueBytes) : k
std::shared_ptr<NodeObject>
DecodedBlob::createObject()
{
XRPL_ASSERT(success_, "xrpl::NodeStore::DecodedBlob::createObject : valid object type");
XRPL_ASSERT(success_, "xrpl::node_store::DecodedBlob::createObject : valid object type");
std::shared_ptr<NodeObject> object;
@@ -69,4 +69,4 @@ DecodedBlob::createObject()
return object;
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -3,7 +3,7 @@
#include <xrpl/nodestore/Scheduler.h>
#include <xrpl/nodestore/Task.h>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
void
DummyScheduler::scheduleTask(Task& task)
@@ -22,4 +22,4 @@ DummyScheduler::onBatchWrite(BatchWriteReport const& report)
{
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -22,7 +22,7 @@
#include <string>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
ManagerImp&
ManagerImp::instance()
@@ -112,7 +112,7 @@ ManagerImp::erase(Factory& factory)
std::scoped_lock const _(mutex_);
auto const iter =
std::ranges::find_if(list_, [&factory](Factory* other) { return other == &factory; });
XRPL_ASSERT(iter != list_.end(), "xrpl::NodeStore::ManagerImp::erase : valid input");
XRPL_ASSERT(iter != list_.end(), "xrpl::node_store::ManagerImp::erase : valid input");
list_.erase(iter);
}
@@ -135,4 +135,4 @@ Manager::instance()
return ManagerImp::instance();
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -24,7 +24,7 @@
#include <tuple>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
struct MemoryDB
{
@@ -132,7 +132,7 @@ public:
Status
fetch(uint256 const& hash, std::shared_ptr<NodeObject>* pObject) override
{
XRPL_ASSERT(db_, "xrpl::NodeStore::MemoryBackend::fetch : non-null database");
XRPL_ASSERT(db_, "xrpl::node_store::MemoryBackend::fetch : non-null database");
std::scoped_lock const _(db_->mutex);
@@ -149,7 +149,7 @@ public:
void
store(std::shared_ptr<NodeObject> const& object) override
{
XRPL_ASSERT(db_, "xrpl::NodeStore::MemoryBackend::store : non-null database");
XRPL_ASSERT(db_, "xrpl::node_store::MemoryBackend::store : non-null database");
std::scoped_lock const _(db_->mutex);
db_->table.emplace(object->getHash(), object);
}
@@ -169,7 +169,7 @@ public:
void
forEach(std::function<void(std::shared_ptr<NodeObject>)> f) override
{
XRPL_ASSERT(db_, "xrpl::NodeStore::MemoryBackend::forEach : non-null database");
XRPL_ASSERT(db_, "xrpl::node_store::MemoryBackend::forEach : non-null database");
for (auto const& e : db_->table)
f(e.second);
}
@@ -216,4 +216,4 @@ MemoryFactory::createInstance(
return std::make_unique<MemoryBackend>(keyBytes, keyValues, journal);
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -44,7 +44,7 @@
#include <string>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
class NuDBBackend : public Backend
{
@@ -136,7 +136,7 @@ public:
{
// LCOV_EXCL_START
UNREACHABLE(
"xrpl::NodeStore::NuDBBackend::open : database is already "
"xrpl::node_store::NuDBBackend::open : database is already "
"open");
JLOG(j.error()) << "database is already open";
return;
@@ -441,4 +441,4 @@ registerNuDBFactory(Manager& manager)
static NuDBFactory const kInstance{manager};
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -13,7 +13,7 @@
#include <memory>
#include <string>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
class NullBackend : public Backend
{
@@ -125,4 +125,4 @@ registerNullFactory(Manager& manager)
static NullFactory const kInstance{manager};
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -42,7 +42,7 @@
#include <stdexcept>
#include <string>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
class RocksDBEnv : public rocksdb::EnvWrapper
{
@@ -231,7 +231,7 @@ public:
{
// LCOV_EXCL_START
UNREACHABLE(
"xrpl::NodeStore::RocksDBBackend::open : database is already "
"xrpl::node_store::RocksDBBackend::open : database is already "
"open");
JLOG(journal.error()) << "database is already open";
return;
@@ -279,7 +279,7 @@ public:
Status
fetch(uint256 const& hash, std::shared_ptr<NodeObject>* pObject) override
{
XRPL_ASSERT(db, "xrpl::NodeStore::RocksDBBackend::fetch : non-null database");
XRPL_ASSERT(db, "xrpl::node_store::RocksDBBackend::fetch : non-null database");
pObject->reset();
Status status = Status::Ok;
@@ -339,7 +339,7 @@ public:
{
XRPL_ASSERT(
db,
"xrpl::NodeStore::RocksDBBackend::storeBatch : non-null "
"xrpl::node_store::RocksDBBackend::storeBatch : non-null "
"database");
rocksdb::WriteBatch wb;
@@ -369,7 +369,7 @@ public:
void
forEach(std::function<void(std::shared_ptr<NodeObject>)> f) override
{
XRPL_ASSERT(db, "xrpl::NodeStore::RocksDBBackend::forEach : non-null database");
XRPL_ASSERT(db, "xrpl::node_store::RocksDBBackend::forEach : non-null database");
rocksdb::ReadOptions const options;
std::unique_ptr<rocksdb::Iterator> it(db->NewIterator(options));
@@ -468,6 +468,6 @@ registerRocksDBFactory(Manager& manager)
static RocksDBFactory const kInstance{manager};
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store
#endif

View File

@@ -104,7 +104,6 @@ enum class LedgerNameSpace : std::uint16_t {
LoanBroker = 'l', // lower-case L
Loan = 'L',
Sponsorship = '>',
TransactionProposal = 'y',
// No longer used or supported. Left here to reserve the space to avoid accidental reuse.
Contract [[deprecated]] = 'c',
@@ -359,14 +358,6 @@ check(AccountID const& id, std::uint32_t seq) noexcept
return {ltCHECK, indexHash(LedgerNameSpace::Check, id, seq)};
}
Keylet
txProposal(AccountID const& target, std::uint32_t ticketSequence) noexcept
{
return {
ltTRANSACTION_PROPOSAL,
indexHash(LedgerNameSpace::TransactionProposal, target, ticketSequence)};
}
Keylet
depositPreauth(AccountID const& owner, AccountID const& preauthorized) noexcept
{

View File

@@ -129,7 +129,8 @@ selectBranch(SHAMapNodeID const& id, uint256 const& hash)
SHAMapNodeID
SHAMapNodeID::createID(int depth, uint256 const& key)
{
XRPL_ASSERT((depth >= 0) && (depth < 65), "xrpl::SHAMapNodeID::createID : valid branch input");
XRPL_ASSERT(
depth >= 0 && depth <= SHAMap::kLeafDepth, "xrpl::SHAMapNodeID::createID : valid depth");
return SHAMapNodeID(depth, key & depthMask(depth));
}

View File

@@ -107,7 +107,7 @@ SHAMap::visitNodes(std::function<bool(SHAMapTreeNode&)> const& function) const
void
SHAMap::visitDifferences(
SHAMap const* have,
SHAMap const* map,
std::function<bool(SHAMapTreeNode const&)> const& function) const
{
// Visit every node in this SHAMap that is not present
@@ -118,13 +118,13 @@ SHAMap::visitDifferences(
if (root_->getHash().isZero())
return;
if ((have != nullptr) && (root_->getHash() == have->root_->getHash()))
if ((map != nullptr) && (root_->getHash() == map->root_->getHash()))
return;
if (root_->isLeaf())
{
auto leaf = intr_ptr::staticPointerCast<SHAMapLeafNode>(root_);
if ((have == nullptr) || !have->hasLeafNode(leaf->peekItem()->key(), leaf->getHash()))
if ((map == nullptr) || !map->hasLeafNode(leaf->peekItem()->key(), leaf->getHash()))
function(*root_);
return;
}
@@ -149,18 +149,15 @@ SHAMap::visitDifferences(
if (!node->isEmptyBranch(i))
{
auto const& childHash = node->getChildHash(i);
SHAMapNodeID const childID = nodeID.getChildNodeID(i);
auto const childID = nodeID.getChildNodeID(i);
auto next = descendThrow(node, i);
if (next->isInner())
{
if ((have == nullptr) || !have->hasInnerNode(childID, childHash))
if ((map == nullptr) || !map->hasInnerNode(childID, childHash))
stack.emplace(safeDowncast<SHAMapInnerNode*>(next), childID);
}
else if (
(have == nullptr) ||
!have->hasLeafNode(
safeDowncast<SHAMapLeafNode*>(next)->peekItem()->key(), childHash))
else if ((map == nullptr) || !map->hasLeafNode(leafKey(*next), childHash))
{
if (!function(*next))
return;
@@ -414,7 +411,7 @@ SHAMap::getMissingNodes(int max, SHAMapSyncFilter const* filter)
bool
SHAMap::getNodeFat(
SHAMapNodeID const& wanted,
std::vector<std::pair<SHAMapNodeID, Blob>>& data,
std::vector<SHAMapNodeData>& data,
bool fatLeaves,
std::uint32_t depth) const
{
@@ -460,7 +457,7 @@ SHAMap::getNodeFat(
// Add this node to the reply
s.erase();
node->serializeForWire(s);
data.emplace_back(nodeID, s.getData());
data.emplace_back(nodeID, node->isLeaf(), s.getData());
if (node->isInner())
{
@@ -490,7 +487,7 @@ SHAMap::getNodeFat(
// Just include this node
s.erase();
childNode->serializeForWire(s);
data.emplace_back(childID, s.getData());
data.emplace_back(childID, childNode->isLeaf(), s.getData());
}
}
}
@@ -508,25 +505,33 @@ SHAMap::serializeRoot(Serializer& s) const
}
SHAMapAddNode
SHAMap::addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFilter const* filter)
SHAMap::addRootNode(
SHAMapHash const& hash,
SHAMapTreeNodePtr rootNode,
SHAMapSyncFilter const* filter)
{
XRPL_ASSERT(cowid_ >= 1, "xrpl::SHAMap::addRootNode : valid cowid");
XRPL_ASSERT(rootNode, "xrpl::SHAMap::addRootNode : non-null root node");
// we already have a root_ node
if (root_->getHash().isNonZero())
{
JLOG(journal_.trace()) << "got root node, already have one";
XRPL_ASSERT(root_->getHash() == hash, "xrpl::SHAMap::addRootNode : valid hash input");
JLOG(journal_.trace()) << "Got root node, already have one";
XRPL_ASSERT(root_->getHash() == hash, "xrpl::SHAMap::addRootNode : valid hash");
return SHAMapAddNode::duplicate();
}
XRPL_ASSERT(cowid_ >= 1, "xrpl::SHAMap::addRootNode : valid cowid");
auto node = SHAMapTreeNode::makeFromWire(rootNode);
if (!node || node->getHash() != hash)
if (rootNode->getHash() != hash)
{
JLOG(journal_.warn()) << "Corrupt root node received: expected hash " << hash << ", got "
<< rootNode->getHash();
return SHAMapAddNode::invalid();
}
if (backed_)
canonicalize(hash, node);
canonicalize(hash, rootNode);
root_ = node;
root_ = std::move(rootNode);
if (root_->isLeaf())
clearSynching();
@@ -543,9 +548,18 @@ SHAMap::addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFil
}
SHAMapAddNode
SHAMap::addKnownNode(SHAMapNodeID const& node, Slice const& rawNode, SHAMapSyncFilter const* filter)
SHAMap::addKnownNode(
SHAMapNodeID const& nodeID,
SHAMapTreeNodePtr treeNode,
SHAMapSyncFilter const* filter)
{
XRPL_ASSERT(!node.isRoot(), "xrpl::SHAMap::addKnownNode : valid node input");
XRPL_ASSERT(!nodeID.isRoot(), "xrpl::SHAMap::addKnownNode : valid node");
XRPL_ASSERT(treeNode, "xrpl::SHAMap::addKnownNode : non-null tree node");
XRPL_ASSERT(
!treeNode->isLeaf() ||
SHAMapNodeID::createID(nodeID.getDepth(), leafKey(*treeNode)).getNodeID() ==
nodeID.getNodeID(),
"xrpl::SHAMap::addKnownNode : leaf position consistent with node ID");
if (!isSynching())
{
@@ -559,14 +573,15 @@ SHAMap::addKnownNode(SHAMapNodeID const& node, Slice const& rawNode, SHAMapSyncF
while (currNode->isInner() &&
!safeDowncast<SHAMapInnerNode*>(currNode)->isFullBelow(generation) &&
(currNodeID.getDepth() < node.getDepth()))
(currNodeID.getDepth() < nodeID.getDepth()))
{
int const branch = selectBranch(currNodeID, node.getNodeID());
int const branch = selectBranch(currNodeID, nodeID.getNodeID());
XRPL_ASSERT(branch >= 0, "xrpl::SHAMap::addKnownNode : valid branch");
auto inner = safeDowncast<SHAMapInnerNode*>(currNode);
if (inner->isEmptyBranch(branch))
{
JLOG(journal_.warn()) << "Add known node for empty branch" << node;
JLOG(journal_.warn()) << "Add known node " << nodeID << " for empty branch " << branch
<< " at " << currNodeID;
return SHAMapAddNode::invalid();
}
@@ -582,67 +597,45 @@ SHAMap::addKnownNode(SHAMapNodeID const& node, Slice const& rawNode, SHAMapSyncF
if (currNode != nullptr)
continue;
auto newNode = SHAMapTreeNode::makeFromWire(rawNode);
if (!newNode || childHash != newNode->getHash())
if (childHash != treeNode->getHash())
{
JLOG(journal_.warn()) << "Corrupt node received";
JLOG(journal_.warn()) << "Corrupt node " << nodeID << " received: expected hash "
<< childHash << ", got " << treeNode->getHash();
return SHAMapAddNode::invalid();
}
// In rare cases, a node can still be corrupt even after hash
// validation. For leaf nodes, we perform an additional check to
// ensure the node's position in the tree is consistent with its
// content to prevent inconsistencies that could
// propagate further down the line.
if (newNode->isLeaf())
{
auto const& actualKey =
safeDowncast<SHAMapLeafNode const*>(newNode.get())->peekItem()->key();
// Validate that this leaf belongs at the target position
auto const expectedNodeID = SHAMapNodeID::createID(node.getDepth(), actualKey);
if (expectedNodeID.getNodeID() != node.getNodeID())
{
JLOG(journal_.debug())
<< "Leaf node position mismatch: "
<< "expected=" << expectedNodeID.getNodeID() << ", actual=" << node.getNodeID();
return SHAMapAddNode::invalid();
}
}
// Inner nodes must be at a level strictly less than 64
// but leaf nodes (while notionally at level 64) can be
// at any depth up to and including 64:
if ((currNodeID.getDepth() > kLeafDepth) ||
(newNode->isInner() && currNodeID.getDepth() == kLeafDepth))
(treeNode->isInner() && currNodeID.getDepth() == kLeafDepth))
{
// Map is provably invalid
state_ = SHAMapState::Invalid;
return SHAMapAddNode::useful();
}
if (currNodeID != node)
if (currNodeID != nodeID)
{
// Either this node is broken or we didn't request it (yet)
JLOG(journal_.warn()) << "unable to hook node " << node;
JLOG(journal_.warn()) << "unable to hook node " << nodeID;
JLOG(journal_.info()) << " stuck at " << currNodeID;
JLOG(journal_.info()) << "got depth=" << node.getDepth()
JLOG(journal_.info()) << "got depth=" << nodeID.getDepth()
<< ", walked to= " << currNodeID.getDepth();
return SHAMapAddNode::useful();
}
if (backed_)
canonicalize(childHash, newNode);
canonicalize(childHash, treeNode);
newNode = prevNode->canonicalizeChild(branch, std::move(newNode));
treeNode = prevNode->canonicalizeChild(branch, std::move(treeNode));
if (filter != nullptr)
{
Serializer s;
newNode->serializeWithPrefix(s);
treeNode->serializeWithPrefix(s);
filter->gotNode(
false, childHash, ledgerSeq_, std::move(s.modData()), newNode->getType());
false, childHash, ledgerSeq_, std::move(s.modData()), treeNode->getType());
}
return SHAMapAddNode::useful();

View File

@@ -324,17 +324,6 @@ preflight(
beast::Journal j)
{
PreflightContext const pfCtx(registry, tx, rules, flags, j);
// XRPL_ASSERT_IF in the PreflightContext constructor only fires in debug
// builds; re-check the same invariant here so a release build can't
// silently skip a proposed transaction's signature-presence checks
// outside of a dry run.
if ((flags & TapProposal) != TapNone && (flags & TapDryRun) == TapNone)
{
JLOG(j.fatal()) << "apply (preflight): TapProposal set without TapDryRun.";
return {pfCtx, {tefEXCEPTION, TxConsequences{tx}}};
}
try
{
return {pfCtx, invokePreflight(pfCtx)};
@@ -356,14 +345,6 @@ preflight(
beast::Journal j)
{
PreflightContext const pfCtx(registry, tx, parentBatchId, rules, flags, j);
// See the comment in the other preflight() overload above.
if ((flags & TapProposal) != TapNone && (flags & TapDryRun) == TapNone)
{
JLOG(j.fatal()) << "apply (preflight): TapProposal set without TapDryRun.";
return {pfCtx, {tefEXCEPTION, TxConsequences{tx}}};
}
try
{
return {pfCtx, invokePreflight(pfCtx)};

View File

@@ -142,7 +142,8 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a
drops_ -= (*before)[sfBalance].xrp().drops();
break;
case ltPAYCHAN:
drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
if (isXRP((*before)[sfAmount]))
drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
break;
case ltESCROW:
if (isXRP((*before)[sfAmount]))
@@ -175,7 +176,7 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a
drops_ += (*after)[sfBalance].xrp().drops();
break;
case ltPAYCHAN:
if (!isDelete)
if (!isDelete && isXRP((*after)[sfAmount]))
drops_ += ((*after)[sfAmount] - (*after)[sfBalance]).xrp().drops();
break;
case ltESCROW:

View File

@@ -375,6 +375,10 @@ ValidMPTIssuance::finalize(
return true;
}
if (tx.getTxnType() == ttPAYCHAN_CLAIM && mptIssuancesCreated_ == 0 &&
mptIssuancesDeleted_ == 0 && mptokensDeleted_ == 0 && mptokensCreated_ <= 1)
return true;
if (hasPrivilege(tx, MayDeleteMpt) &&
((txnType == ttAMM_DELETE && mptokensDeleted_ <= 2) || mptokensDeleted_ == 1) &&
mptokensCreated_ == 0 && mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0)

View File

@@ -283,10 +283,23 @@ OfferCreate::checkAcceptAsset(
return asset.visit(
[&](Issue const& issue) -> TER {
auto const& issuer = issue.getIssuer();
auto const trustLine = view.read(keylet::trustLine(id, issuer, issue.currency));
// Check if the issuer has lsfDisallowIncomingTrustline set.
// If so, the account must already have a trustline to receive tokens.
if (view.rules().enabled(fixCleanup3_4_0) &&
issuerAccount->isFlag(lsfDisallowIncomingTrustline))
{
if (!trustLine)
{
JLOG(j.debug()) << "delay: can't receive IOUs from issuer with "
"DisallowIncomingTrustline set";
return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE};
}
}
if (issuerAccount->isFlag(lsfRequireAuth))
{
auto const trustLine = view.read(keylet::trustLine(id, issuer, issue.currency));
if (!trustLine)
{
return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE};
@@ -309,8 +322,6 @@ OfferCreate::checkAcceptAsset(
}
}
auto const trustLine = view.read(keylet::trustLine(id, issue.account, issue.currency));
if (!trustLine)
{
return tesSUCCESS;

Some files were not shown because too many files have changed in this diff Show More