From 32043463a8b59c12387268bd1a2b4165d70a96de Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Thu, 24 Jul 2025 19:08:46 -0400 Subject: [PATCH 01/24] Fix: Don't flag consensus as stalled prematurely (#5658) Fix stalled consensus detection to prevent false positives in situations where there are no disputed transactions. Stalled consensus detection was added to 2.5.0 in response to a network consensus halt that caused a round to run for over an hour. However, it has a flaw that makes it very easy to have false positives. Those false positives are usually mitigated by other checks that prevent them from having an effect, but there have been several instances of validators "running ahead" because there are circumstances where the other checks are "successful", allowing the stall state to be checked. --- src/test/consensus/Consensus_test.cpp | 183 ++++++++++++++++----- src/xrpld/app/consensus/RCLValidations.cpp | 2 +- src/xrpld/consensus/Consensus.cpp | 10 +- src/xrpld/consensus/Consensus.h | 24 ++- src/xrpld/consensus/DisputedTx.h | 27 ++- 5 files changed, 192 insertions(+), 54 deletions(-) diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index db56ab58c6..7899336a6f 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -1136,6 +1136,10 @@ public: ConsensusParms p; std::size_t peersUnchanged = 0; + auto logs = std::make_unique(beast::severities::kError); + auto j = logs->journal("Test"); + auto clog = std::make_unique(); + // Three cases: // 1 proposing, initial vote yes // 2 proposing, initial vote no @@ -1172,10 +1176,15 @@ public: BEAST_EXPECT(proposingFalse.getOurVote() == false); BEAST_EXPECT(followingTrue.getOurVote() == true); BEAST_EXPECT(followingFalse.getOurVote() == false); - BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged)); - BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged)); - BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged)); - BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged)); + BEAST_EXPECT( + !proposingTrue.stalled(p, true, peersUnchanged, j, clog)); + BEAST_EXPECT( + !proposingFalse.stalled(p, true, peersUnchanged, j, clog)); + BEAST_EXPECT( + !followingTrue.stalled(p, false, peersUnchanged, j, clog)); + BEAST_EXPECT( + !followingFalse.stalled(p, false, peersUnchanged, j, clog)); + BEAST_EXPECT(clog->str() == ""); // I'm in the majority, my vote should not change BEAST_EXPECT(!proposingTrue.updateVote(5, true, p)); @@ -1189,10 +1198,15 @@ public: BEAST_EXPECT(!followingFalse.updateVote(10, false, p)); peersUnchanged = 2; - BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged)); - BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged)); - BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged)); - BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged)); + BEAST_EXPECT( + !proposingTrue.stalled(p, true, peersUnchanged, j, clog)); + BEAST_EXPECT( + !proposingFalse.stalled(p, true, peersUnchanged, j, clog)); + BEAST_EXPECT( + !followingTrue.stalled(p, false, peersUnchanged, j, clog)); + BEAST_EXPECT( + !followingFalse.stalled(p, false, peersUnchanged, j, clog)); + BEAST_EXPECT(clog->str() == ""); // Right now, the vote is 51%. The requirement is about to jump to // 65% @@ -1282,10 +1296,15 @@ public: BEAST_EXPECT(followingFalse.getOurVote() == false); peersUnchanged = 3; - BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged)); - BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged)); - BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged)); - BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged)); + BEAST_EXPECT( + !proposingTrue.stalled(p, true, peersUnchanged, j, clog)); + BEAST_EXPECT( + !proposingFalse.stalled(p, true, peersUnchanged, j, clog)); + BEAST_EXPECT( + !followingTrue.stalled(p, false, peersUnchanged, j, clog)); + BEAST_EXPECT( + !followingFalse.stalled(p, false, peersUnchanged, j, clog)); + BEAST_EXPECT(clog->str() == ""); // Threshold jumps to 95% BEAST_EXPECT(proposingTrue.updateVote(220, true, p)); @@ -1322,12 +1341,60 @@ public: for (peersUnchanged = 0; peersUnchanged < 6; ++peersUnchanged) { - BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged)); - BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged)); - BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged)); - BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged)); + BEAST_EXPECT( + !proposingTrue.stalled(p, true, peersUnchanged, j, clog)); + BEAST_EXPECT( + !proposingFalse.stalled(p, true, peersUnchanged, j, clog)); + BEAST_EXPECT( + !followingTrue.stalled(p, false, peersUnchanged, j, clog)); + BEAST_EXPECT( + !followingFalse.stalled(p, false, peersUnchanged, j, clog)); + BEAST_EXPECT(clog->str() == ""); } + auto expectStalled = [this, &clog]( + int txid, + bool ourVote, + int ourTime, + int peerTime, + int support, + std::uint32_t line) { + using namespace std::string_literals; + + auto const s = clog->str(); + expect(s.find("stalled"), s, __FILE__, line); + expect( + s.starts_with("Transaction "s + std::to_string(txid)), + s, + __FILE__, + line); + expect( + s.find("voting "s + (ourVote ? "YES" : "NO")) != s.npos, + s, + __FILE__, + line); + expect( + s.find("for "s + std::to_string(ourTime) + " rounds."s) != + s.npos, + s, + __FILE__, + line); + expect( + s.find( + "votes in "s + std::to_string(peerTime) + " rounds.") != + s.npos, + s, + __FILE__, + line); + expect( + s.ends_with( + "has "s + std::to_string(support) + "% support. "s), + s, + __FILE__, + line); + clog = std::make_unique(); + }; + for (int i = 0; i < 1; ++i) { BEAST_EXPECT(!proposingTrue.updateVote(250 + 10 * i, true, p)); @@ -1342,22 +1409,34 @@ public: BEAST_EXPECT(followingFalse.getOurVote() == false); // true vote has changed recently, so not stalled - BEAST_EXPECT(!proposingTrue.stalled(p, true, 0)); + BEAST_EXPECT(!proposingTrue.stalled(p, true, 0, j, clog)); + BEAST_EXPECT(clog->str() == ""); // remaining votes have been unchanged in so long that we only // need to hit the second round at 95% to be stalled, regardless // of peers - BEAST_EXPECT(proposingFalse.stalled(p, true, 0)); - BEAST_EXPECT(followingTrue.stalled(p, false, 0)); - BEAST_EXPECT(followingFalse.stalled(p, false, 0)); + BEAST_EXPECT(proposingFalse.stalled(p, true, 0, j, clog)); + expectStalled(98, false, 11, 0, 2, __LINE__); + BEAST_EXPECT(followingTrue.stalled(p, false, 0, j, clog)); + expectStalled(97, true, 11, 0, 97, __LINE__); + BEAST_EXPECT(followingFalse.stalled(p, false, 0, j, clog)); + expectStalled(96, false, 11, 0, 3, __LINE__); // true vote has changed recently, so not stalled - BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged)); + BEAST_EXPECT( + !proposingTrue.stalled(p, true, peersUnchanged, j, clog)); + BEAST_EXPECTS(clog->str() == "", clog->str()); // remaining votes have been unchanged in so long that we only // need to hit the second round at 95% to be stalled, regardless // of peers - BEAST_EXPECT(proposingFalse.stalled(p, true, peersUnchanged)); - BEAST_EXPECT(followingTrue.stalled(p, false, peersUnchanged)); - BEAST_EXPECT(followingFalse.stalled(p, false, peersUnchanged)); + BEAST_EXPECT( + proposingFalse.stalled(p, true, peersUnchanged, j, clog)); + expectStalled(98, false, 11, 6, 2, __LINE__); + BEAST_EXPECT( + followingTrue.stalled(p, false, peersUnchanged, j, clog)); + expectStalled(97, true, 11, 6, 97, __LINE__); + BEAST_EXPECT( + followingFalse.stalled(p, false, peersUnchanged, j, clog)); + expectStalled(96, false, 11, 6, 3, __LINE__); } for (int i = 1; i < 3; ++i) { @@ -1374,19 +1453,31 @@ public: // true vote changed 2 rounds ago, and peers are changing, so // not stalled - BEAST_EXPECT(!proposingTrue.stalled(p, true, 0)); + BEAST_EXPECT(!proposingTrue.stalled(p, true, 0, j, clog)); + BEAST_EXPECTS(clog->str() == "", clog->str()); // still stalled - BEAST_EXPECT(proposingFalse.stalled(p, true, 0)); - BEAST_EXPECT(followingTrue.stalled(p, false, 0)); - BEAST_EXPECT(followingFalse.stalled(p, false, 0)); + BEAST_EXPECT(proposingFalse.stalled(p, true, 0, j, clog)); + expectStalled(98, false, 11 + i, 0, 2, __LINE__); + BEAST_EXPECT(followingTrue.stalled(p, false, 0, j, clog)); + expectStalled(97, true, 11 + i, 0, 97, __LINE__); + BEAST_EXPECT(followingFalse.stalled(p, false, 0, j, clog)); + expectStalled(96, false, 11 + i, 0, 3, __LINE__); // true vote changed 2 rounds ago, and peers are NOT changing, // so stalled - BEAST_EXPECT(proposingTrue.stalled(p, true, peersUnchanged)); + BEAST_EXPECT( + proposingTrue.stalled(p, true, peersUnchanged, j, clog)); + expectStalled(99, true, 1 + i, 6, 97, __LINE__); // still stalled - BEAST_EXPECT(proposingFalse.stalled(p, true, peersUnchanged)); - BEAST_EXPECT(followingTrue.stalled(p, false, peersUnchanged)); - BEAST_EXPECT(followingFalse.stalled(p, false, peersUnchanged)); + BEAST_EXPECT( + proposingFalse.stalled(p, true, peersUnchanged, j, clog)); + expectStalled(98, false, 11 + i, 6, 2, __LINE__); + BEAST_EXPECT( + followingTrue.stalled(p, false, peersUnchanged, j, clog)); + expectStalled(97, true, 11 + i, 6, 97, __LINE__); + BEAST_EXPECT( + followingFalse.stalled(p, false, peersUnchanged, j, clog)); + expectStalled(96, false, 11 + i, 6, 3, __LINE__); } for (int i = 3; i < 5; ++i) { @@ -1401,15 +1492,27 @@ public: BEAST_EXPECT(followingTrue.getOurVote() == true); BEAST_EXPECT(followingFalse.getOurVote() == false); - BEAST_EXPECT(proposingTrue.stalled(p, true, 0)); - BEAST_EXPECT(proposingFalse.stalled(p, true, 0)); - BEAST_EXPECT(followingTrue.stalled(p, false, 0)); - BEAST_EXPECT(followingFalse.stalled(p, false, 0)); + BEAST_EXPECT(proposingTrue.stalled(p, true, 0, j, clog)); + expectStalled(99, true, 1 + i, 0, 97, __LINE__); + BEAST_EXPECT(proposingFalse.stalled(p, true, 0, j, clog)); + expectStalled(98, false, 11 + i, 0, 2, __LINE__); + BEAST_EXPECT(followingTrue.stalled(p, false, 0, j, clog)); + expectStalled(97, true, 11 + i, 0, 97, __LINE__); + BEAST_EXPECT(followingFalse.stalled(p, false, 0, j, clog)); + expectStalled(96, false, 11 + i, 0, 3, __LINE__); - BEAST_EXPECT(proposingTrue.stalled(p, true, peersUnchanged)); - BEAST_EXPECT(proposingFalse.stalled(p, true, peersUnchanged)); - BEAST_EXPECT(followingTrue.stalled(p, false, peersUnchanged)); - BEAST_EXPECT(followingFalse.stalled(p, false, peersUnchanged)); + BEAST_EXPECT( + proposingTrue.stalled(p, true, peersUnchanged, j, clog)); + expectStalled(99, true, 1 + i, 6, 97, __LINE__); + BEAST_EXPECT( + proposingFalse.stalled(p, true, peersUnchanged, j, clog)); + expectStalled(98, false, 11 + i, 6, 2, __LINE__); + BEAST_EXPECT( + followingTrue.stalled(p, false, peersUnchanged, j, clog)); + expectStalled(97, true, 11 + i, 6, 97, __LINE__); + BEAST_EXPECT( + followingFalse.stalled(p, false, peersUnchanged, j, clog)); + expectStalled(96, false, 11 + i, 6, 3, __LINE__); } } } diff --git a/src/xrpld/app/consensus/RCLValidations.cpp b/src/xrpld/app/consensus/RCLValidations.cpp index a04047c78a..5305c95357 100644 --- a/src/xrpld/app/consensus/RCLValidations.cpp +++ b/src/xrpld/app/consensus/RCLValidations.cpp @@ -136,7 +136,7 @@ RCLValidationsAdaptor::acquire(LedgerHash const& hash) if (!ledger) { - JLOG(j_.debug()) + JLOG(j_.warn()) << "Need validated ledger for preferred ledger analysis " << hash; Application* pApp = &app_; diff --git a/src/xrpld/consensus/Consensus.cpp b/src/xrpld/consensus/Consensus.cpp index fb57687df0..d4edb1445c 100644 --- a/src/xrpld/consensus/Consensus.cpp +++ b/src/xrpld/consensus/Consensus.cpp @@ -139,11 +139,11 @@ checkConsensusReached( return false; } - // We only get stalled when every disputed transaction unequivocally has 80% - // (minConsensusPct) agreement, either for or against. That is: either under - // 20% or over 80% consensus (repectively "nay" or "yay"). This prevents - // manipulation by a minority of byzantine peers of which transactions make - // the cut to get into the ledger. + // We only get stalled when there are disputed transactions and all of them + // unequivocally have 80% (minConsensusPct) agreement, either for or + // against. That is: either under 20% or over 80% consensus (repectively + // "nay" or "yay"). This prevents manipulation by a minority of byzantine + // peers of which transactions make the cut to get into the ledger. if (stalled) { CLOG(clog) << "consensus stalled. "; diff --git a/src/xrpld/consensus/Consensus.h b/src/xrpld/consensus/Consensus.h index f3265cf381..df6cedccff 100644 --- a/src/xrpld/consensus/Consensus.h +++ b/src/xrpld/consensus/Consensus.h @@ -84,8 +84,8 @@ shouldCloseLedger( agree @param stalled the network appears to be stalled, where neither we nor our peers have changed their vote on any disputes in a - while. This is undesirable, and will cause us to end consensus - without 80% agreement. + while. This is undesirable, and should be rare, and will cause us to + end consensus without 80% agreement. @param parms Consensus constant parameters @param proposing whether we should count ourselves @param j journal for logging @@ -1712,15 +1712,29 @@ Consensus::haveConsensus( << ", disagree=" << disagree; ConsensusParms const& parms = adaptor_.parms(); - // Stalling is BAD + // Stalling is BAD. It means that we have a consensus on the close time, so + // peers are talking, but we have disputed transactions that peers are + // unable or unwilling to come to agreement on one way or the other. bool const stalled = haveCloseTimeConsensus_ && + !result_->disputes.empty() && std::ranges::all_of(result_->disputes, - [this, &parms](auto const& dispute) { + [this, &parms, &clog](auto const& dispute) { return dispute.second.stalled( parms, mode_.get() == ConsensusMode::proposing, - peerUnchangedCounter_); + peerUnchangedCounter_, + j_, + clog); }); + if (stalled) + { + std::stringstream ss; + ss << "Consensus detects as stalled with " << (agree + disagree) << "/" + << prevProposers_ << " proposers, and " << result_->disputes.size() + << " stalled disputed transactions."; + JLOG(j_.error()) << ss.str(); + CLOG(clog) << ss.str(); + } // Determine if we actually have consensus or not result_->state = checkConsensus( diff --git a/src/xrpld/consensus/DisputedTx.h b/src/xrpld/consensus/DisputedTx.h index 4ed31b77ca..e774c8366c 100644 --- a/src/xrpld/consensus/DisputedTx.h +++ b/src/xrpld/consensus/DisputedTx.h @@ -85,7 +85,12 @@ public: //! Are we and our peers "stalled" where we probably won't change //! our vote? bool - stalled(ConsensusParms const& p, bool proposing, int peersUnchanged) const + stalled( + ConsensusParms const& p, + bool proposing, + int peersUnchanged, + beast::Journal j, + std::unique_ptr const& clog) const { // at() can throw, but the map is built by hand to ensure all valid // values are available. @@ -123,8 +128,24 @@ public: int const weight = support / total; // Returns true if the tx has more than minCONSENSUS_PCT (80) percent // agreement. Either voting for _or_ voting against the tx. - return weight > p.minCONSENSUS_PCT || - weight < (100 - p.minCONSENSUS_PCT); + bool const stalled = + weight > p.minCONSENSUS_PCT || weight < (100 - p.minCONSENSUS_PCT); + + if (stalled) + { + // stalling is an error condition for even a single + // transaction. + std::stringstream s; + s << "Transaction " << ID() << " is stalled. We have been voting " + << (getOurVote() ? "YES" : "NO") << " for " << currentVoteCounter_ + << " rounds. Peers have not changed their votes in " + << peersUnchanged << " rounds. The transaction has " << weight + << "% support. "; + JLOG(j_.error()) << s.str(); + CLOG(clog) << s.str(); + } + + return stalled; } //! The disputed transaction. From 16c2ff97ccc5e78fe450bd92c431322de75aa94e Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Wed, 3 Sep 2025 10:19:38 -0400 Subject: [PATCH 02/24] Set version to 2.5.1 --- src/libxrpl/protocol/BuildInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libxrpl/protocol/BuildInfo.cpp b/src/libxrpl/protocol/BuildInfo.cpp index 4cb6fbfd36..c996e1e2e9 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -36,7 +36,7 @@ namespace BuildInfo { // and follow the format described at http://semver.org/ //------------------------------------------------------------------------------ // clang-format off -char const* const versionString = "2.5.0" +char const* const versionString = "2.5.1" // clang-format on #if defined(DEBUG) || defined(SANITIZER) From fbd60fc0007d95caa3fbbfcfcbbfc097c25ed502 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 11 Sep 2025 13:58:11 +0100 Subject: [PATCH 03/24] ci: Use pre-commit reusable workflow (#5772) --- .github/workflows/check-format.yml | 44 ------------------------------ .github/workflows/on-pr.yml | 9 ------ .github/workflows/pre-commit.yml | 14 ++++++++++ 3 files changed, 14 insertions(+), 53 deletions(-) delete mode 100644 .github/workflows/check-format.yml create mode 100644 .github/workflows/pre-commit.yml diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml deleted file mode 100644 index c63589017d..0000000000 --- a/.github/workflows/check-format.yml +++ /dev/null @@ -1,44 +0,0 @@ -# This workflow checks if the code is properly formatted. -name: Check format - -# This workflow can only be triggered by other workflows. -on: workflow_call - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-format - cancel-in-progress: true - -defaults: - run: - shell: bash - -jobs: - pre-commit: - runs-on: ubuntu-latest - container: ghcr.io/xrplf/ci/tools-rippled-pre-commit - steps: - - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - - name: Prepare runner - uses: XRPLF/actions/.github/actions/prepare-runner@638e0dc11ea230f91bd26622fb542116bb5254d5 - - name: Format code - run: pre-commit run --show-diff-on-failure --color=always --all-files - - name: Check for differences - env: - MESSAGE: | - One or more files did not conform to the formatting. Maybe you did - not run 'pre-commit' before committing, or your version of - 'clang-format' or 'prettier' has an incompatibility with the ones - used here (see the "Check configuration" step above). - - Run 'pre-commit run --all-files' in your repo, and then commit and - push the changes. - run: | - DIFF=$(git status --porcelain) - if [ -n "${DIFF}" ]; then - # Print the files that changed to give the contributor a hint about - # what to expect when running pre-commit on their own machine. - git status - echo "${MESSAGE}" - exit 1 - fi diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index c480cc5476..24f27d5162 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -50,12 +50,9 @@ jobs: files: | # These paths are unique to `on-pr.yml`. .github/scripts/levelization/** - .github/workflows/check-format.yml .github/workflows/check-levelization.yml .github/workflows/notify-clio.yml .github/workflows/on-pr.yml - .clang-format - .pre-commit-config.yaml # Keep the paths below in sync with those in `on-trigger.yml`. .github/actions/build-deps/** @@ -93,11 +90,6 @@ jobs: outputs: go: ${{ steps.go.outputs.go == 'true' }} - check-format: - needs: should-run - if: needs.should-run.outputs.go == 'true' - uses: ./.github/workflows/check-format.yml - check-levelization: needs: should-run if: needs.should-run.outputs.go == 'true' @@ -130,7 +122,6 @@ jobs: if: failure() || cancelled() needs: - build-test - - check-format - check-levelization runs-on: ubuntu-latest steps: diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000000..ead137308d --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,14 @@ +name: Run pre-commit hooks + +on: + pull_request: + push: + branches: [develop, release, master] + workflow_dispatch: + +jobs: + run-hooks: + uses: XRPLF/actions/.github/workflows/pre-commit.yml@af1b0f0d764cda2e5435f5ac97b240d4bd4d95d3 + with: + runs_on: ubuntu-latest + container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit" }' From e6f8bc720fdbbe63ee907f5ac429aa9cfc7e56c9 Mon Sep 17 00:00:00 2001 From: tequ Date: Thu, 11 Sep 2025 23:17:06 +0900 Subject: [PATCH 04/24] Add additional metadata to simulate response (#5754) --- src/test/rpc/Simulate_test.cpp | 104 ++++++++++++++++++++++++++++ src/xrpld/rpc/handlers/Simulate.cpp | 14 ++++ 2 files changed, 118 insertions(+) diff --git a/src/test/rpc/Simulate_test.cpp b/src/test/rpc/Simulate_test.cpp index 5b3c0d2372..0a36a8a841 100644 --- a/src/test/rpc/Simulate_test.cpp +++ b/src/test/rpc/Simulate_test.cpp @@ -131,6 +131,32 @@ class Simulate_test : public beast::unit_test::suite std::to_string(env.current()->txCount())); } + void + testTxJsonMetadataField( + jtx::Env& env, + Json::Value const& tx, + std::function const& validate, + Json::Value const& expectedMetadataKey, + bool testSerialized = true) + { + env.close(); + + Json::Value params; + params[jss::tx_json] = tx; + validate( + env.rpc("json", "simulate", to_string(params)), + tx, + expectedMetadataKey); + validate(env.rpc("simulate", to_string(tx)), tx, expectedMetadataKey); + + BEAST_EXPECTS( + env.current()->txCount() == 0, + std::to_string(env.current()->txCount())); + } + Json::Value getJsonMetadata(Json::Value txResult) const { @@ -1186,6 +1212,83 @@ class Simulate_test : public beast::unit_test::suite } } + void + testSuccessfulTransactionAdditionalMetadata() + { + testcase("Successful transaction with additional metadata"); + + using namespace jtx; + Env env{*this, envconfig([&](std::unique_ptr cfg) { + cfg->NETWORK_ID = 1025; + return cfg; + })}; + + Account const alice("alice"); + + env.fund(XRP(10000), alice); + env.close(); + + { + auto validateOutput = [&](Json::Value const& resp, + Json::Value const& tx, + Json::Value const& expectedMetadataKey) { + auto result = resp[jss::result]; + + BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS"); + BEAST_EXPECT(result[jss::engine_result_code] == 0); + BEAST_EXPECT( + result[jss::engine_result_message] == + "The simulated transaction would have been applied."); + + if (BEAST_EXPECT( + result.isMember(jss::meta) || + result.isMember(jss::meta_blob))) + { + Json::Value const metadata = getJsonMetadata(result); + + BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0); + BEAST_EXPECT( + metadata[sfTransactionResult.jsonName] == "tesSUCCESS"); + BEAST_EXPECT( + metadata.isMember(expectedMetadataKey.asString())); + } + }; + + { + Json::Value tx; + tx[jss::Account] = env.master.human(); + tx[jss::TransactionType] = jss::Payment; + tx[sfDestination] = alice.human(); + tx[sfAmount] = "100"; + + // test delivered amount + testTxJsonMetadataField( + env, tx, validateOutput, jss::delivered_amount); + } + + { + Json::Value tx; + tx[jss::Account] = env.master.human(); + tx[jss::TransactionType] = jss::NFTokenMint; + tx[sfNFTokenTaxon] = 1; + + // test nft synthetic + testTxJsonMetadataField( + env, tx, validateOutput, jss::nftoken_id); + } + + { + Json::Value tx; + tx[jss::Account] = env.master.human(); + tx[jss::TransactionType] = jss::MPTokenIssuanceCreate; + + // test mpt issuance id + testTxJsonMetadataField( + env, tx, validateOutput, jss::mpt_issuance_id); + } + } + } + public: void run() override @@ -1202,6 +1305,7 @@ public: testMultisignedBadPubKey(); testDeleteExpiredCredentials(); testSuccessfulTransactionNetworkID(); + testSuccessfulTransactionAdditionalMetadata(); } }; diff --git a/src/xrpld/rpc/handlers/Simulate.cpp b/src/xrpld/rpc/handlers/Simulate.cpp index 3c175883c5..092b0b4562 100644 --- a/src/xrpld/rpc/handlers/Simulate.cpp +++ b/src/xrpld/rpc/handlers/Simulate.cpp @@ -24,10 +24,13 @@ #include #include #include +#include #include +#include #include #include +#include #include #include #include @@ -272,6 +275,17 @@ simulateTxn(RPC::JsonContext& context, std::shared_ptr transaction) else { jvResult[jss::meta] = result.metadata->getJson(JsonOptions::none); + RPC::insertDeliveredAmount( + jvResult[jss::meta], + view, + transaction->getSTransaction(), + *result.metadata); + RPC::insertNFTSyntheticInJson( + jvResult, transaction->getSTransaction(), *result.metadata); + RPC::insertMPTokenIssuanceID( + jvResult[jss::meta], + transaction->getSTransaction(), + *result.metadata); } } From 6fe0599cc26db01e4912b133a70b9d252281a568 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Thu, 11 Sep 2025 10:49:26 -0400 Subject: [PATCH 05/24] refactor: clean up `CTID.h` (#5681) --- src/xrpld/rpc/CTID.h | 85 +++++++++++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 21 deletions(-) diff --git a/src/xrpld/rpc/CTID.h b/src/xrpld/rpc/CTID.h index be531c536a..0e2b7e0d65 100644 --- a/src/xrpld/rpc/CTID.h +++ b/src/xrpld/rpc/CTID.h @@ -39,53 +39,96 @@ namespace RPC { // The Concise Transaction ID provides a way to identify a transaction // that includes which network the transaction was submitted to. +/** + * @brief Encodes ledger sequence, transaction index, and network ID into a CTID + * string. + * + * @param ledgerSeq Ledger sequence number (max 0x0FFF'FFFF). + * @param txnIndex Transaction index within the ledger (max 0xFFFF). + * @param networkID Network identifier (max 0xFFFF). + * @return Optional CTID string in uppercase hexadecimal, or std::nullopt if + * inputs are out of range. + */ inline std::optional encodeCTID(uint32_t ledgerSeq, uint32_t txnIndex, uint32_t networkID) noexcept { - if (ledgerSeq > 0x0FFF'FFFF || txnIndex > 0xFFFF || networkID > 0xFFFF) - return {}; + constexpr uint32_t maxLedgerSeq = 0x0FFF'FFFF; + constexpr uint32_t maxTxnIndex = 0xFFFF; + constexpr uint32_t maxNetworkID = 0xFFFF; + + if (ledgerSeq > maxLedgerSeq || txnIndex > maxTxnIndex || + networkID > maxNetworkID) + return std::nullopt; uint64_t ctidValue = - ((0xC000'0000ULL + static_cast(ledgerSeq)) << 32) + - (static_cast(txnIndex) << 16) + networkID; + ((0xC000'0000ULL + static_cast(ledgerSeq)) << 32) | + ((static_cast(txnIndex) << 16) | networkID); std::stringstream buffer; buffer << std::hex << std::uppercase << std::setfill('0') << std::setw(16) << ctidValue; - return {buffer.str()}; + return buffer.str(); } +/** + * @brief Decodes a CTID string or integer into its component parts. + * + * @tparam T Type of the CTID input (string, string_view, char*, integral). + * @param ctid CTID value to decode. + * @return Optional tuple of (ledgerSeq, txnIndex, networkID), or std::nullopt + * if invalid. + */ template inline std::optional> decodeCTID(T const ctid) noexcept { - uint64_t ctidValue{0}; + uint64_t ctidValue = 0; + if constexpr ( - std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v) + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v) { std::string const ctidString(ctid); - if (ctidString.length() != 16) - return {}; + if (ctidString.size() != 16) + return std::nullopt; - if (!boost::regex_match(ctidString, boost::regex("^[0-9A-Fa-f]+$"))) - return {}; + static boost::regex const hexRegex("^[0-9A-Fa-f]{16}$"); + if (!boost::regex_match(ctidString, hexRegex)) + return std::nullopt; - ctidValue = std::stoull(ctidString, nullptr, 16); + try + { + ctidValue = std::stoull(ctidString, nullptr, 16); + } + // LCOV_EXCL_START + catch (...) + { + // should be impossible to hit given the length/regex check + return std::nullopt; + } + // LCOV_EXCL_STOP } else if constexpr (std::is_integral_v) - ctidValue = ctid; + { + ctidValue = static_cast(ctid); + } else - return {}; + { + return std::nullopt; + } - if ((ctidValue & 0xF000'0000'0000'0000ULL) != 0xC000'0000'0000'0000ULL) - return {}; + // Validate CTID prefix. + constexpr uint64_t ctidPrefixMask = 0xF000'0000'0000'0000ULL; + constexpr uint64_t ctidPrefix = 0xC000'0000'0000'0000ULL; + if ((ctidValue & ctidPrefixMask) != ctidPrefix) + return std::nullopt; - uint32_t ledger_seq = (ctidValue >> 32) & 0xFFFF'FFFUL; - uint16_t txn_index = (ctidValue >> 16) & 0xFFFFU; - uint16_t network_id = ctidValue & 0xFFFFU; - return {{ledger_seq, txn_index, network_id}}; + uint32_t ledgerSeq = static_cast((ctidValue >> 32) & 0x0FFF'FFFF); + uint16_t txnIndex = static_cast((ctidValue >> 16) & 0xFFFF); + uint16_t networkID = static_cast(ctidValue & 0xFFFF); + + return std::make_tuple(ledgerSeq, txnIndex, networkID); } } // namespace RPC From f69ad4eff66d26b8c57e6e1649850652abfbb3b9 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 11 Sep 2025 16:42:27 +0100 Subject: [PATCH 06/24] docs: Add remote to `conan lock create` command (#5770) * docs: Add remote to `conan lock create` command * Document error resolution for conan package issues * Update BUILD.md * Add more info about lockfiles --- BUILD.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index 6b1594bb5e..fd7a0b855d 100644 --- a/BUILD.md +++ b/BUILD.md @@ -132,7 +132,7 @@ higher index than the default Conan Center remote, so it is consulted first. You can do this by running: ```bash -conan remote add --index 0 xrplf "https://conan.ripplex.io" +conan remote add --index 0 xrplf https://conan.ripplex.io ``` Alternatively, you can pull the patched recipes into the repository and use them @@ -479,12 +479,24 @@ It is implicitly used when running `conan` commands, you don't need to specify i You have to update this file every time you add a new dependency or change a revision or version of an existing dependency. -To do that, run the following command in the repository root: +> [!NOTE] +> Conan uses local cache by default when creating a lockfile. +> +> To ensure, that lockfile creation works the same way on all developer machines, you should clear the local cache before creating a new lockfile. + +To create a new lockfile, run the following commands in the repository root: ```bash +conan remove '*' --confirm +rm conan.lock +# This ensure that xrplf remote is the first to be consulted +conan remote add --force --index 0 xrplf https://conan.ripplex.io conan lock create . -o '&:jemalloc=True' -o '&:rocksdb=True' ``` +> [!NOTE] +> If some dependencies are exclusive for some OS, you may need to run the last command for them adding `--profile:all `. + ## Coverage report The coverage report is intended for developers using compilers GCC @@ -586,6 +598,11 @@ After any updates or changes to dependencies, you may need to do the following: 4. [Regenerate lockfile](#conan-lockfile). 5. Re-run [conan install](#build-and-test). +#### ERROR: Package not resolved + +If you're seeing an error like `ERROR: Package 'snappy/1.1.10' not resolved: Unable to find 'snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1756234314.246' in remotes.`, +please add `xrplf` remote or re-run `conan export` for [patched recipes](#patched-recipes). + ### `protobuf/port_def.inc` file not found If `cmake --build .` results in an error due to a missing a protobuf file, then From 9bd1ce436aaed263190a422ae555ed49eba921e5 Mon Sep 17 00:00:00 2001 From: Jingchen Date: Fri, 12 Sep 2025 16:13:27 +0100 Subject: [PATCH 07/24] Fix code coverage error (#5765) * Fix the issue where COVERAGE_CXX_COMPILER_FLAGS is never used --- cmake/CodeCoverage.cmake | 112 ++++++++++++++++++----------------- cmake/RippledCov.cmake | 2 + cmake/RippledInterface.cmake | 4 -- 3 files changed, 61 insertions(+), 57 deletions(-) diff --git a/cmake/CodeCoverage.cmake b/cmake/CodeCoverage.cmake index ec601de453..c2b66c9cac 100644 --- a/cmake/CodeCoverage.cmake +++ b/cmake/CodeCoverage.cmake @@ -104,6 +104,11 @@ # 2025-08-28, Bronek Kozicki # - fix "At least one COMMAND must be given" CMake warning from policy CMP0175 # +# 2025-09-03, Jingchen Wu +# - remove the unused function append_coverage_compiler_flags and append_coverage_compiler_flags_to_target +# - add a new function add_code_coverage_to_target +# - remove some unused code +# # USAGE: # # 1. Copy this file into your cmake modules path. @@ -112,10 +117,8 @@ # using a CMake option() to enable it just optionally): # include(CodeCoverage) # -# 3. Append necessary compiler flags for all supported source files: -# append_coverage_compiler_flags() -# Or for specific target: -# append_coverage_compiler_flags_to_target(YOUR_TARGET_NAME) +# 3. Append necessary compiler flags and linker flags for all supported source files: +# add_code_coverage_to_target( ) # # 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og # @@ -204,67 +207,69 @@ endforeach() set(COVERAGE_COMPILER_FLAGS "-g --coverage" CACHE INTERNAL "") + +set(COVERAGE_CXX_COMPILER_FLAGS "") +set(COVERAGE_C_COMPILER_FLAGS "") +set(COVERAGE_CXX_LINKER_FLAGS "") +set(COVERAGE_C_LINKER_FLAGS "") + if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") include(CheckCXXCompilerFlag) include(CheckCCompilerFlag) + include(CheckLinkerFlag) + + set(COVERAGE_CXX_COMPILER_FLAGS ${COVERAGE_COMPILER_FLAGS}) + set(COVERAGE_C_COMPILER_FLAGS ${COVERAGE_COMPILER_FLAGS}) + set(COVERAGE_CXX_LINKER_FLAGS ${COVERAGE_COMPILER_FLAGS}) + set(COVERAGE_C_LINKER_FLAGS ${COVERAGE_COMPILER_FLAGS}) check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path) if(HAVE_cxx_fprofile_abs_path) - set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_CXX_COMPILER_FLAGS} -fprofile-abs-path") endif() check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path) if(HAVE_c_fprofile_abs_path) - set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_C_COMPILER_FLAGS} -fprofile-abs-path") + endif() + + check_linker_flag(CXX -fprofile-abs-path HAVE_cxx_linker_fprofile_abs_path) + if(HAVE_cxx_linker_fprofile_abs_path) + set(COVERAGE_CXX_LINKER_FLAGS "${COVERAGE_CXX_LINKER_FLAGS} -fprofile-abs-path") + endif() + + check_linker_flag(C -fprofile-abs-path HAVE_c_linker_fprofile_abs_path) + if(HAVE_c_linker_fprofile_abs_path) + set(COVERAGE_C_LINKER_FLAGS "${COVERAGE_C_LINKER_FLAGS} -fprofile-abs-path") endif() check_cxx_compiler_flag(-fprofile-update=atomic HAVE_cxx_fprofile_update) if(HAVE_cxx_fprofile_update) - set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-update=atomic") + set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_CXX_COMPILER_FLAGS} -fprofile-update=atomic") endif() check_c_compiler_flag(-fprofile-update=atomic HAVE_c_fprofile_update) if(HAVE_c_fprofile_update) - set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-update=atomic") + set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_C_COMPILER_FLAGS} -fprofile-update=atomic") endif() -endif() -set(CMAKE_Fortran_FLAGS_COVERAGE - ${COVERAGE_COMPILER_FLAGS} - CACHE STRING "Flags used by the Fortran compiler during coverage builds." - FORCE ) -set(CMAKE_CXX_FLAGS_COVERAGE - ${COVERAGE_COMPILER_FLAGS} - CACHE STRING "Flags used by the C++ compiler during coverage builds." - FORCE ) -set(CMAKE_C_FLAGS_COVERAGE - ${COVERAGE_COMPILER_FLAGS} - CACHE STRING "Flags used by the C compiler during coverage builds." - FORCE ) -set(CMAKE_EXE_LINKER_FLAGS_COVERAGE - "" - CACHE STRING "Flags used for linking binaries during coverage builds." - FORCE ) -set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE - "" - CACHE STRING "Flags used by the shared libraries linker during coverage builds." - FORCE ) -mark_as_advanced( - CMAKE_Fortran_FLAGS_COVERAGE - CMAKE_CXX_FLAGS_COVERAGE - CMAKE_C_FLAGS_COVERAGE - CMAKE_EXE_LINKER_FLAGS_COVERAGE - CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + check_linker_flag(CXX -fprofile-update=atomic HAVE_cxx_linker_fprofile_update) + if(HAVE_cxx_linker_fprofile_update) + set(COVERAGE_CXX_LINKER_FLAGS "${COVERAGE_CXX_LINKER_FLAGS} -fprofile-update=atomic") + endif() + + check_linker_flag(C -fprofile-update=atomic HAVE_c_linker_fprofile_update) + if(HAVE_c_linker_fprofile_update) + set(COVERAGE_C_LINKER_FLAGS "${COVERAGE_C_LINKER_FLAGS} -fprofile-update=atomic") + endif() + +endif() get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) -if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") - link_libraries(gcov) -endif() - # Defines a target for running and collection code coverage information # Builds dependencies, runs the given executable and outputs reports. # NOTE! The executable should always have a ZERO as exit code otherwise @@ -454,18 +459,19 @@ function(setup_target_for_coverage_gcovr) ) endfunction() # setup_target_for_coverage_gcovr -function(append_coverage_compiler_flags) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) - set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) - message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") -endfunction() # append_coverage_compiler_flags +function(add_code_coverage_to_target name scope) + separate_arguments(COVERAGE_CXX_COMPILER_FLAGS NATIVE_COMMAND "${COVERAGE_CXX_COMPILER_FLAGS}") + separate_arguments(COVERAGE_C_COMPILER_FLAGS NATIVE_COMMAND "${COVERAGE_C_COMPILER_FLAGS}") + separate_arguments(COVERAGE_CXX_LINKER_FLAGS NATIVE_COMMAND "${COVERAGE_CXX_LINKER_FLAGS}") + separate_arguments(COVERAGE_C_LINKER_FLAGS NATIVE_COMMAND "${COVERAGE_C_LINKER_FLAGS}") -# Setup coverage for specific library -function(append_coverage_compiler_flags_to_target name) - separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}") - target_compile_options(${name} PRIVATE ${_flag_list}) - if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") - target_link_libraries(${name} PRIVATE gcov) - endif() -endfunction() + # Add compiler options to the target + target_compile_options(${name} ${scope} + $<$:${COVERAGE_CXX_COMPILER_FLAGS}> + $<$:${COVERAGE_C_COMPILER_FLAGS}>) + + target_link_libraries (${name} ${scope} + $<$:${COVERAGE_CXX_LINKER_FLAGS} gcov> + $<$:${COVERAGE_C_LINKER_FLAGS} gcov> + ) +endfunction() # add_code_coverage_to_target diff --git a/cmake/RippledCov.cmake b/cmake/RippledCov.cmake index 3c48bb1c14..847915a51a 100644 --- a/cmake/RippledCov.cmake +++ b/cmake/RippledCov.cmake @@ -36,3 +36,5 @@ setup_target_for_coverage_gcovr( EXCLUDE "src/test" "include/xrpl/beast/test" "include/xrpl/beast/unit_test" "${CMAKE_BINARY_DIR}/pb-xrpl.libpb" DEPENDENCIES rippled ) + +add_code_coverage_to_target(opts INTERFACE) diff --git a/cmake/RippledInterface.cmake b/cmake/RippledInterface.cmake index 85e2717271..375338c788 100644 --- a/cmake/RippledInterface.cmake +++ b/cmake/RippledInterface.cmake @@ -28,15 +28,11 @@ target_compile_options (opts $<$,$>:-Wsuggest-override> $<$:-Wno-maybe-uninitialized> $<$:-fno-omit-frame-pointer> - $<$,$>:-g --coverage -fprofile-abs-path> - $<$,$>:-g --coverage> $<$:-pg> $<$,$>:-p>) target_link_libraries (opts INTERFACE - $<$,$>:-g --coverage -fprofile-abs-path> - $<$,$>:-g --coverage> $<$:-pg> $<$,$>:-p>) From 406c26cc72a7a8bf4a27baa02d74da81be977dd0 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 12 Sep 2025 18:09:42 +0100 Subject: [PATCH 08/24] ci: Fix conan secrets in `upload-conan-deps` (#5785) - Accounts for some variables that were changed and missed when the reusable workflow was removed. --- .github/workflows/upload-conan-deps.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 5af72a9e41..6b94815284 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -34,6 +34,10 @@ on: - conanfile.py - conan.lock +env: + CONAN_REMOTE_NAME: xrplf + CONAN_REMOTE_URL: https://conan.ripplex.io + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -67,6 +71,9 @@ jobs: - name: Setup Conan uses: ./.github/actions/setup-conan + with: + conan_remote_name: ${{ env.CONAN_REMOTE_NAME }} + conan_remote_url: ${{ env.CONAN_REMOTE_URL }} - name: Build dependencies uses: ./.github/actions/build-deps @@ -75,10 +82,10 @@ jobs: build_type: ${{ matrix.build_type }} force_build: ${{ github.event_name == 'schedule' || github.event.inputs.force_source_build == 'true' }} - - name: Login to Conan + - name: Log into Conan remote if: github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' - run: conan remote login -p ${{ secrets.CONAN_PASSWORD }} ${{ inputs.conan_remote_name }} ${{ secrets.CONAN_USERNAME }} + run: conan remote login ${{ env.CONAN_REMOTE_NAME }} "${{ secrets.CONAN_REMOTE_USERNAME }}" --password "${{ secrets.CONAN_REMOTE_PASSWORD }}" - name: Upload Conan packages if: github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' && github.event_name != 'schedule' - run: conan upload "*" -r=${{ inputs.conan_remote_name }} --confirm ${{ github.event.inputs.force_upload == 'true' && '--force' || '' }} + run: conan upload "*" -r=${{ env.CONAN_REMOTE_NAME }} --confirm ${{ github.event.inputs.force_upload == 'true' && '--force' || '' }} From bd182c0a3e0c153ef15ce6721db57c40e2369ccd Mon Sep 17 00:00:00 2001 From: Jingchen Date: Mon, 15 Sep 2025 14:51:19 +0100 Subject: [PATCH 09/24] fix: Skip processing transaction batch if the batch is empty (#5670) Avoids an assertion failure in NetworkOPsImp::apply in the unlikely event that all incoming transactions are invalid. --- src/test/app/NetworkOPs_test.cpp | 80 ++++++++++++++++++++++++++++ src/xrpld/app/misc/NetworkOPs.cpp | 5 ++ src/xrpld/overlay/detail/PeerImp.cpp | 7 ++- 3 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 src/test/app/NetworkOPs_test.cpp diff --git a/src/test/app/NetworkOPs_test.cpp b/src/test/app/NetworkOPs_test.cpp new file mode 100644 index 0000000000..edea55105b --- /dev/null +++ b/src/test/app/NetworkOPs_test.cpp @@ -0,0 +1,80 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2020 Dev Null Productions + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include + +#include + +namespace ripple { +namespace test { + +class NetworkOPs_test : public beast::unit_test::suite +{ +public: + void + run() override + { + testAllBadHeldTransactions(); + } + + void + testAllBadHeldTransactions() + { + // All trasactions are already marked as SF_BAD, and we should be able + // to handle the case properly without an assertion failure + testcase("No valid transactions in batch"); + + std::string logs; + + { + using namespace jtx; + auto const alice = Account{"alice"}; + Env env{ + *this, + envconfig(), + std::make_unique(&logs), + beast::severities::kAll}; + env.memoize(env.master); + env.memoize(alice); + + auto const jtx = env.jt(ticket::create(alice, 1), seq(1), fee(10)); + + auto transacionId = jtx.stx->getTransactionID(); + env.app().getHashRouter().setFlags( + transacionId, HashRouterFlags::HELD); + + env(jtx, json(jss::Sequence, 1), ter(terNO_ACCOUNT)); + + env.app().getHashRouter().setFlags( + transacionId, HashRouterFlags::BAD); + + env.close(); + } + + BEAST_EXPECT( + logs.find("No transaction to process!") != std::string::npos); + } +}; + +BEAST_DEFINE_TESTSUITE(NetworkOPs, app, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 403090c390..b9069442f8 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -1452,6 +1452,11 @@ NetworkOPsImp::processTransactionSet(CanonicalTXSet const& set) for (auto& t : transactions) mTransactions.push_back(std::move(t)); } + if (mTransactions.empty()) + { + JLOG(m_journal.debug()) << "No transaction to process!"; + return; + } doTransactionSyncBatch(lock, [&](std::unique_lock const&) { XRPL_ASSERT( diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 69f25e1eb4..2cd9432eb8 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -2880,6 +2880,9 @@ PeerImp::checkTransaction( (stx->getFieldU32(sfLastLedgerSequence) < app_.getLedgerMaster().getValidLedgerIndex())) { + JLOG(p_journal_.info()) + << "Marking transaction " << stx->getTransactionID() + << "as BAD because it's expired"; app_.getHashRouter().setFlags( stx->getTransactionID(), HashRouterFlags::BAD); charge(Resource::feeUselessData, "expired tx"); @@ -2936,7 +2939,7 @@ PeerImp::checkTransaction( { if (!validReason.empty()) { - JLOG(p_journal_.trace()) + JLOG(p_journal_.debug()) << "Exception checking transaction: " << validReason; } @@ -2963,7 +2966,7 @@ PeerImp::checkTransaction( { if (!reason.empty()) { - JLOG(p_journal_.trace()) + JLOG(p_journal_.debug()) << "Exception checking transaction: " << reason; } app_.getHashRouter().setFlags( From 37c377a1b6ab18055bd3c8b954c5de97aaee683b Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 15 Sep 2025 16:48:47 +0200 Subject: [PATCH 10/24] Fix: EscrowTokenV1 (#5571) * resolves an accounting inconsistency in MPT escrows where transfer fees were not properly handled when unlocking escrowed tokens. --- include/xrpl/protocol/detail/features.macro | 1 + src/test/app/EscrowToken_test.cpp | 68 +++++++++++++++++++++ src/xrpld/app/tx/detail/Escrow.cpp | 9 ++- src/xrpld/ledger/View.h | 3 +- src/xrpld/ledger/detail/View.cpp | 42 ++++++++++--- 5 files changed, 113 insertions(+), 10 deletions(-) diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 9aacbbe3d9..f04e9f3641 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -32,6 +32,7 @@ // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. +XRPL_FIX (TokenEscrowV1, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (DelegateV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (PriceOracleOrder, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo) diff --git a/src/test/app/EscrowToken_test.cpp b/src/test/app/EscrowToken_test.cpp index e81064c825..28c9a5b167 100644 --- a/src/test/app/EscrowToken_test.cpp +++ b/src/test/app/EscrowToken_test.cpp @@ -3501,6 +3501,10 @@ struct EscrowToken_test : public beast::unit_test::suite BEAST_EXPECT( transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 125); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 125); + BEAST_EXPECT(env.balance(gw, MPT) == MPT(20'000)); + // bob can finish escrow env(escrow::finish(bob, alice, seq1), escrow::condition(escrow::cb1), @@ -3510,6 +3514,15 @@ struct EscrowToken_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(alice, MPT) == preAlice - delta); BEAST_EXPECT(env.balance(bob, MPT) == MPT(10'100)); + + auto const escrowedWithFix = + env.current()->rules().enabled(fixTokenEscrowV1) ? 0 : 25; + auto const outstandingWithFix = + env.current()->rules().enabled(fixTokenEscrowV1) ? MPT(19'975) + : MPT(20'000); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == escrowedWithFix); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == escrowedWithFix); + BEAST_EXPECT(env.balance(gw, MPT) == outstandingWithFix); } // test locked rate: cancel @@ -3554,6 +3567,60 @@ struct EscrowToken_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(alice, MPT) == preAlice); BEAST_EXPECT(env.balance(bob, MPT) == preBob); + BEAST_EXPECT(env.balance(gw, MPT) == MPT(20'000)); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0); + } + + // test locked rate: issuer is destination + { + Env env{*this, features}; + auto const baseFee = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + MPTTester mptGw(env, gw, {.holders = {alice, bob}}); + mptGw.create( + {.transferFee = 25000, + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanEscrow | tfMPTCanTransfer}); + mptGw.authorize({.account = alice}); + mptGw.authorize({.account = bob}); + auto const MPT = mptGw["MPT"]; + env(pay(gw, alice, MPT(10'000))); + env(pay(gw, bob, MPT(10'000))); + env.close(); + + // alice can create escrow w/ xfer rate + auto const preAlice = env.balance(alice, MPT); + auto const seq1 = env.seq(alice); + auto const delta = MPT(125); + env(escrow::create(alice, gw, MPT(125)), + escrow::condition(escrow::cb1), + escrow::finish_time(env.now() + 1s), + fee(baseFee * 150)); + env.close(); + auto const transferRate = escrow::rate(env, alice, seq1); + BEAST_EXPECT( + transferRate.value == std::uint32_t(1'000'000'000 * 1.25)); + + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 125); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 125); + BEAST_EXPECT(env.balance(gw, MPT) == MPT(20'000)); + + // bob can finish escrow + env(escrow::finish(gw, alice, seq1), + escrow::condition(escrow::cb1), + escrow::fulfillment(escrow::fb1), + fee(baseFee * 150)); + env.close(); + + BEAST_EXPECT(env.balance(alice, MPT) == preAlice - delta); + BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0); + BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0); + BEAST_EXPECT(env.balance(gw, MPT) == MPT(19'875)); } } @@ -3878,6 +3945,7 @@ public: FeatureBitset const all{testable_amendments()}; testIOUWithFeats(all); testMPTWithFeats(all); + testMPTWithFeats(all - fixTokenEscrowV1); } }; diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index dd0ffac778..3b05aa0007 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -1007,8 +1007,13 @@ escrowUnlockApplyHelper( // compute balance to transfer finalAmt = amount.value() - xferFee; } - - return rippleUnlockEscrowMPT(view, sender, receiver, finalAmt, journal); + return rippleUnlockEscrowMPT( + view, + sender, + receiver, + finalAmt, + view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt, + journal); } TER diff --git a/src/xrpld/ledger/View.h b/src/xrpld/ledger/View.h index cfd3599f78..faad633e00 100644 --- a/src/xrpld/ledger/View.h +++ b/src/xrpld/ledger/View.h @@ -719,7 +719,8 @@ rippleUnlockEscrowMPT( ApplyView& view, AccountID const& uGrantorID, AccountID const& uGranteeID, - STAmount const& saAmount, + STAmount const& netAmount, + STAmount const& grossAmount, beast::Journal j); /** Calls static accountSendIOU if saAmount represents Issue. diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index 708d5b29f7..473efa58fb 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -3006,11 +3006,17 @@ rippleUnlockEscrowMPT( ApplyView& view, AccountID const& sender, AccountID const& receiver, - STAmount const& amount, + STAmount const& netAmount, + STAmount const& grossAmount, beast::Journal j) { - auto const issuer = amount.getIssuer(); - auto const mptIssue = amount.get(); + if (!view.rules().enabled(fixTokenEscrowV1)) + XRPL_ASSERT( + netAmount == grossAmount, + "ripple::rippleUnlockEscrowMPT : netAmount == grossAmount"); + + auto const& issuer = netAmount.getIssuer(); + auto const& mptIssue = netAmount.get(); auto const mptID = keylet::mptIssuance(mptIssue.getMptID()); auto sleIssuance = view.peek(mptID); if (!sleIssuance) @@ -3031,7 +3037,7 @@ rippleUnlockEscrowMPT( } // LCOV_EXCL_STOP auto const locked = sleIssuance->getFieldU64(sfLockedAmount); - auto const redeem = amount.mpt().value(); + auto const redeem = grossAmount.mpt().value(); // Underflow check for subtraction if (!canSubtract( @@ -3064,7 +3070,7 @@ rippleUnlockEscrowMPT( } // LCOV_EXCL_STOP auto current = sle->getFieldU64(sfMPTAmount); - auto delta = amount.mpt().value(); + auto delta = netAmount.mpt().value(); // Overflow check for addition if (!canAdd(STAmount(mptIssue, current), STAmount(mptIssue, delta))) @@ -3082,7 +3088,7 @@ rippleUnlockEscrowMPT( { // Decrease the Issuance OutstandingAmount auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount); - auto const redeem = amount.mpt().value(); + auto const redeem = netAmount.mpt().value(); // Underflow check for subtraction if (!canSubtract( @@ -3126,7 +3132,7 @@ rippleUnlockEscrowMPT( } // LCOV_EXCL_STOP auto const locked = sle->getFieldU64(sfLockedAmount); - auto const delta = amount.mpt().value(); + auto const delta = grossAmount.mpt().value(); // Underflow check for subtraction if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, delta))) @@ -3144,6 +3150,28 @@ rippleUnlockEscrowMPT( sle->setFieldU64(sfLockedAmount, newLocked); view.update(sle); } + + // Note: The gross amount is the amount that was locked, the net + // amount is the amount that is being unlocked. The difference is the fee + // that was charged for the transfer. If this difference is greater than + // zero, we need to update the outstanding amount. + auto const diff = grossAmount.mpt().value() - netAmount.mpt().value(); + if (diff != 0) + { + auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount); + // Underflow check for subtraction + if (!canSubtract( + STAmount(mptIssue, outstanding), STAmount(mptIssue, diff))) + { // LCOV_EXCL_START + JLOG(j.error()) + << "rippleUnlockEscrowMPT: insufficient outstanding amount for " + << mptIssue.getMptID() << ": " << outstanding << " < " << diff; + return tecINTERNAL; + } // LCOV_EXCL_STOP + + sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff); + view.update(sleIssuance); + } return tesSUCCESS; } From 4caebfbd0eed74283705f4a2723761bd345a56da Mon Sep 17 00:00:00 2001 From: Bart Date: Mon, 15 Sep 2025 12:26:08 -0400 Subject: [PATCH 11/24] refactor: Wrap GitHub CI conditionals in curly braces (#5796) This change wraps all GitHub conditionals in `${{ .. }}`, both for consistency and to reduce unexpected failures, because it was previously noticed that not all conditionals work without those curly braces. --- .github/workflows/on-pr.yml | 6 +++--- .github/workflows/upload-conan-deps.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 24f27d5162..23f65c14cb 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -92,12 +92,12 @@ jobs: check-levelization: needs: should-run - if: needs.should-run.outputs.go == 'true' + if: ${{ needs.should-run.outputs.go == 'true' }} uses: ./.github/workflows/check-levelization.yml build-test: needs: should-run - if: needs.should-run.outputs.go == 'true' + if: ${{ needs.should-run.outputs.go == 'true' }} uses: ./.github/workflows/build-test.yml strategy: matrix: @@ -111,7 +111,7 @@ jobs: needs: - should-run - build-test - if: needs.should-run.outputs.go == 'true' + if: ${{ needs.should-run.outputs.go == 'true' }} uses: ./.github/workflows/notify-clio.yml secrets: clio_notify_token: ${{ secrets.CLIO_NOTIFY_TOKEN }} diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 6b94815284..c52b3c89d3 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -83,9 +83,9 @@ jobs: force_build: ${{ github.event_name == 'schedule' || github.event.inputs.force_source_build == 'true' }} - name: Log into Conan remote - if: github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' + if: ${{ github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' }} run: conan remote login ${{ env.CONAN_REMOTE_NAME }} "${{ secrets.CONAN_REMOTE_USERNAME }}" --password "${{ secrets.CONAN_REMOTE_PASSWORD }}" - name: Upload Conan packages - if: github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' && github.event_name != 'schedule' + if: ${{ github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' && github.event_name != 'schedule' }} run: conan upload "*" -r=${{ env.CONAN_REMOTE_NAME }} --confirm ${{ github.event.inputs.force_upload == 'true' && '--force' || '' }} From 3e4e9a2ddce8b4c1fc536574cc6d4ab3dcbd6a23 Mon Sep 17 00:00:00 2001 From: Bart Date: Mon, 15 Sep 2025 13:28:47 -0400 Subject: [PATCH 12/24] Only notify clio for PRs targeting the release and master branches (#5794) Clio should only be notified when releases are about to be made, instead of for all PR, so this change only notifies Clio when a PR targets the release or master branch. --- .github/workflows/on-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 23f65c14cb..9befd31e71 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -111,7 +111,7 @@ jobs: needs: - should-run - build-test - if: ${{ needs.should-run.outputs.go == 'true' }} + if: ${{ needs.should-run.outputs.go == 'true' && contains(fromJSON('["release", "master"]'), github.ref_name) }} uses: ./.github/workflows/notify-clio.yml secrets: clio_notify_token: ${{ secrets.CLIO_NOTIFY_TOKEN }} From ccb9f1e42d13bf83c9c953fb562938e3734a699e Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Mon, 15 Sep 2025 15:42:36 -0400 Subject: [PATCH 13/24] Support DynamicMPT XLS-94d (#5705) * extends the functionality of the MPTokenIssuanceSet transaction, allowing the issuer to update fields or flags that were explicitly marked as mutable during creation. --- include/xrpl/protocol/LedgerFormats.h | 9 + include/xrpl/protocol/TxFlags.h | 33 + include/xrpl/protocol/detail/features.macro | 1 + .../xrpl/protocol/detail/ledger_entries.macro | 1 + include/xrpl/protocol/detail/sfields.macro | 1 + .../xrpl/protocol/detail/transactions.macro | 4 + src/test/app/MPToken_test.cpp | 920 +++++++++++++++++- src/test/jtx/impl/mpt.cpp | 93 +- src/test/jtx/mpt.h | 16 + .../app/tx/detail/MPTokenIssuanceCreate.cpp | 12 + .../app/tx/detail/MPTokenIssuanceCreate.h | 1 + .../app/tx/detail/MPTokenIssuanceSet.cpp | 150 ++- 12 files changed, 1215 insertions(+), 26 deletions(-) diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index e3efe8fec2..711754df94 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -188,6 +188,15 @@ enum LedgerSpecificFlags { lsfMPTCanTransfer = 0x00000020, lsfMPTCanClawback = 0x00000040, + lsfMPTCanMutateCanLock = 0x00000002, + lsfMPTCanMutateRequireAuth = 0x00000004, + lsfMPTCanMutateCanEscrow = 0x00000008, + lsfMPTCanMutateCanTrade = 0x00000010, + lsfMPTCanMutateCanTransfer = 0x00000020, + lsfMPTCanMutateCanClawback = 0x00000040, + lsfMPTCanMutateMetadata = 0x00010000, + lsfMPTCanMutateTransferFee = 0x00020000, + // ltMPTOKEN lsfMPTAuthorized = 0x00000002, diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index a37474b780..c376180ac0 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -151,6 +151,20 @@ constexpr std::uint32_t const tfMPTCanClawback = lsfMPTCanClawback; constexpr std::uint32_t const tfMPTokenIssuanceCreateMask = ~(tfUniversal | tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback); +// MPTokenIssuanceCreate MutableFlags: +// Indicating specific fields or flags may be changed after issuance. +constexpr std::uint32_t const tfMPTCanMutateCanLock = lsfMPTCanMutateCanLock; +constexpr std::uint32_t const tfMPTCanMutateRequireAuth = lsfMPTCanMutateRequireAuth; +constexpr std::uint32_t const tfMPTCanMutateCanEscrow = lsfMPTCanMutateCanEscrow; +constexpr std::uint32_t const tfMPTCanMutateCanTrade = lsfMPTCanMutateCanTrade; +constexpr std::uint32_t const tfMPTCanMutateCanTransfer = lsfMPTCanMutateCanTransfer; +constexpr std::uint32_t const tfMPTCanMutateCanClawback = lsfMPTCanMutateCanClawback; +constexpr std::uint32_t const tfMPTCanMutateMetadata = lsfMPTCanMutateMetadata; +constexpr std::uint32_t const tfMPTCanMutateTransferFee = lsfMPTCanMutateTransferFee; +constexpr std::uint32_t const tfMPTokenIssuanceCreateMutableMask = + ~(tfMPTCanMutateCanLock | tfMPTCanMutateRequireAuth | tfMPTCanMutateCanEscrow | tfMPTCanMutateCanTrade + | tfMPTCanMutateCanTransfer | tfMPTCanMutateCanClawback | tfMPTCanMutateMetadata | tfMPTCanMutateTransferFee); + // MPTokenAuthorize flags: constexpr std::uint32_t const tfMPTUnauthorize = 0x00000001; constexpr std::uint32_t const tfMPTokenAuthorizeMask = ~(tfUniversal | tfMPTUnauthorize); @@ -161,6 +175,25 @@ constexpr std::uint32_t const tfMPTUnlock = 0x00000002; constexpr std::uint32_t const tfMPTokenIssuanceSetMask = ~(tfUniversal | tfMPTLock | tfMPTUnlock); constexpr std::uint32_t const tfMPTokenIssuanceSetPermissionMask = ~(tfUniversal | tfMPTLock | tfMPTUnlock); +// MPTokenIssuanceSet MutableFlags: +// Set or Clear flags. +constexpr std::uint32_t const tfMPTSetCanLock = 0x00000001; +constexpr std::uint32_t const tfMPTClearCanLock = 0x00000002; +constexpr std::uint32_t const tfMPTSetRequireAuth = 0x00000004; +constexpr std::uint32_t const tfMPTClearRequireAuth = 0x00000008; +constexpr std::uint32_t const tfMPTSetCanEscrow = 0x00000010; +constexpr std::uint32_t const tfMPTClearCanEscrow = 0x00000020; +constexpr std::uint32_t const tfMPTSetCanTrade = 0x00000040; +constexpr std::uint32_t const tfMPTClearCanTrade = 0x00000080; +constexpr std::uint32_t const tfMPTSetCanTransfer = 0x00000100; +constexpr std::uint32_t const tfMPTClearCanTransfer = 0x00000200; +constexpr std::uint32_t const tfMPTSetCanClawback = 0x00000400; +constexpr std::uint32_t const tfMPTClearCanClawback = 0x00000800; +constexpr std::uint32_t const tfMPTokenIssuanceSetMutableMask = ~(tfMPTSetCanLock | tfMPTClearCanLock | + tfMPTSetRequireAuth | tfMPTClearRequireAuth | tfMPTSetCanEscrow | tfMPTClearCanEscrow | + tfMPTSetCanTrade | tfMPTClearCanTrade | tfMPTSetCanTransfer | tfMPTClearCanTransfer | + tfMPTSetCanClawback | tfMPTClearCanClawback); + // MPTokenIssuanceDestroy flags: constexpr std::uint32_t const tfMPTokenIssuanceDestroyMask = ~tfUniversal; diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index f04e9f3641..a9f5d95624 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -32,6 +32,7 @@ // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. +XRPL_FEATURE(DynamicMPT, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (TokenEscrowV1, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (DelegateV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (PriceOracleOrder, Supported::no, VoteBehavior::DefaultNo) diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index ac9ebc6069..1066986223 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -412,6 +412,7 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({ {sfPreviousTxnID, soeREQUIRED}, {sfPreviousTxnLgrSeq, soeREQUIRED}, {sfDomainID, soeOPTIONAL}, + {sfMutableFlags, soeDEFAULT}, })) /** A ledger object which tracks MPToken diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 537fcae479..96192324fd 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -114,6 +114,7 @@ TYPED_SFIELD(sfVoteWeight, UINT32, 48) TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50) TYPED_SFIELD(sfOracleDocumentID, UINT32, 51) TYPED_SFIELD(sfPermissionValue, UINT32, 52) +TYPED_SFIELD(sfMutableFlags, UINT32, 53) // 64-bit integers (common) TYPED_SFIELD(sfIndexNext, UINT64, 1) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index bfbc18aa1b..3aaa5a40a3 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -548,6 +548,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, {sfMaximumAmount, soeOPTIONAL}, {sfMPTokenMetadata, soeOPTIONAL}, {sfDomainID, soeOPTIONAL}, + {sfMutableFlags, soeOPTIONAL}, })) /** This transaction type destroys a MPTokensIssuance instance */ @@ -566,6 +567,9 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, {sfMPTokenIssuanceID, soeREQUIRED}, {sfHolder, soeOPTIONAL}, {sfDomainID, soeOPTIONAL}, + {sfMPTokenMetadata, soeOPTIONAL}, + {sfTransferFee, soeOPTIONAL}, + {sfMutableFlags, soeOPTIONAL}, })) /** This transaction type authorizes a MPToken instance */ diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 6470962f2f..1410370c33 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -589,7 +589,8 @@ class MPToken_test : public beast::unit_test::suite .flags = 0x00000008, .err = temINVALID_FLAG}); - if (!features[featureSingleAssetVault]) + if (!features[featureSingleAssetVault] && + !features[featureDynamicMPT]) { // test invalid flags - nothing is being changed mptAlice.set( @@ -623,7 +624,8 @@ class MPToken_test : public beast::unit_test::suite .flags = 0x00000000, .err = temMALFORMED}); - if (!features[featurePermissionedDomains]) + if (!features[featurePermissionedDomains] || + !features[featureSingleAssetVault]) { // cannot set DomainID since PD is not enabled mptAlice.set( @@ -631,7 +633,7 @@ class MPToken_test : public beast::unit_test::suite .domainID = uint256(42), .err = temDISABLED}); } - else + else if (features[featureSingleAssetVault]) { // cannot set DomainID since Holder is set mptAlice.set( @@ -2738,6 +2740,882 @@ class MPToken_test : public beast::unit_test::suite } } + void + testInvalidCreateDynamic(FeatureBitset features) + { + testcase("invalid MPTokenIssuanceCreate for DynamicMPT"); + + using namespace test::jtx; + Account const alice("alice"); + + // Can not provide MutableFlags when DynamicMPT amendment is not enabled + { + Env env{*this, features - featureDynamicMPT}; + MPTTester mptAlice(env, alice); + mptAlice.create( + {.ownerCount = 0, .mutableFlags = 2, .err = temDISABLED}); + mptAlice.create( + {.ownerCount = 0, .mutableFlags = 0, .err = temDISABLED}); + } + + // MutableFlags contains invalid values + { + Env env{*this, features}; + MPTTester mptAlice(env, alice); + + // Value 1 is reserved for MPT lock. + mptAlice.create( + {.ownerCount = 0, .mutableFlags = 1, .err = temINVALID_FLAG}); + mptAlice.create( + {.ownerCount = 0, .mutableFlags = 17, .err = temINVALID_FLAG}); + mptAlice.create( + {.ownerCount = 0, + .mutableFlags = 65535, + .err = temINVALID_FLAG}); + + // MutableFlags can not be 0 + mptAlice.create( + {.ownerCount = 0, .mutableFlags = 0, .err = temINVALID_FLAG}); + } + } + + void + testInvalidSetDynamic(FeatureBitset features) + { + testcase("invalid MPTokenIssuanceSet for DynamicMPT"); + + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + + // Can not provide MutableFlags, MPTokenMetadata or TransferFee when + // DynamicMPT amendment is not enabled + { + Env env{*this, features - featureDynamicMPT}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + auto const mptID = makeMptID(env.seq(alice), alice); + + // MutableFlags is not allowed when DynamicMPT is not enabled + mptAlice.set( + {.account = alice, + .id = mptID, + .mutableFlags = 2, + .err = temDISABLED}); + mptAlice.set( + {.account = alice, + .id = mptID, + .mutableFlags = 0, + .err = temDISABLED}); + + // MPTokenMetadata is not allowed when DynamicMPT is not enabled + mptAlice.set( + {.account = alice, + .id = mptID, + .metadata = "test", + .err = temDISABLED}); + mptAlice.set( + {.account = alice, + .id = mptID, + .metadata = "", + .err = temDISABLED}); + + // TransferFee is not allowed when DynamicMPT is not enabled + mptAlice.set( + {.account = alice, + .id = mptID, + .transferFee = 100, + .err = temDISABLED}); + mptAlice.set( + {.account = alice, + .id = mptID, + .transferFee = 0, + .err = temDISABLED}); + } + + // Can not provide holder when MutableFlags, MPTokenMetadata or + // TransferFee is present + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + auto const mptID = makeMptID(env.seq(alice), alice); + + // Holder is not allowed when MutableFlags is present + mptAlice.set( + {.account = alice, + .holder = bob, + .id = mptID, + .mutableFlags = 2, + .err = temMALFORMED}); + + // Holder is not allowed when MPTokenMetadata is present + mptAlice.set( + {.account = alice, + .holder = bob, + .id = mptID, + .metadata = "test", + .err = temMALFORMED}); + + // Holder is not allowed when TransferFee is present + mptAlice.set( + {.account = alice, + .holder = bob, + .id = mptID, + .transferFee = 100, + .err = temMALFORMED}); + } + + // Can not set Flags when MutableFlags, MPTokenMetadata or + // TransferFee is present + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.ownerCount = 1, + .mutableFlags = tfMPTCanMutateMetadata | + tfMPTCanMutateCanLock | tfMPTCanMutateTransferFee}); + + // Setting flags is not allowed when MutableFlags is present + mptAlice.set( + {.account = alice, + .flags = tfMPTCanLock, + .mutableFlags = 2, + .err = temMALFORMED}); + + // Setting flags is not allowed when MPTokenMetadata is present + mptAlice.set( + {.account = alice, + .flags = tfMPTCanLock, + .metadata = "test", + .err = temMALFORMED}); + + // setting flags is not allowed when TransferFee is present + mptAlice.set( + {.account = alice, + .flags = tfMPTCanLock, + .transferFee = 100, + .err = temMALFORMED}); + } + + // Flags being 0 or tfFullyCanonicalSig is fine + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.transferFee = 10, + .ownerCount = 1, + .flags = tfMPTCanTransfer, + .mutableFlags = + tfMPTCanMutateTransferFee | tfMPTCanMutateMetadata}); + + mptAlice.set( + {.account = alice, + .flags = 0, + .transferFee = 100, + .metadata = "test"}); + mptAlice.set( + {.account = alice, + .flags = tfFullyCanonicalSig, + .transferFee = 200, + .metadata = "test2"}); + } + + // Invalid MutableFlags + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + auto const mptID = makeMptID(env.seq(alice), alice); + + for (auto const flags : {10000, 0, 5000}) + { + mptAlice.set( + {.account = alice, + .id = mptID, + .mutableFlags = flags, + .err = temINVALID_FLAG}); + } + } + + // Can not set and clear the same mutable flag + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + auto const mptID = makeMptID(env.seq(alice), alice); + + auto const flagCombinations = { + tfMPTSetCanLock | tfMPTClearCanLock, + tfMPTSetRequireAuth | tfMPTClearRequireAuth, + tfMPTSetCanEscrow | tfMPTClearCanEscrow, + tfMPTSetCanTrade | tfMPTClearCanTrade, + tfMPTSetCanTransfer | tfMPTClearCanTransfer, + tfMPTSetCanClawback | tfMPTClearCanClawback, + tfMPTSetCanLock | tfMPTClearCanLock | tfMPTClearCanTrade, + tfMPTSetCanTransfer | tfMPTClearCanTransfer | + tfMPTSetCanEscrow | tfMPTClearCanClawback}; + + for (auto const& mutableFlags : flagCombinations) + { + mptAlice.set( + {.account = alice, + .id = mptID, + .mutableFlags = mutableFlags, + .err = temINVALID_FLAG}); + } + } + + // Can not mutate flag which is not mutable + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create({.ownerCount = 1}); + + auto const mutableFlags = { + tfMPTSetCanLock, + tfMPTClearCanLock, + tfMPTSetRequireAuth, + tfMPTClearRequireAuth, + tfMPTSetCanEscrow, + tfMPTClearCanEscrow, + tfMPTSetCanTrade, + tfMPTClearCanTrade, + tfMPTSetCanTransfer, + tfMPTClearCanTransfer, + tfMPTSetCanClawback, + tfMPTClearCanClawback}; + + for (auto const& mutableFlag : mutableFlags) + { + mptAlice.set( + {.account = alice, + .mutableFlags = mutableFlag, + .err = tecNO_PERMISSION}); + } + } + + // Metadata exceeding max length + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, .mutableFlags = tfMPTCanMutateMetadata}); + + std::string metadata(maxMPTokenMetadataLength + 1, 'a'); + mptAlice.set( + {.account = alice, .metadata = metadata, .err = temMALFORMED}); + } + + // Can not mutate metadata when it is not mutable + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create({.ownerCount = 1}); + mptAlice.set( + {.account = alice, + .metadata = "test", + .err = tecNO_PERMISSION}); + } + + // Transfer fee exceeding the max value + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + auto const mptID = makeMptID(env.seq(alice), alice); + + mptAlice.create( + {.ownerCount = 1, .mutableFlags = tfMPTCanMutateTransferFee}); + + mptAlice.set( + {.account = alice, + .id = mptID, + .transferFee = maxTransferFee + 1, + .err = temBAD_TRANSFER_FEE}); + } + + // Test setting non-zero transfer fee and clearing MPTCanTransfer at the + // same time + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.transferFee = 100, + .ownerCount = 1, + .flags = tfMPTCanTransfer, + .mutableFlags = + tfMPTCanMutateTransferFee | tfMPTCanMutateCanTransfer}); + + // Can not set non-zero transfer fee and clear MPTCanTransfer at the + // same time + mptAlice.set( + {.account = alice, + .mutableFlags = tfMPTClearCanTransfer, + .transferFee = 1, + .err = temMALFORMED}); + + // Can set transfer fee to zero and clear MPTCanTransfer at the same + // time. tfMPTCanTransfer will be cleared and TransferFee field will + // be removed. + mptAlice.set( + {.account = alice, + .mutableFlags = tfMPTClearCanTransfer, + .transferFee = 0}); + BEAST_EXPECT(!mptAlice.isTransferFeePresent()); + } + + // Can not set non-zero transfer fee when MPTCanTransfer is not set + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .mutableFlags = + tfMPTCanMutateTransferFee | tfMPTCanMutateCanTransfer}); + + mptAlice.set( + {.account = alice, + .transferFee = 100, + .err = tecNO_PERMISSION}); + + // Can not set transfer fee even when trying to set MPTCanTransfer + // at the same time. MPTCanTransfer must be set first, then transfer + // fee can be set in a separate transaction. + mptAlice.set( + {.account = alice, + .mutableFlags = tfMPTSetCanTransfer, + .transferFee = 100, + .err = tecNO_PERMISSION}); + } + + // Can not mutate transfer fee when it is not mutable + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.transferFee = 10, + .ownerCount = 1, + .flags = tfMPTCanTransfer}); + + mptAlice.set( + {.account = alice, + .transferFee = 100, + .err = tecNO_PERMISSION}); + + mptAlice.set( + {.account = alice, .transferFee = 0, .err = tecNO_PERMISSION}); + } + + // Set some flags mutable. Can not mutate the others + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .mutableFlags = tfMPTCanMutateCanTrade | + tfMPTCanMutateCanTransfer | tfMPTCanMutateMetadata}); + + // Can not mutate transfer fee + mptAlice.set( + {.account = alice, + .transferFee = 100, + .err = tecNO_PERMISSION}); + + auto const invalidFlags = { + tfMPTSetCanLock, + tfMPTClearCanLock, + tfMPTSetRequireAuth, + tfMPTClearRequireAuth, + tfMPTSetCanEscrow, + tfMPTClearCanEscrow, + tfMPTSetCanClawback, + tfMPTClearCanClawback}; + + // Can not mutate flags which are not mutable + for (auto const& mutableFlag : invalidFlags) + { + mptAlice.set( + {.account = alice, + .mutableFlags = mutableFlag, + .err = tecNO_PERMISSION}); + } + + // Can mutate MPTCanTrade + mptAlice.set({.account = alice, .mutableFlags = tfMPTSetCanTrade}); + mptAlice.set( + {.account = alice, .mutableFlags = tfMPTClearCanTrade}); + + // Can mutate MPTCanTransfer + mptAlice.set( + {.account = alice, .mutableFlags = tfMPTSetCanTransfer}); + mptAlice.set( + {.account = alice, .mutableFlags = tfMPTClearCanTransfer}); + + // Can mutate metadata + mptAlice.set({.account = alice, .metadata = "test"}); + mptAlice.set({.account = alice, .metadata = ""}); + } + } + + void + testMutateMPT(FeatureBitset features) + { + testcase("Mutate MPT"); + using namespace test::jtx; + + Account const alice("alice"); + + // Mutate metadata + { + Env env{*this, features}; + MPTTester mptAlice(env, alice); + mptAlice.create( + {.metadata = "test", + .ownerCount = 1, + .mutableFlags = tfMPTCanMutateMetadata}); + + std::vector metadatas = { + "mutate metadata", + "mutate metadata 2", + "mutate metadata 3", + "mutate metadata 3", + "test", + "mutate metadata"}; + + for (auto const& metadata : metadatas) + { + mptAlice.set({.account = alice, .metadata = metadata}); + BEAST_EXPECT(mptAlice.checkMetadata(metadata)); + } + + // Metadata being empty will remove the field + mptAlice.set({.account = alice, .metadata = ""}); + BEAST_EXPECT(!mptAlice.isMetadataPresent()); + } + + // Mutate transfer fee + { + Env env{*this, features}; + MPTTester mptAlice(env, alice); + mptAlice.create( + {.transferFee = 100, + .metadata = "test", + .ownerCount = 1, + .flags = tfMPTCanTransfer, + .mutableFlags = tfMPTCanMutateTransferFee}); + + for (std::uint16_t const fee : std::initializer_list{ + 1, 10, 100, 200, 500, 1000, maxTransferFee}) + { + mptAlice.set({.account = alice, .transferFee = fee}); + BEAST_EXPECT(mptAlice.checkTransferFee(fee)); + } + + // Setting TransferFee to zero will remove the field + mptAlice.set({.account = alice, .transferFee = 0}); + BEAST_EXPECT(!mptAlice.isTransferFeePresent()); + + // Set transfer fee again + mptAlice.set({.account = alice, .transferFee = 10}); + BEAST_EXPECT(mptAlice.checkTransferFee(10)); + } + + // Test flag toggling + { + auto testFlagToggle = [&](std::uint32_t createFlags, + std::uint32_t setFlags, + std::uint32_t clearFlags) { + Env env{*this, features}; + MPTTester mptAlice(env, alice); + + // Create the MPT object with the specified initial flags + mptAlice.create( + {.metadata = "test", + .ownerCount = 1, + .mutableFlags = createFlags}); + + // Set and clear the flag multiple times + mptAlice.set({.account = alice, .mutableFlags = setFlags}); + mptAlice.set({.account = alice, .mutableFlags = clearFlags}); + mptAlice.set({.account = alice, .mutableFlags = clearFlags}); + mptAlice.set({.account = alice, .mutableFlags = setFlags}); + mptAlice.set({.account = alice, .mutableFlags = setFlags}); + mptAlice.set({.account = alice, .mutableFlags = clearFlags}); + mptAlice.set({.account = alice, .mutableFlags = setFlags}); + mptAlice.set({.account = alice, .mutableFlags = clearFlags}); + }; + + testFlagToggle( + tfMPTCanMutateCanLock, tfMPTCanLock, tfMPTClearCanLock); + testFlagToggle( + tfMPTCanMutateRequireAuth, + tfMPTSetRequireAuth, + tfMPTClearRequireAuth); + testFlagToggle( + tfMPTCanMutateCanEscrow, + tfMPTSetCanEscrow, + tfMPTClearCanEscrow); + testFlagToggle( + tfMPTCanMutateCanTrade, tfMPTSetCanTrade, tfMPTClearCanTrade); + testFlagToggle( + tfMPTCanMutateCanTransfer, + tfMPTSetCanTransfer, + tfMPTClearCanTransfer); + testFlagToggle( + tfMPTCanMutateCanClawback, + tfMPTSetCanClawback, + tfMPTClearCanClawback); + } + } + + void + testMutateCanLock(FeatureBitset features) + { + testcase("Mutate MPTCanLock"); + using namespace test::jtx; + + Account const alice("alice"); + Account const bob("bob"); + + // Individual lock + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanLock | tfMPTCanTransfer, + .mutableFlags = tfMPTCanMutateCanLock | + tfMPTCanMutateCanTrade | tfMPTCanMutateTransferFee}); + mptAlice.authorize({.account = bob, .holderCount = 1}); + + // Lock bob's mptoken + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + + // Can mutate the mutable flags and fields + mptAlice.set({.account = alice, .mutableFlags = tfMPTClearCanLock}); + mptAlice.set({.account = alice, .mutableFlags = tfMPTSetCanLock}); + mptAlice.set({.account = alice, .mutableFlags = tfMPTClearCanLock}); + mptAlice.set({.account = alice, .mutableFlags = tfMPTSetCanTrade}); + mptAlice.set( + {.account = alice, .mutableFlags = tfMPTClearCanTrade}); + mptAlice.set({.account = alice, .transferFee = 200}); + } + + // Global lock + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanLock, + .mutableFlags = tfMPTCanMutateCanLock | + tfMPTCanMutateCanClawback | tfMPTCanMutateMetadata}); + mptAlice.authorize({.account = bob, .holderCount = 1}); + + // Lock issuance + mptAlice.set({.account = alice, .flags = tfMPTLock}); + + // Can mutate the mutable flags and fields + mptAlice.set({.account = alice, .mutableFlags = tfMPTClearCanLock}); + mptAlice.set({.account = alice, .mutableFlags = tfMPTSetCanLock}); + mptAlice.set({.account = alice, .mutableFlags = tfMPTClearCanLock}); + mptAlice.set( + {.account = alice, .mutableFlags = tfMPTSetCanClawback}); + mptAlice.set( + {.account = alice, .mutableFlags = tfMPTClearCanClawback}); + mptAlice.set({.account = alice, .metadata = "mutate"}); + } + + // Test lock and unlock after mutating MPTCanLock + { + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanLock, + .mutableFlags = tfMPTCanMutateCanLock | + tfMPTCanMutateCanClawback | tfMPTCanMutateMetadata}); + mptAlice.authorize({.account = bob, .holderCount = 1}); + + // Can lock and unlock + mptAlice.set({.account = alice, .flags = tfMPTLock}); + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + mptAlice.set({.account = alice, .flags = tfMPTUnlock}); + mptAlice.set( + {.account = alice, .holder = bob, .flags = tfMPTUnlock}); + + // Clear lsfMPTCanLock + mptAlice.set({.account = alice, .mutableFlags = tfMPTClearCanLock}); + + // Can not lock or unlock + mptAlice.set( + {.account = alice, + .flags = tfMPTLock, + .err = tecNO_PERMISSION}); + mptAlice.set( + {.account = alice, + .flags = tfMPTUnlock, + .err = tecNO_PERMISSION}); + mptAlice.set( + {.account = alice, + .holder = bob, + .flags = tfMPTLock, + .err = tecNO_PERMISSION}); + mptAlice.set( + {.account = alice, + .holder = bob, + .flags = tfMPTUnlock, + .err = tecNO_PERMISSION}); + + // Set MPTCanLock again + mptAlice.set({.account = alice, .mutableFlags = tfMPTSetCanLock}); + + // Can lock and unlock again + mptAlice.set({.account = alice, .flags = tfMPTLock}); + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + mptAlice.set({.account = alice, .flags = tfMPTUnlock}); + mptAlice.set( + {.account = alice, .holder = bob, .flags = tfMPTUnlock}); + } + } + + void + testMutateRequireAuth(FeatureBitset features) + { + testcase("Mutate MPTRequireAuth"); + using namespace test::jtx; + + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.ownerCount = 1, + .flags = tfMPTRequireAuth, + .mutableFlags = tfMPTCanMutateRequireAuth}); + + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = alice, .holder = bob}); + + // Pay to bob + mptAlice.pay(alice, bob, 1000); + + // Unauthorize bob + mptAlice.authorize( + {.account = alice, .holder = bob, .flags = tfMPTUnauthorize}); + + // Can not pay to bob + mptAlice.pay(bob, alice, 100, tecNO_AUTH); + + // Clear RequireAuth + mptAlice.set({.account = alice, .mutableFlags = tfMPTClearRequireAuth}); + + // Can pay to bob + mptAlice.pay(alice, bob, 1000); + + // Set RequireAuth again + mptAlice.set({.account = alice, .mutableFlags = tfMPTSetRequireAuth}); + + // Can not pay to bob since he is not authorized + mptAlice.pay(bob, alice, 100, tecNO_AUTH); + + // Authorize bob again + mptAlice.authorize({.account = alice, .holder = bob}); + + // Can pay to bob again + mptAlice.pay(alice, bob, 100); + } + + void + testMutateCanEscrow(FeatureBitset features) + { + testcase("Mutate MPTCanEscrow"); + using namespace test::jtx; + using namespace std::literals; + + Env env{*this, features}; + auto const baseFee = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + + MPTTester mptAlice(env, alice, {.holders = {carol, bob}}); + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer, + .mutableFlags = tfMPTCanMutateCanEscrow}); + mptAlice.authorize({.account = carol}); + mptAlice.authorize({.account = bob}); + + auto const MPT = mptAlice["MPT"]; + env(pay(alice, carol, MPT(10'000))); + env(pay(alice, bob, MPT(10'000))); + env.close(); + + // MPTCanEscrow is not enabled + env(escrow::create(carol, bob, MPT(3)), + escrow::condition(escrow::cb1), + escrow::finish_time(env.now() + 1s), + fee(baseFee * 150), + ter(tecNO_PERMISSION)); + + // MPTCanEscrow is enabled now + mptAlice.set({.account = alice, .mutableFlags = tfMPTSetCanEscrow}); + env(escrow::create(carol, bob, MPT(3)), + escrow::condition(escrow::cb1), + escrow::finish_time(env.now() + 1s), + fee(baseFee * 150)); + + // Clear MPTCanEscrow + mptAlice.set({.account = alice, .mutableFlags = tfMPTClearCanEscrow}); + env(escrow::create(carol, bob, MPT(3)), + escrow::condition(escrow::cb1), + escrow::finish_time(env.now() + 1s), + fee(baseFee * 150), + ter(tecNO_PERMISSION)); + } + + void + testMutateCanTransfer(FeatureBitset features) + { + testcase("Mutate MPTCanTransfer"); + + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + mptAlice.create( + {.ownerCount = 1, + .mutableFlags = + tfMPTCanMutateCanTransfer | tfMPTCanMutateTransferFee}); + + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = carol}); + + // Pay to bob + mptAlice.pay(alice, bob, 1000); + + // Bob can not pay carol since MPTCanTransfer is not set + mptAlice.pay(bob, carol, 50, tecNO_AUTH); + + // Can not set non-zero transfer fee when MPTCanTransfer is not set + mptAlice.set( + {.account = alice, + .transferFee = 100, + .err = tecNO_PERMISSION}); + + // Can not set non-zero transfer fee even when trying to set + // MPTCanTransfer at the same time + mptAlice.set( + {.account = alice, + .mutableFlags = tfMPTSetCanTransfer, + .transferFee = 100, + .err = tecNO_PERMISSION}); + + // Alice sets MPTCanTransfer + mptAlice.set( + {.account = alice, .mutableFlags = tfMPTSetCanTransfer}); + + // Can set transfer fee now + BEAST_EXPECT(!mptAlice.isTransferFeePresent()); + mptAlice.set({.account = alice, .transferFee = 100}); + BEAST_EXPECT(mptAlice.isTransferFeePresent()); + + // Bob can pay carol + mptAlice.pay(bob, carol, 50); + + // Alice clears MPTCanTransfer + mptAlice.set( + {.account = alice, .mutableFlags = tfMPTClearCanTransfer}); + + // TransferFee field is removed when MPTCanTransfer is cleared + BEAST_EXPECT(!mptAlice.isTransferFeePresent()); + + // Bob can not pay + mptAlice.pay(bob, carol, 50, tecNO_AUTH); + } + + // Can set transfer fee to zero when MPTCanTransfer is not set, but + // tfMPTCanMutateTransferFee is set. + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + mptAlice.create( + {.transferFee = 100, + .ownerCount = 1, + .flags = tfMPTCanTransfer, + .mutableFlags = + tfMPTCanMutateTransferFee | tfMPTCanMutateCanTransfer}); + + BEAST_EXPECT(mptAlice.checkTransferFee(100)); + + // Clear MPTCanTransfer and transfer fee is removed + mptAlice.set( + {.account = alice, .mutableFlags = tfMPTClearCanTransfer}); + BEAST_EXPECT(!mptAlice.isTransferFeePresent()); + + // Can still set transfer fee to zero, although it is already zero + mptAlice.set({.account = alice, .transferFee = 0}); + + // TransferFee field is still not present + BEAST_EXPECT(!mptAlice.isTransferFeePresent()); + } + } + + void + testMutateCanClawback(FeatureBitset features) + { + testcase("Mutate MPTCanClawback"); + + using namespace test::jtx; + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .mutableFlags = tfMPTCanMutateCanClawback}); + + // Bob creates an MPToken + mptAlice.authorize({.account = bob}); + + // Alice pays bob 100 tokens + mptAlice.pay(alice, bob, 100); + + // MPTCanClawback is not enabled + mptAlice.claw(alice, bob, 1, tecNO_PERMISSION); + + // Enable MPTCanClawback + mptAlice.set({.account = alice, .mutableFlags = tfMPTSetCanClawback}); + + // Can clawback now + mptAlice.claw(alice, bob, 1); + + // Clear MPTCanClawback + mptAlice.set({.account = alice, .mutableFlags = tfMPTClearCanClawback}); + + // Can not clawback + mptAlice.claw(alice, bob, 1, tecNO_PERMISSION); + } + public: void run() override @@ -2747,39 +3625,39 @@ public: // MPTokenIssuanceCreate testCreateValidation(all - featureSingleAssetVault); - testCreateValidation( - (all | featureSingleAssetVault) - featurePermissionedDomains); - testCreateValidation(all | featureSingleAssetVault); + testCreateValidation(all - featurePermissionedDomains); + testCreateValidation(all); testCreateEnabled(all - featureSingleAssetVault); - testCreateEnabled(all | featureSingleAssetVault); + testCreateEnabled(all); // MPTokenIssuanceDestroy testDestroyValidation(all - featureSingleAssetVault); - testDestroyValidation(all | featureSingleAssetVault); + testDestroyValidation(all); testDestroyEnabled(all - featureSingleAssetVault); - testDestroyEnabled(all | featureSingleAssetVault); + testDestroyEnabled(all); // MPTokenAuthorize testAuthorizeValidation(all - featureSingleAssetVault); - testAuthorizeValidation(all | featureSingleAssetVault); + testAuthorizeValidation(all); testAuthorizeEnabled(all - featureSingleAssetVault); - testAuthorizeEnabled(all | featureSingleAssetVault); + testAuthorizeEnabled(all); // MPTokenIssuanceSet + testSetValidation(all - featureSingleAssetVault - featureDynamicMPT); testSetValidation(all - featureSingleAssetVault); - testSetValidation( - (all | featureSingleAssetVault) - featurePermissionedDomains); - testSetValidation(all | featureSingleAssetVault); + testSetValidation(all - featureDynamicMPT); + testSetValidation(all - featurePermissionedDomains); + testSetValidation(all); testSetEnabled(all - featureSingleAssetVault); - testSetEnabled(all | featureSingleAssetVault); + testSetEnabled(all); // MPT clawback testClawbackValidation(all); testClawback(all); // Test Direct Payment - testPayment(all | featureSingleAssetVault); + testPayment(all); testDepositPreauth(all); testDepositPreauth(all - featureCredentials); @@ -2794,6 +3672,16 @@ public: // Test helpers testHelperFunctions(); + + // Dynamic MPT + testInvalidCreateDynamic(all); + testInvalidSetDynamic(all); + testMutateMPT(all); + testMutateCanLock(all); + testMutateRequireAuth(all); + testMutateCanEscrow(all); + testMutateCanTransfer(all); + testMutateCanClawback(all); } }; diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 9f7a611feb..f35b1b1ebb 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -102,6 +102,8 @@ MPTTester::create(MPTCreate const& arg) jv[sfMaximumAmount] = std::to_string(*arg.maxAmt); if (arg.domainID) jv[sfDomainID] = to_string(*arg.domainID); + if (arg.mutableFlags) + jv[sfMutableFlags] = *arg.mutableFlags; if (submit(arg, jv) != tesSUCCESS) { // Verify issuance doesn't exist @@ -240,19 +242,59 @@ MPTTester::set(MPTSet const& arg) jv[sfDelegate] = arg.delegate->human(); if (arg.domainID) jv[sfDomainID] = to_string(*arg.domainID); - if (submit(arg, jv) == tesSUCCESS && arg.flags.value_or(0)) + if (arg.mutableFlags) + jv[sfMutableFlags] = *arg.mutableFlags; + if (arg.transferFee) + jv[sfTransferFee] = *arg.transferFee; + if (arg.metadata) + jv[sfMPTokenMetadata] = strHex(*arg.metadata); + if (submit(arg, jv) == tesSUCCESS && (arg.flags || arg.mutableFlags)) { auto require = [&](std::optional const& holder, bool unchanged) { auto flags = getFlags(holder); if (!unchanged) { - if (*arg.flags & tfMPTLock) - flags |= lsfMPTLocked; - else if (*arg.flags & tfMPTUnlock) - flags &= ~lsfMPTLocked; - else - Throw("Invalid flags"); + if (arg.flags) + { + if (*arg.flags & tfMPTLock) + flags |= lsfMPTLocked; + else if (*arg.flags & tfMPTUnlock) + flags &= ~lsfMPTLocked; + } + + if (arg.mutableFlags) + { + if (*arg.mutableFlags & tfMPTSetCanLock) + flags |= lsfMPTCanLock; + else if (*arg.mutableFlags & tfMPTClearCanLock) + flags &= ~lsfMPTCanLock; + + if (*arg.mutableFlags & tfMPTSetRequireAuth) + flags |= lsfMPTRequireAuth; + else if (*arg.mutableFlags & tfMPTClearRequireAuth) + flags &= ~lsfMPTRequireAuth; + + if (*arg.mutableFlags & tfMPTSetCanEscrow) + flags |= lsfMPTCanEscrow; + else if (*arg.mutableFlags & tfMPTClearCanEscrow) + flags &= ~lsfMPTCanEscrow; + + if (*arg.mutableFlags & tfMPTSetCanClawback) + flags |= lsfMPTCanClawback; + else if (*arg.mutableFlags & tfMPTClearCanClawback) + flags &= ~lsfMPTCanClawback; + + if (*arg.mutableFlags & tfMPTSetCanTrade) + flags |= lsfMPTCanTrade; + else if (*arg.mutableFlags & tfMPTClearCanTrade) + flags &= ~lsfMPTCanTrade; + + if (*arg.mutableFlags & tfMPTSetCanTransfer) + flags |= lsfMPTCanTransfer; + else if (*arg.mutableFlags & tfMPTClearCanTransfer) + flags &= ~lsfMPTCanTransfer; + } } env_.require(mptflags(*this, flags, holder)); }; @@ -313,6 +355,43 @@ MPTTester::checkFlags( return expectedFlags == getFlags(holder); } +[[nodiscard]] bool +MPTTester::checkMetadata(std::string const& metadata) const +{ + return forObject([&](SLEP const& sle) -> bool { + if (sle->isFieldPresent(sfMPTokenMetadata)) + return strHex(sle->getFieldVL(sfMPTokenMetadata)) == + strHex(metadata); + return false; + }); +} + +[[nodiscard]] bool +MPTTester::isMetadataPresent() const +{ + return forObject([&](SLEP const& sle) -> bool { + return sle->isFieldPresent(sfMPTokenMetadata); + }); +} + +[[nodiscard]] bool +MPTTester::checkTransferFee(std::uint16_t transferFee) const +{ + return forObject([&](SLEP const& sle) -> bool { + if (sle->isFieldPresent(sfTransferFee)) + return sle->getFieldU16(sfTransferFee) == transferFee; + return false; + }); +} + +[[nodiscard]] bool +MPTTester::isTransferFeePresent() const +{ + return forObject([&](SLEP const& sle) -> bool { + return sle->isFieldPresent(sfTransferFee); + }); +} + void MPTTester::pay( Account const& src, diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index 4756ca723d..2eacac68ec 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -106,6 +106,7 @@ struct MPTCreate std::optional holderCount = std::nullopt; bool fund = true; std::optional flags = {0}; + std::optional mutableFlags = std::nullopt; std::optional domainID = std::nullopt; std::optional err = std::nullopt; }; @@ -139,6 +140,9 @@ struct MPTSet std::optional ownerCount = std::nullopt; std::optional holderCount = std::nullopt; std::optional flags = std::nullopt; + std::optional mutableFlags = std::nullopt; + std::optional transferFee = std::nullopt; + std::optional metadata = std::nullopt; std::optional delegate = std::nullopt; std::optional domainID = std::nullopt; std::optional err = std::nullopt; @@ -182,6 +186,18 @@ public: uint32_t const expectedFlags, std::optional const& holder = std::nullopt) const; + [[nodiscard]] bool + checkMetadata(std::string const& metadata) const; + + [[nodiscard]] bool + isMetadataPresent() const; + + [[nodiscard]] bool + checkTransferFee(std::uint16_t transferFee) const; + + [[nodiscard]] bool + isTransferFeePresent() const; + Account const& issuer() const { diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp index da3b57c8fe..6a6e598f42 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp @@ -36,9 +36,17 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) ctx.rules.enabled(featureSingleAssetVault))) return temDISABLED; + if (ctx.tx.isFieldPresent(sfMutableFlags) && + !ctx.rules.enabled(featureDynamicMPT)) + return temDISABLED; + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (auto const mutableFlags = ctx.tx[~sfMutableFlags]; mutableFlags && + (!*mutableFlags || *mutableFlags & tfMPTokenIssuanceCreateMutableMask)) + return temINVALID_FLAG; + if (ctx.tx.getFlags() & tfMPTokenIssuanceCreateMask) return temINVALID_FLAG; @@ -132,6 +140,9 @@ MPTokenIssuanceCreate::create( if (args.domainId) (*mptIssuance)[sfDomainID] = *args.domainId; + if (args.mutableFlags) + (*mptIssuance)[sfMutableFlags] = *args.mutableFlags; + view.insert(mptIssuance); } @@ -158,6 +169,7 @@ MPTokenIssuanceCreate::doApply() .transferFee = tx[~sfTransferFee], .metadata = tx[~sfMPTokenMetadata], .domainId = tx[~sfDomainID], + .mutableFlags = tx[~sfMutableFlags], }); return result ? tesSUCCESS : result.error(); } diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.h b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.h index ea01908dff..0527b9602f 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.h +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.h @@ -38,6 +38,7 @@ struct MPTCreateArgs std::optional transferFee{}; std::optional const& metadata{}; std::optional domainId{}; + std::optional mutableFlags{}; }; class MPTokenIssuanceCreate : public Transactor diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp index e05862af37..83b771c705 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp @@ -26,6 +26,24 @@ namespace ripple { +// Maps set/clear mutable flags in an MPTokenIssuanceSet transaction to the +// corresponding ledger mutable flags that control whether the change is +// allowed. +struct MPTMutabilityFlags +{ + std::uint32_t setFlag; + std::uint32_t clearFlag; + std::uint32_t canMutateFlag; +}; + +static constexpr std::array mptMutabilityFlags = { + {{tfMPTSetCanLock, tfMPTClearCanLock, lsfMPTCanMutateCanLock}, + {tfMPTSetRequireAuth, tfMPTClearRequireAuth, lsfMPTCanMutateRequireAuth}, + {tfMPTSetCanEscrow, tfMPTClearCanEscrow, lsfMPTCanMutateCanEscrow}, + {tfMPTSetCanTrade, tfMPTClearCanTrade, lsfMPTCanMutateCanTrade}, + {tfMPTSetCanTransfer, tfMPTClearCanTransfer, lsfMPTCanMutateCanTransfer}, + {tfMPTSetCanClawback, tfMPTClearCanClawback, lsfMPTCanMutateCanClawback}}}; + NotTEC MPTokenIssuanceSet::preflight(PreflightContext const& ctx) { @@ -37,6 +55,14 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx) ctx.rules.enabled(featureSingleAssetVault))) return temDISABLED; + auto const mutableFlags = ctx.tx[~sfMutableFlags]; + auto const metadata = ctx.tx[~sfMPTokenMetadata]; + auto const transferFee = ctx.tx[~sfTransferFee]; + auto const isMutate = mutableFlags || metadata || transferFee; + + if (isMutate && !ctx.rules.enabled(featureDynamicMPT)) + return temDISABLED; + if (ctx.tx.isFieldPresent(sfDomainID) && ctx.tx.isFieldPresent(sfHolder)) return temMALFORMED; @@ -57,13 +83,54 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx) if (holderID && accountID == holderID) return temMALFORMED; - if (ctx.rules.enabled(featureSingleAssetVault)) + if (ctx.rules.enabled(featureSingleAssetVault) || + ctx.rules.enabled(featureDynamicMPT)) { // Is this transaction actually changing anything ? - if (txFlags == 0 && !ctx.tx.isFieldPresent(sfDomainID)) + if (txFlags == 0 && !ctx.tx.isFieldPresent(sfDomainID) && !isMutate) return temMALFORMED; } + if (ctx.rules.enabled(featureDynamicMPT)) + { + // Holder field is not allowed when mutating MPTokenIssuance + if (isMutate && holderID) + return temMALFORMED; + + // Can not set flags when mutating MPTokenIssuance + if (isMutate && (txFlags & tfUniversalMask)) + return temMALFORMED; + + if (transferFee && *transferFee > maxTransferFee) + return temBAD_TRANSFER_FEE; + + if (metadata && metadata->length() > maxMPTokenMetadataLength) + return temMALFORMED; + + if (mutableFlags) + { + if (!*mutableFlags || + (*mutableFlags & tfMPTokenIssuanceSetMutableMask)) + return temINVALID_FLAG; + + // Can not set and clear the same flag + if (std::any_of( + mptMutabilityFlags.begin(), + mptMutabilityFlags.end(), + [mutableFlags](auto const& f) { + return (*mutableFlags & f.setFlag) && + (*mutableFlags & f.clearFlag); + })) + return temINVALID_FLAG; + + // Trying to set a non-zero TransferFee and clear MPTCanTransfer + // in the same transaction is not allowed. + if (transferFee.value_or(0) && + (*mutableFlags & tfMPTClearCanTransfer)) + return temMALFORMED; + } + } + return preflight2(ctx); } @@ -116,7 +183,8 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) if (!sleMptIssuance->isFlag(lsfMPTCanLock)) { // For readability two separate `if` rather than `||` of two conditions - if (!ctx.view.rules().enabled(featureSingleAssetVault)) + if (!ctx.view.rules().enabled(featureSingleAssetVault) && + !ctx.view.rules().enabled(featureDynamicMPT)) return tecNO_PERMISSION; else if (ctx.tx.isFlag(tfMPTLock) || ctx.tx.isFlag(tfMPTUnlock)) return tecNO_PERMISSION; @@ -152,6 +220,44 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) } } + // sfMutableFlags is soeDEFAULT, defaulting to 0 if not specified on + // the ledger. + auto const currentMutableFlags = + sleMptIssuance->getFieldU32(sfMutableFlags); + + auto isMutableFlag = [&](std::uint32_t mutableFlag) -> bool { + return currentMutableFlags & mutableFlag; + }; + + if (auto const mutableFlags = ctx.tx[~sfMutableFlags]) + { + if (std::any_of( + mptMutabilityFlags.begin(), + mptMutabilityFlags.end(), + [mutableFlags, &isMutableFlag](auto const& f) { + return !isMutableFlag(f.canMutateFlag) && + ((*mutableFlags & (f.setFlag | f.clearFlag))); + })) + return tecNO_PERMISSION; + } + + if (!isMutableFlag(lsfMPTCanMutateMetadata) && + ctx.tx.isFieldPresent(sfMPTokenMetadata)) + return tecNO_PERMISSION; + + if (auto const fee = ctx.tx[~sfTransferFee]) + { + // A non-zero TransferFee is only valid if the lsfMPTCanTransfer flag + // was previously enabled (at issuance or via a prior mutation). Setting + // it by tfMPTSetCanTransfer in the current transaction does not meet + // this requirement. + if (fee > 0u && !sleMptIssuance->isFlag(lsfMPTCanTransfer)) + return tecNO_PERMISSION; + + if (!isMutableFlag(lsfMPTCanMutateTransferFee)) + return tecNO_PERMISSION; + } + return tesSUCCESS; } @@ -180,9 +286,47 @@ MPTokenIssuanceSet::doApply() else if (txFlags & tfMPTUnlock) flagsOut &= ~lsfMPTLocked; + if (auto const mutableFlags = ctx_.tx[~sfMutableFlags].value_or(0)) + { + for (auto const& f : mptMutabilityFlags) + { + if (mutableFlags & f.setFlag) + flagsOut |= f.canMutateFlag; + else if (mutableFlags & f.clearFlag) + flagsOut &= ~f.canMutateFlag; + } + + if (mutableFlags & tfMPTClearCanTransfer) + { + // If the lsfMPTCanTransfer flag is being cleared, then also clear + // the TransferFee field. + sle->makeFieldAbsent(sfTransferFee); + } + } + if (flagsIn != flagsOut) sle->setFieldU32(sfFlags, flagsOut); + if (auto const transferFee = ctx_.tx[~sfTransferFee]) + { + // TransferFee uses soeDEFAULT style: + // - If the field is absent, it is interpreted as 0. + // - If the field is present, it must be non-zero. + // Therefore, when TransferFee is 0, the field should be removed. + if (transferFee == 0) + sle->makeFieldAbsent(sfTransferFee); + else + sle->setFieldU16(sfTransferFee, *transferFee); + } + + if (auto const metadata = ctx_.tx[~sfMPTokenMetadata]) + { + if (metadata->empty()) + sle->makeFieldAbsent(sfMPTokenMetadata); + else + sle->setFieldVL(sfMPTokenMetadata, *metadata); + } + if (domainID) { // This is enforced in preflight. From 17a2606591cf4f45b03b128630895ebef66b42bd Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Tue, 16 Sep 2025 11:51:55 +0200 Subject: [PATCH 14/24] Bugfix: Adds graceful peer disconnection (#5669) The XRPL establishes connections in three stages: first a TCP connection, then a TLS/SSL handshake to secure the connection, and finally an upgrade to the bespoke XRP Ledger peer-to-peer protocol. During connection termination, xrpld directly closes the TCP connection, bypassing the TLS/SSL shutdown handshake. This makes peer disconnection diagnostics more difficult - abrupt TCP termination appears as if the peer crashed rather than disconnected gracefully. This change refactors the connection lifecycle with the following changes: - Enhanced outgoing connection logic with granular timeouts for each connection stage (TCP, TLS, XRPL handshake) to improve diagnostic capabilities - Updated both PeerImp and ConnectAttempt to use proper asynchronous TLS shutdown procedures for graceful connection termination --- src/xrpld/overlay/detail/ConnectAttempt.cpp | 488 ++++++++++++++------ src/xrpld/overlay/detail/ConnectAttempt.h | 208 ++++++++- src/xrpld/overlay/detail/PeerImp.cpp | 397 +++++++++------- src/xrpld/overlay/detail/PeerImp.h | 208 ++++++++- 4 files changed, 966 insertions(+), 335 deletions(-) diff --git a/src/xrpld/overlay/detail/ConnectAttempt.cpp b/src/xrpld/overlay/detail/ConnectAttempt.cpp index 397ac06ba6..c1bc4bb069 100644 --- a/src/xrpld/overlay/detail/ConnectAttempt.cpp +++ b/src/xrpld/overlay/detail/ConnectAttempt.cpp @@ -24,6 +24,8 @@ #include +#include + namespace ripple { ConnectAttempt::ConnectAttempt( @@ -45,6 +47,7 @@ ConnectAttempt::ConnectAttempt( , usage_(usage) , strand_(boost::asio::make_strand(io_context)) , timer_(io_context) + , stepTimer_(io_context) , stream_ptr_(std::make_unique( socket_type(std::forward(io_context)), *context)) @@ -52,14 +55,14 @@ ConnectAttempt::ConnectAttempt( , stream_(*stream_ptr_) , slot_(slot) { - JLOG(journal_.debug()) << "Connect " << remote_endpoint; } ConnectAttempt::~ConnectAttempt() { + // slot_ will be null if we successfully connected + // and transferred ownership to a PeerImp if (slot_ != nullptr) overlay_.peerFinder().on_closed(slot_); - JLOG(journal_.trace()) << "~ConnectAttempt"; } void @@ -68,16 +71,29 @@ ConnectAttempt::stop() if (!strand_.running_in_this_thread()) return boost::asio::post( strand_, std::bind(&ConnectAttempt::stop, shared_from_this())); - if (socket_.is_open()) - { - JLOG(journal_.debug()) << "Stop"; - } - close(); + + if (!socket_.is_open()) + return; + + JLOG(journal_.debug()) << "stop: Stop"; + + shutdown(); } void ConnectAttempt::run() { + if (!strand_.running_in_this_thread()) + return boost::asio::post( + strand_, std::bind(&ConnectAttempt::run, shared_from_this())); + + JLOG(journal_.debug()) << "run: connecting to " << remote_endpoint_; + + ioPending_ = true; + + // Allow up to connectTimeout_ seconds to establish remote peer connection + setTimer(ConnectionStep::TcpConnect); + stream_.next_layer().async_connect( remote_endpoint_, boost::asio::bind_executor( @@ -90,61 +106,177 @@ ConnectAttempt::run() //------------------------------------------------------------------------------ +void +ConnectAttempt::shutdown() +{ + XRPL_ASSERT( + strand_.running_in_this_thread(), + "ripple::ConnectAttempt::shutdown: strand in this thread"); + + if (!socket_.is_open()) + return; + + shutdown_ = true; + boost::beast::get_lowest_layer(stream_).cancel(); + + tryAsyncShutdown(); +} + +void +ConnectAttempt::tryAsyncShutdown() +{ + XRPL_ASSERT( + strand_.running_in_this_thread(), + "ripple::ConnectAttempt::tryAsyncShutdown : strand in this thread"); + + if (!shutdown_ || currentStep_ == ConnectionStep::ShutdownStarted) + return; + + if (ioPending_) + return; + + // gracefully shutdown the SSL socket, performing a shutdown handshake + if (currentStep_ != ConnectionStep::TcpConnect && + currentStep_ != ConnectionStep::TlsHandshake) + { + setTimer(ConnectionStep::ShutdownStarted); + return stream_.async_shutdown(bind_executor( + strand_, + std::bind( + &ConnectAttempt::onShutdown, + shared_from_this(), + std::placeholders::_1))); + } + + close(); +} + +void +ConnectAttempt::onShutdown(error_code ec) +{ + cancelTimer(); + + if (ec) + { + // - eof: the stream was cleanly closed + // - operation_aborted: an expired timer (slow shutdown) + // - stream_truncated: the tcp connection closed (no handshake) it could + // occur if a peer does not perform a graceful disconnect + // - broken_pipe: the peer is gone + // - application data after close notify: benign SSL shutdown condition + bool shouldLog = + (ec != boost::asio::error::eof && + ec != boost::asio::error::operation_aborted && + ec.message().find("application data after close notify") == + std::string::npos); + + if (shouldLog) + { + JLOG(journal_.debug()) << "onShutdown: " << ec.message(); + } + } + + close(); +} + void ConnectAttempt::close() { XRPL_ASSERT( strand_.running_in_this_thread(), "ripple::ConnectAttempt::close : strand in this thread"); - if (socket_.is_open()) - { - try - { - timer_.cancel(); - socket_.close(); - } - catch (boost::system::system_error const&) - { - // ignored - } + if (!socket_.is_open()) + return; - JLOG(journal_.debug()) << "Closed"; - } + cancelTimer(); + + error_code ec; + socket_.close(ec); } void ConnectAttempt::fail(std::string const& reason) { JLOG(journal_.debug()) << reason; - close(); + shutdown(); } void ConnectAttempt::fail(std::string const& name, error_code ec) { JLOG(journal_.debug()) << name << ": " << ec.message(); - close(); + shutdown(); } void -ConnectAttempt::setTimer() +ConnectAttempt::setTimer(ConnectionStep step) { - try + currentStep_ = step; + + // Set global timer (only if not already set) + if (timer_.expiry() == std::chrono::steady_clock::time_point{}) { - timer_.expires_after(std::chrono::seconds(15)); - } - catch (boost::system::system_error const& e) - { - JLOG(journal_.error()) << "setTimer: " << e.code(); - return; + try + { + timer_.expires_after(connectTimeout); + timer_.async_wait(boost::asio::bind_executor( + strand_, + std::bind( + &ConnectAttempt::onTimer, + shared_from_this(), + std::placeholders::_1))); + } + catch (std::exception const& ex) + { + JLOG(journal_.error()) << "setTimer (global): " << ex.what(); + return close(); + } } - timer_.async_wait(boost::asio::bind_executor( - strand_, - std::bind( - &ConnectAttempt::onTimer, - shared_from_this(), - std::placeholders::_1))); + // Set step-specific timer + try + { + std::chrono::seconds stepTimeout; + switch (step) + { + case ConnectionStep::TcpConnect: + stepTimeout = StepTimeouts::tcpConnect; + break; + case ConnectionStep::TlsHandshake: + stepTimeout = StepTimeouts::tlsHandshake; + break; + case ConnectionStep::HttpWrite: + stepTimeout = StepTimeouts::httpWrite; + break; + case ConnectionStep::HttpRead: + stepTimeout = StepTimeouts::httpRead; + break; + case ConnectionStep::ShutdownStarted: + stepTimeout = StepTimeouts::tlsShutdown; + break; + case ConnectionStep::Complete: + case ConnectionStep::Init: + return; // No timer needed for init or complete step + } + + // call to expires_after cancels previous timer + stepTimer_.expires_after(stepTimeout); + stepTimer_.async_wait(boost::asio::bind_executor( + strand_, + std::bind( + &ConnectAttempt::onTimer, + shared_from_this(), + std::placeholders::_1))); + + JLOG(journal_.trace()) << "setTimer: " << stepToString(step) + << " timeout=" << stepTimeout.count() << "s"; + } + catch (std::exception const& ex) + { + JLOG(journal_.error()) + << "setTimer (step " << stepToString(step) << "): " << ex.what(); + return close(); + } } void @@ -153,6 +285,7 @@ ConnectAttempt::cancelTimer() try { timer_.cancel(); + stepTimer_.cancel(); } catch (boost::system::system_error const&) { @@ -165,34 +298,69 @@ ConnectAttempt::onTimer(error_code ec) { if (!socket_.is_open()) return; - if (ec == boost::asio::error::operation_aborted) - return; + if (ec) { + // do not initiate shutdown, timers are frequently cancelled + if (ec == boost::asio::error::operation_aborted) + return; + // This should never happen JLOG(journal_.error()) << "onTimer: " << ec.message(); return close(); } - fail("Timeout"); + + // Determine which timer expired by checking their expiry times + auto const now = std::chrono::steady_clock::now(); + bool globalExpired = (timer_.expiry() <= now); + bool stepExpired = (stepTimer_.expiry() <= now); + + if (globalExpired) + { + JLOG(journal_.debug()) + << "onTimer: Global timeout; step: " << stepToString(currentStep_); + } + else if (stepExpired) + { + JLOG(journal_.debug()) + << "onTimer: Step timeout; step: " << stepToString(currentStep_); + } + else + { + JLOG(journal_.warn()) << "onTimer: Unexpected timer callback"; + } + + close(); } void ConnectAttempt::onConnect(error_code ec) { - cancelTimer(); + ioPending_ = false; - if (ec == boost::asio::error::operation_aborted) - return; - endpoint_type local_endpoint; - if (!ec) - local_endpoint = socket_.local_endpoint(ec); if (ec) + { + if (ec == boost::asio::error::operation_aborted) + return tryAsyncShutdown(); + return fail("onConnect", ec); + } + if (!socket_.is_open()) return; - JLOG(journal_.trace()) << "onConnect"; - setTimer(); + // check if connection has really been established + socket_.local_endpoint(ec); + if (ec) + return fail("onConnect", ec); + + if (shutdown_) + return tryAsyncShutdown(); + + ioPending_ = true; + + setTimer(ConnectionStep::TlsHandshake); + stream_.set_verify_mode(boost::asio::ssl::verify_none); stream_.async_handshake( boost::asio::ssl::stream_base::client, @@ -207,25 +375,30 @@ ConnectAttempt::onConnect(error_code ec) void ConnectAttempt::onHandshake(error_code ec) { - cancelTimer(); - if (!socket_.is_open()) - return; - if (ec == boost::asio::error::operation_aborted) - return; - endpoint_type local_endpoint; - if (!ec) - local_endpoint = socket_.local_endpoint(ec); + ioPending_ = false; + + if (ec) + { + if (ec == boost::asio::error::operation_aborted) + return tryAsyncShutdown(); + + return fail("onHandshake", ec); + } + + auto const local_endpoint = socket_.local_endpoint(ec); if (ec) return fail("onHandshake", ec); - JLOG(journal_.trace()) << "onHandshake"; + setTimer(ConnectionStep::HttpWrite); + + // check if we connected to ourselves if (!overlay_.peerFinder().onConnected( slot_, beast::IPAddressConversion::from_asio(local_endpoint))) - return fail("Duplicate connection"); + return fail("Self connection"); auto const sharedValue = makeSharedValue(*stream_ptr_, journal_); if (!sharedValue) - return close(); // makeSharedValue logs + return shutdown(); // makeSharedValue logs req_ = makeRequest( !overlay_.peerFinder().config().peerPrivate, @@ -242,7 +415,11 @@ ConnectAttempt::onHandshake(error_code ec) remote_endpoint_.address(), app_); - setTimer(); + if (shutdown_) + return tryAsyncShutdown(); + + ioPending_ = true; + boost::beast::http::async_write( stream_, req_, @@ -257,13 +434,23 @@ ConnectAttempt::onHandshake(error_code ec) void ConnectAttempt::onWrite(error_code ec) { - cancelTimer(); - if (!socket_.is_open()) - return; - if (ec == boost::asio::error::operation_aborted) - return; + ioPending_ = false; + if (ec) + { + if (ec == boost::asio::error::operation_aborted) + return tryAsyncShutdown(); + return fail("onWrite", ec); + } + + if (shutdown_) + return tryAsyncShutdown(); + + ioPending_ = true; + + setTimer(ConnectionStep::HttpRead); + boost::beast::http::async_read( stream_, read_buf_, @@ -280,39 +467,27 @@ void ConnectAttempt::onRead(error_code ec) { cancelTimer(); + ioPending_ = false; + currentStep_ = ConnectionStep::Complete; - if (!socket_.is_open()) - return; - if (ec == boost::asio::error::operation_aborted) - return; - if (ec == boost::asio::error::eof) - { - JLOG(journal_.info()) << "EOF"; - setTimer(); - return stream_.async_shutdown(boost::asio::bind_executor( - strand_, - std::bind( - &ConnectAttempt::onShutdown, - shared_from_this(), - std::placeholders::_1))); - } if (ec) - return fail("onRead", ec); - processResponse(); -} - -void -ConnectAttempt::onShutdown(error_code ec) -{ - cancelTimer(); - if (!ec) { - JLOG(journal_.error()) << "onShutdown: expected error condition"; - return close(); + if (ec == boost::asio::error::eof) + { + JLOG(journal_.debug()) << "EOF"; + return shutdown(); + } + + if (ec == boost::asio::error::operation_aborted) + return tryAsyncShutdown(); + + return fail("onRead", ec); } - if (ec != boost::asio::error::eof) - return fail("onShutdown", ec); - close(); + + if (shutdown_) + return tryAsyncShutdown(); + + processResponse(); } //-------------------------------------------------------------------------- @@ -320,48 +495,69 @@ ConnectAttempt::onShutdown(error_code ec) void ConnectAttempt::processResponse() { - if (response_.result() == boost::beast::http::status::service_unavailable) - { - Json::Value json; - Json::Reader r; - std::string s; - s.reserve(boost::asio::buffer_size(response_.body().data())); - for (auto const buffer : response_.body().data()) - s.append( - static_cast(buffer.data()), - boost::asio::buffer_size(buffer)); - auto const success = r.parse(s, json); - if (success) - { - if (json.isObject() && json.isMember("peer-ips")) - { - Json::Value const& ips = json["peer-ips"]; - if (ips.isArray()) - { - std::vector eps; - eps.reserve(ips.size()); - for (auto const& v : ips) - { - if (v.isString()) - { - error_code ec; - auto const ep = parse_endpoint(v.asString(), ec); - if (!ec) - eps.push_back(ep); - } - } - overlay_.peerFinder().onRedirects(remote_endpoint_, eps); - } - } - } - } - if (!OverlayImpl::isPeerUpgrade(response_)) { - JLOG(journal_.info()) - << "Unable to upgrade to peer protocol: " << response_.result() - << " (" << response_.reason() << ")"; - return close(); + // A peer may respond with service_unavailable and a list of alternative + // peers to connect to, a differing status code is unexpected + if (response_.result() != + boost::beast::http::status::service_unavailable) + { + JLOG(journal_.warn()) + << "Unable to upgrade to peer protocol: " << response_.result() + << " (" << response_.reason() << ")"; + return shutdown(); + } + + // Parse response body to determine if this is a redirect or other + // service unavailable + std::string responseBody; + responseBody.reserve(boost::asio::buffer_size(response_.body().data())); + for (auto const buffer : response_.body().data()) + responseBody.append( + static_cast(buffer.data()), + boost::asio::buffer_size(buffer)); + + Json::Value json; + Json::Reader reader; + auto const isValidJson = reader.parse(responseBody, json); + + // Check if this is a redirect response (contains peer-ips field) + auto const isRedirect = + isValidJson && json.isObject() && json.isMember("peer-ips"); + + if (!isRedirect) + { + JLOG(journal_.warn()) + << "processResponse: " << remote_endpoint_ + << " failed to upgrade to peer protocol: " << response_.result() + << " (" << response_.reason() << ")"; + + return shutdown(); + } + + Json::Value const& peerIps = json["peer-ips"]; + if (!peerIps.isArray()) + return fail("processResponse: invalid peer-ips format"); + + // Extract and validate peer endpoints + std::vector redirectEndpoints; + redirectEndpoints.reserve(peerIps.size()); + + for (auto const& ipValue : peerIps) + { + if (!ipValue.isString()) + continue; + + error_code ec; + auto const endpoint = parse_endpoint(ipValue.asString(), ec); + if (!ec) + redirectEndpoints.push_back(endpoint); + } + + // Notify PeerFinder about the redirect redirectEndpoints may be empty + overlay_.peerFinder().onRedirects(remote_endpoint_, redirectEndpoints); + + return fail("processResponse: failed to connect to peer: redirected"); } // Just because our peer selected a particular protocol version doesn't @@ -381,11 +577,11 @@ ConnectAttempt::processResponse() auto const sharedValue = makeSharedValue(*stream_ptr_, journal_); if (!sharedValue) - return close(); // makeSharedValue logs + return shutdown(); // makeSharedValue logs try { - auto publicKey = verifyHandshake( + auto const publicKey = verifyHandshake( response_, *sharedValue, overlay_.setup().networkID, @@ -393,11 +589,10 @@ ConnectAttempt::processResponse() remote_endpoint_.address(), app_); - JLOG(journal_.info()) - << "Public Key: " << toBase58(TokenType::NodePublic, publicKey); - JLOG(journal_.debug()) << "Protocol: " << to_string(*negotiatedProtocol); + JLOG(journal_.info()) + << "Public Key: " << toBase58(TokenType::NodePublic, publicKey); auto const member = app_.cluster().member(publicKey); if (member) @@ -405,10 +600,21 @@ ConnectAttempt::processResponse() JLOG(journal_.info()) << "Cluster name: " << *member; } - auto const result = overlay_.peerFinder().activate( - slot_, publicKey, static_cast(member)); + auto const result = + overlay_.peerFinder().activate(slot_, publicKey, !member->empty()); if (result != PeerFinder::Result::success) - return fail("Outbound " + std::string(to_string(result))); + { + std::stringstream ss; + ss << "Outbound Connect Attempt " << remote_endpoint_ << " " + << to_string(result); + return fail(ss.str()); + } + + if (!socket_.is_open()) + return; + + if (shutdown_) + return tryAsyncShutdown(); auto const peer = std::make_shared( app_, diff --git a/src/xrpld/overlay/detail/ConnectAttempt.h b/src/xrpld/overlay/detail/ConnectAttempt.h index febbe88f45..38b9482d9d 100644 --- a/src/xrpld/overlay/detail/ConnectAttempt.h +++ b/src/xrpld/overlay/detail/ConnectAttempt.h @@ -22,90 +22,258 @@ #include +#include + namespace ripple { -/** Manages an outbound connection attempt. */ +/** + * @class ConnectAttempt + * @brief Manages outbound peer connection attempts with comprehensive timeout + * handling + * + * The ConnectAttempt class handles the complete lifecycle of establishing an + * outbound connection to a peer in the XRPL network. It implements a + * sophisticated dual-timer system that provides both global timeout protection + * and per-step timeout diagnostics. + * + * The connection establishment follows these steps: + * 1. **TCP Connect**: Establish basic network connection + * 2. **TLS Handshake**: Negotiate SSL/TLS encryption + * 3. **HTTP Write**: Send peer handshake request + * 4. **HTTP Read**: Receive and validate peer response + * 5. **Complete**: Connection successfully established + * + * Uses a hybrid timeout approach: + * - **Global Timer**: Hard limit (20s) for entire connection process + * - **Step Timers**: Individual timeouts for each connection phase + * + * - All errors result in connection termination + * + * All operations are serialized using boost::asio::strand to ensure thread + * safety. The class is designed to be used exclusively within the ASIO event + * loop. + * + * @note This class should not be used directly. It is managed by OverlayImpl + * as part of the peer discovery and connection management system. + * + */ class ConnectAttempt : public OverlayImpl::Child, public std::enable_shared_from_this { private: using error_code = boost::system::error_code; - using endpoint_type = boost::asio::ip::tcp::endpoint; - using request_type = boost::beast::http::request; - using response_type = boost::beast::http::response; - using socket_type = boost::asio::ip::tcp::socket; using middle_type = boost::beast::tcp_stream; using stream_type = boost::beast::ssl_stream; using shared_context = std::shared_ptr; + /** + * @enum ConnectionStep + * @brief Represents the current phase of the connection establishment + * process + * + * Used for tracking progress and providing detailed timeout diagnostics. + * Each step has its own timeout value defined in StepTimeouts. + */ + enum class ConnectionStep { + Init, // Initial state, nothing started + TcpConnect, // Establishing TCP connection to remote peer + TlsHandshake, // Performing SSL/TLS handshake + HttpWrite, // Sending HTTP upgrade request + HttpRead, // Reading HTTP upgrade response + Complete, // Connection successfully established + ShutdownStarted // Connection shutdown has started + }; + + // A timeout for connection process, greater than all step timeouts + static constexpr std::chrono::seconds connectTimeout{25}; + + /** + * @struct StepTimeouts + * @brief Defines timeout values for each connection step + * + * These timeouts are designed to detect slow individual phases while + * allowing the global timeout to enforce the overall time limit. + */ + struct StepTimeouts + { + // TCP connection timeout + static constexpr std::chrono::seconds tcpConnect{8}; + // SSL handshake timeout + static constexpr std::chrono::seconds tlsHandshake{8}; + // HTTP write timeout + static constexpr std::chrono::seconds httpWrite{3}; + // HTTP read timeout + static constexpr std::chrono::seconds httpRead{3}; + // SSL shutdown timeout + static constexpr std::chrono::seconds tlsShutdown{2}; + }; + + // Core application and networking components Application& app_; - std::uint32_t const id_; + Peer::id_t const id_; beast::WrappedSink sink_; beast::Journal const journal_; endpoint_type remote_endpoint_; Resource::Consumer usage_; + boost::asio::strand strand_; boost::asio::basic_waitable_timer timer_; - std::unique_ptr stream_ptr_; + boost::asio::basic_waitable_timer stepTimer_; + + std::unique_ptr stream_ptr_; // SSL stream (owned) socket_type& socket_; stream_type& stream_; boost::beast::multi_buffer read_buf_; + response_type response_; std::shared_ptr slot_; request_type req_; + bool shutdown_ = false; // Shutdown has been initiated + bool ioPending_ = false; // Async I/O operation in progress + ConnectionStep currentStep_ = ConnectionStep::Init; + public: + /** + * @brief Construct a new ConnectAttempt object + * + * @param app Application context providing configuration and services + * @param io_context ASIO I/O context for async operations + * @param remote_endpoint Target peer endpoint to connect to + * @param usage Resource usage tracker for rate limiting + * @param context Shared SSL context for encryption + * @param id Unique peer identifier for this connection attempt + * @param slot PeerFinder slot representing this connection + * @param journal Logging interface for diagnostics + * @param overlay Parent overlay manager + * + * @note The constructor only initializes the object. Call run() to begin + * the actual connection attempt. + */ ConnectAttempt( Application& app, boost::asio::io_context& io_context, endpoint_type const& remote_endpoint, Resource::Consumer usage, shared_context const& context, - std::uint32_t id, + Peer::id_t id, std::shared_ptr const& slot, beast::Journal journal, OverlayImpl& overlay); ~ConnectAttempt(); + /** + * @brief Stop the connection attempt + * + * This method is thread-safe and can be called from any thread. + */ void stop() override; + /** + * @brief Begin the connection attempt + * + * This method is thread-safe and posts to the strand if needed. + */ void run(); private: + /** + * @brief Set timers for the specified connection step + * + * @param step The connection step to set timers for + * + * Sets both the step-specific timer and the global timer (if not already + * set). + */ void - close(); - void - fail(std::string const& reason); - void - fail(std::string const& name, error_code ec); - void - setTimer(); + setTimer(ConnectionStep step); + + /** + * @brief Cancel both global and step timers + * + * Used during cleanup and when connection completes successfully. + * Exceptions from timer cancellation are safely ignored. + */ void cancelTimer(); + + /** + * @brief Handle timer expiration events + * + * @param ec Error code from timer operation + * + * Determines which timer expired (global vs step) and logs appropriate + * diagnostic information before terminating the connection. + */ void onTimer(error_code ec); + + // Connection phase handlers void - onConnect(error_code ec); + onConnect(error_code ec); // TCP connection completion handler void - onHandshake(error_code ec); + onHandshake(error_code ec); // TLS handshake completion handler void - onWrite(error_code ec); + onWrite(error_code ec); // HTTP write completion handler void - onRead(error_code ec); + onRead(error_code ec); // HTTP read completion handler + + // Error and cleanup handlers void - onShutdown(error_code ec); + fail(std::string const& reason); // Fail with custom reason + void + fail(std::string const& name, error_code ec); // Fail with system error + void + shutdown(); // Initiate graceful shutdown + void + tryAsyncShutdown(); // Attempt async SSL shutdown + void + onShutdown(error_code ec); // SSL shutdown completion handler + void + close(); // Force close socket + + /** + * @brief Process the HTTP upgrade response from peer + * + * Validates the peer's response, extracts protocol information, + * verifies handshake, and either creates a PeerImp or handles + * redirect responses. + */ void processResponse(); + static std::string + stepToString(ConnectionStep step) + { + switch (step) + { + case ConnectionStep::Init: + return "Init"; + case ConnectionStep::TcpConnect: + return "TcpConnect"; + case ConnectionStep::TlsHandshake: + return "TlsHandshake"; + case ConnectionStep::HttpWrite: + return "HttpWrite"; + case ConnectionStep::HttpRead: + return "HttpRead"; + case ConnectionStep::Complete: + return "Complete"; + case ConnectionStep::ShutdownStarted: + return "ShutdownStarted"; + } + return "Unknown"; + }; + template static boost::asio::ip::tcp::endpoint parse_endpoint(std::string const& s, boost::system::error_code& ec) diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 2cd9432eb8..93371f42ab 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -44,6 +44,7 @@ #include #include +#include #include #include #include @@ -59,6 +60,10 @@ std::chrono::milliseconds constexpr peerHighLatency{300}; /** How often we PING the peer to check for latency and sendq probe */ std::chrono::seconds constexpr peerTimerInterval{60}; + +/** The timeout for a shutdown timer */ +std::chrono::seconds constexpr shutdownTimerInterval{5}; + } // namespace // TODO: Remove this exclusion once unit tests are added after the hotfix @@ -215,23 +220,17 @@ PeerImp::stop() { if (!strand_.running_in_this_thread()) return post(strand_, std::bind(&PeerImp::stop, shared_from_this())); - if (socket_.is_open()) - { - // The rationale for using different severity levels is that - // outbound connections are under our control and may be logged - // at a higher level, but inbound connections are more numerous and - // uncontrolled so to prevent log flooding the severity is reduced. - // - if (inbound_) - { - JLOG(journal_.debug()) << "Stop"; - } - else - { - JLOG(journal_.info()) << "Stop"; - } - } - close(); + + if (!socket_.is_open()) + return; + + // The rationale for using different severity levels is that + // outbound connections are under our control and may be logged + // at a higher level, but inbound connections are more numerous and + // uncontrolled so to prevent log flooding the severity is reduced. + JLOG(journal_.debug()) << "stop: Stop"; + + shutdown(); } //------------------------------------------------------------------------------ @@ -241,11 +240,14 @@ PeerImp::send(std::shared_ptr const& m) { if (!strand_.running_in_this_thread()) return post(strand_, std::bind(&PeerImp::send, shared_from_this(), m)); - if (gracefulClose_) - return; - if (detaching_) + + if (!socket_.is_open()) return; + // we are in progress of closing the connection + if (shutdown_) + return tryAsyncShutdown(); + auto validator = m->getValidatorKey(); if (validator && !squelch_.expireSquelch(*validator)) { @@ -287,6 +289,7 @@ PeerImp::send(std::shared_ptr const& m) if (sendq_size != 0) return; + writePending_ = true; boost::asio::async_write( stream_, boost::asio::buffer( @@ -573,34 +576,21 @@ PeerImp::hasRange(std::uint32_t uMin, std::uint32_t uMax) //------------------------------------------------------------------------------ void -PeerImp::close() +PeerImp::fail(std::string const& name, error_code ec) { XRPL_ASSERT( strand_.running_in_this_thread(), - "ripple::PeerImp::close : strand in this thread"); - if (socket_.is_open()) - { - detaching_ = true; // DEPRECATED - try - { - timer_.cancel(); - socket_.close(); - } - catch (boost::system::system_error const&) - { - // ignored - } + "ripple::PeerImp::fail : strand in this thread"); - overlay_.incPeerDisconnect(); - if (inbound_) - { - JLOG(journal_.debug()) << "Closed"; - } - else - { - JLOG(journal_.info()) << "Closed"; - } - } + if (!socket_.is_open()) + return; + + JLOG(journal_.warn()) << name << " from " + << toBase58(TokenType::NodePublic, publicKey_) + << " at " << remote_address_.to_string() << ": " + << ec.message(); + + shutdown(); } void @@ -613,45 +603,39 @@ PeerImp::fail(std::string const& reason) (void(Peer::*)(std::string const&)) & PeerImp::fail, shared_from_this(), reason)); - if (journal_.active(beast::severities::kWarning) && socket_.is_open()) + + if (!socket_.is_open()) + return; + + // Call to name() locks, log only if the message will be outputed + if (journal_.active(beast::severities::kWarning)) { std::string const n = name(); JLOG(journal_.warn()) << (n.empty() ? remote_address_.to_string() : n) << " failed: " << reason; } - close(); + + shutdown(); } void -PeerImp::fail(std::string const& name, error_code ec) +PeerImp::tryAsyncShutdown() { XRPL_ASSERT( strand_.running_in_this_thread(), - "ripple::PeerImp::fail : strand in this thread"); - if (socket_.is_open()) - { - JLOG(journal_.warn()) - << name << " from " << toBase58(TokenType::NodePublic, publicKey_) - << " at " << remote_address_.to_string() << ": " << ec.message(); - } - close(); -} + "ripple::PeerImp::tryAsyncShutdown : strand in this thread"); -void -PeerImp::gracefulClose() -{ - XRPL_ASSERT( - strand_.running_in_this_thread(), - "ripple::PeerImp::gracefulClose : strand in this thread"); - XRPL_ASSERT( - socket_.is_open(), "ripple::PeerImp::gracefulClose : socket is open"); - XRPL_ASSERT( - !gracefulClose_, - "ripple::PeerImp::gracefulClose : socket is not closing"); - gracefulClose_ = true; - if (send_queue_.size() > 0) + if (!shutdown_ || shutdownStarted_) return; - setTimer(); + + if (readPending_ || writePending_) + return; + + shutdownStarted_ = true; + + setTimer(shutdownTimerInterval); + + // gracefully shutdown the SSL socket, performing a shutdown handshake stream_.async_shutdown(bind_executor( strand_, std::bind( @@ -659,69 +643,125 @@ PeerImp::gracefulClose() } void -PeerImp::setTimer() +PeerImp::shutdown() +{ + XRPL_ASSERT( + strand_.running_in_this_thread(), + "ripple::PeerImp::shutdown: strand in this thread"); + + if (!socket_.is_open() || shutdown_) + return; + + shutdown_ = true; + + boost::beast::get_lowest_layer(stream_).cancel(); + + tryAsyncShutdown(); +} + +void +PeerImp::onShutdown(error_code ec) +{ + cancelTimer(); + if (ec) + { + // - eof: the stream was cleanly closed + // - operation_aborted: an expired timer (slow shutdown) + // - stream_truncated: the tcp connection closed (no handshake) it could + // occur if a peer does not perform a graceful disconnect + // - broken_pipe: the peer is gone + bool shouldLog = + (ec != boost::asio::error::eof && + ec != boost::asio::error::operation_aborted && + ec.message().find("application data after close notify") == + std::string::npos); + + if (shouldLog) + { + JLOG(journal_.debug()) << "onShutdown: " << ec.message(); + } + } + + close(); +} + +void +PeerImp::close() +{ + XRPL_ASSERT( + strand_.running_in_this_thread(), + "ripple::PeerImp::close : strand in this thread"); + + if (!socket_.is_open()) + return; + + cancelTimer(); + + error_code ec; + socket_.close(ec); + + overlay_.incPeerDisconnect(); + + // The rationale for using different severity levels is that + // outbound connections are under our control and may be logged + // at a higher level, but inbound connections are more numerous and + // uncontrolled so to prevent log flooding the severity is reduced. + JLOG((inbound_ ? journal_.debug() : journal_.info())) << "close: Closed"; +} + +//------------------------------------------------------------------------------ + +void +PeerImp::setTimer(std::chrono::seconds interval) { try { - timer_.expires_after(peerTimerInterval); + timer_.expires_after(interval); } - catch (boost::system::system_error const& e) + catch (std::exception const& ex) { - JLOG(journal_.error()) << "setTimer: " << e.code(); - return; + JLOG(journal_.error()) << "setTimer: " << ex.what(); + return shutdown(); } + timer_.async_wait(bind_executor( strand_, std::bind( &PeerImp::onTimer, shared_from_this(), std::placeholders::_1))); } -// convenience for ignoring the error code -void -PeerImp::cancelTimer() -{ - try - { - timer_.cancel(); - } - catch (boost::system::system_error const&) - { - // ignored - } -} - -//------------------------------------------------------------------------------ - -std::string -PeerImp::makePrefix(id_t id) -{ - std::stringstream ss; - ss << "[" << std::setfill('0') << std::setw(3) << id << "] "; - return ss.str(); -} - void PeerImp::onTimer(error_code const& ec) { - if (!socket_.is_open()) - return; + XRPL_ASSERT( + strand_.running_in_this_thread(), + "ripple::PeerImp::onTimer : strand in this thread"); - if (ec == boost::asio::error::operation_aborted) + if (!socket_.is_open()) return; if (ec) { + // do not initiate shutdown, timers are frequently cancelled + if (ec == boost::asio::error::operation_aborted) + return; + // This should never happen JLOG(journal_.error()) << "onTimer: " << ec.message(); return close(); } - if (large_sendq_++ >= Tuning::sendqIntervals) + // the timer expired before the shutdown completed + // force close the connection + if (shutdown_) { - fail("Large send queue"); - return; + JLOG(journal_.debug()) << "onTimer: shutdown timer expired"; + return close(); } + if (large_sendq_++ >= Tuning::sendqIntervals) + return fail("Large send queue"); + if (auto const t = tracking_.load(); !inbound_ && t != Tracking::converged) { clock_type::duration duration; @@ -737,17 +777,13 @@ PeerImp::onTimer(error_code const& ec) (duration > app_.config().MAX_UNKNOWN_TIME))) { overlay_.peerFinder().on_failure(slot_); - fail("Not useful"); - return; + return fail("Not useful"); } } // Already waiting for PONG if (lastPingSeq_) - { - fail("Ping Timeout"); - return; - } + return fail("Ping Timeout"); lastPingTime_ = clock_type::now(); lastPingSeq_ = rand_int(); @@ -758,22 +794,28 @@ PeerImp::onTimer(error_code const& ec) send(std::make_shared(message, protocol::mtPING)); - setTimer(); + setTimer(peerTimerInterval); } void -PeerImp::onShutdown(error_code ec) +PeerImp::cancelTimer() noexcept { - cancelTimer(); - // If we don't get eof then something went wrong - if (!ec) + try { - JLOG(journal_.error()) << "onShutdown: expected error condition"; - return close(); + timer_.cancel(); } - if (ec != boost::asio::error::eof) - return fail("onShutdown", ec); - close(); + catch (std::exception const& ex) + { + JLOG(journal_.error()) << "cancelTimer: " << ex.what(); + } +} + +std::string +PeerImp::makePrefix(id_t id) +{ + std::stringstream ss; + ss << "[" << std::setfill('0') << std::setw(3) << id << "] "; + return ss.str(); } //------------------------------------------------------------------------------ @@ -786,6 +828,10 @@ PeerImp::doAccept() JLOG(journal_.debug()) << "doAccept: " << remote_address_; + // a shutdown was initiated before the handshake, there is nothing to do + if (shutdown_) + return tryAsyncShutdown(); + auto const sharedValue = makeSharedValue(*stream_ptr_, journal_); // This shouldn't fail since we already computed @@ -793,7 +839,7 @@ PeerImp::doAccept() if (!sharedValue) return fail("makeSharedValue: Unexpected failure"); - JLOG(journal_.info()) << "Protocol: " << to_string(protocol_); + JLOG(journal_.debug()) << "Protocol: " << to_string(protocol_); JLOG(journal_.info()) << "Public Key: " << toBase58(TokenType::NodePublic, publicKey_); @@ -836,7 +882,7 @@ PeerImp::doAccept() if (!socket_.is_open()) return; if (ec == boost::asio::error::operation_aborted) - return; + return tryAsyncShutdown(); if (ec) return fail("onWriteResponse", ec); if (write_buffer->size() == bytes_transferred) @@ -865,6 +911,10 @@ PeerImp::domain() const void PeerImp::doProtocolStart() { + // a shutdown was initiated before the handshare, there is nothing to do + if (shutdown_) + return tryAsyncShutdown(); + onReadMessage(error_code(), 0); // Send all the validator lists that have been loaded @@ -896,30 +946,45 @@ PeerImp::doProtocolStart() if (auto m = overlay_.getManifestsMessage()) send(m); - setTimer(); + setTimer(peerTimerInterval); } // Called repeatedly with protocol message data void PeerImp::onReadMessage(error_code ec, std::size_t bytes_transferred) { + XRPL_ASSERT( + strand_.running_in_this_thread(), + "ripple::PeerImp::onReadMessage : strand in this thread"); + + readPending_ = false; + if (!socket_.is_open()) return; - if (ec == boost::asio::error::operation_aborted) - return; - if (ec == boost::asio::error::eof) - { - JLOG(journal_.info()) << "EOF"; - return gracefulClose(); - } + if (ec) + { + if (ec == boost::asio::error::eof) + { + JLOG(journal_.debug()) << "EOF"; + return shutdown(); + } + + if (ec == boost::asio::error::operation_aborted) + return tryAsyncShutdown(); + return fail("onReadMessage", ec); + } + // we started shutdown, no reason to process further data + if (shutdown_) + return tryAsyncShutdown(); + if (auto stream = journal_.trace()) { - if (bytes_transferred > 0) - stream << "onReadMessage: " << bytes_transferred << " bytes"; - else - stream << "onReadMessage"; + stream << "onReadMessage: " + << (bytes_transferred > 0 + ? to_string(bytes_transferred) + " bytes" + : ""); } metrics_.recv.add_message(bytes_transferred); @@ -941,17 +1006,29 @@ PeerImp::onReadMessage(error_code ec, std::size_t bytes_transferred) 350ms, journal_); - if (ec) - return fail("onReadMessage", ec); if (!socket_.is_open()) return; - if (gracefulClose_) - return; + + // the error_code is produced by invokeProtocolMessage + // it could be due to a bad message + if (ec) + return fail("onReadMessage", ec); + if (bytes_consumed == 0) break; + read_buffer_.consume(bytes_consumed); } + // check if a shutdown was initiated while processing messages + if (shutdown_) + return tryAsyncShutdown(); + + readPending_ = true; + + XRPL_ASSERT( + !shutdownStarted_, "ripple::PeerImp::onReadMessage : shutdown started"); + // Timeout on writes only stream_.async_read_some( read_buffer_.prepare(std::max(Tuning::readBufferBytes, hint)), @@ -967,18 +1044,29 @@ PeerImp::onReadMessage(error_code ec, std::size_t bytes_transferred) void PeerImp::onWriteMessage(error_code ec, std::size_t bytes_transferred) { + XRPL_ASSERT( + strand_.running_in_this_thread(), + "ripple::PeerImp::onWriteMessage : strand in this thread"); + + writePending_ = false; + if (!socket_.is_open()) return; - if (ec == boost::asio::error::operation_aborted) - return; + if (ec) + { + if (ec == boost::asio::error::operation_aborted) + return tryAsyncShutdown(); + return fail("onWriteMessage", ec); + } + if (auto stream = journal_.trace()) { - if (bytes_transferred > 0) - stream << "onWriteMessage: " << bytes_transferred << " bytes"; - else - stream << "onWriteMessage"; + stream << "onWriteMessage: " + << (bytes_transferred > 0 + ? to_string(bytes_transferred) + " bytes" + : ""); } metrics_.sent.add_message(bytes_transferred); @@ -987,8 +1075,17 @@ PeerImp::onWriteMessage(error_code ec, std::size_t bytes_transferred) !send_queue_.empty(), "ripple::PeerImp::onWriteMessage : non-empty send buffer"); send_queue_.pop(); + + if (shutdown_) + return tryAsyncShutdown(); + if (!send_queue_.empty()) { + writePending_ = true; + XRPL_ASSERT( + !shutdownStarted_, + "ripple::PeerImp::onWriteMessage : shutdown started"); + // Timeout on writes only return boost::asio::async_write( stream_, @@ -1002,16 +1099,6 @@ PeerImp::onWriteMessage(error_code ec, std::size_t bytes_transferred) std::placeholders::_1, std::placeholders::_2))); } - - if (gracefulClose_) - { - return stream_.async_shutdown(bind_executor( - strand_, - std::bind( - &PeerImp::onShutdown, - shared_from_this(), - std::placeholders::_1))); - } } //------------------------------------------------------------------------------ diff --git a/src/xrpld/overlay/detail/PeerImp.h b/src/xrpld/overlay/detail/PeerImp.h index 3d9a0c0b1e..c2221c136d 100644 --- a/src/xrpld/overlay/detail/PeerImp.h +++ b/src/xrpld/overlay/detail/PeerImp.h @@ -40,6 +40,7 @@ #include #include +#include #include #include #include @@ -49,6 +50,68 @@ namespace ripple { struct ValidatorBlobInfo; class SHAMap; +/** + * @class PeerImp + * @brief This class manages established peer-to-peer connections, handles + message exchange, monitors connection health, and graceful shutdown. + * + + * The PeerImp shutdown mechanism is a multi-stage process + * designed to ensure graceful connection termination while handling ongoing + * I/O operations safely. The shutdown can be initiated from multiple points + * and follows a deterministic state machine. + * + * The shutdown process can be triggered from several entry points: + * - **External requests**: `stop()` method called by overlay management + * - **Error conditions**: `fail(error_code)` or `fail(string)` on protocol + * violations + * - **Timer expiration**: Various timeout scenarios (ping timeout, large send + * queue) + * - **Connection health**: Peer tracking divergence or unknown state timeouts + * + * The shutdown follows this progression: + * + * Normal Operation → shutdown() → tryAsyncShutdown() → onShutdown() → close() + * ↓ ↓ ↓ ↓ + * Set shutdown_ SSL graceful Timer cancel Socket close + * Cancel timer shutdown start & cleanup & metrics + * 5s safety timer Set shutdownStarted_ update + * + * Two primary flags coordinate the shutdown process: + * - `shutdown_`: Set when shutdown is requested + * - `shutdownStarted_`: Set when SSL shutdown begins + * + * The shutdown mechanism carefully coordinates with ongoing read/write + * operations: + * + * **Read Operations (`onReadMessage`)**: + * - Checks `shutdown_` flag after processing each message batch + * - If shutdown initiated during processing, calls `tryAsyncShutdown()` + * + * **Write Operations (`onWriteMessage`)**: + * - Checks `shutdown_` flag before queuing new writes + * - Calls `tryAsyncShutdown()` when shutdown flag detected + * + * Multiple timers require coordination during shutdown: + * 1. **Peer Timer**: Regular ping/pong timer cancelled immediately in + * `shutdown()` + * 2. **Shutdown Timer**: 5-second safety timer ensures shutdown completion + * 3. **Operation Cancellation**: All pending async operations are cancelled + * + * The shutdown implements fallback mechanisms: + * - **Graceful Path**: SSL shutdown → Socket close → Cleanup + * - **Forced Path**: If SSL shutdown fails or times out, proceeds to socket + * close + * - **Safety Timer**: 5-second timeout prevents hanging shutdowns + * + * All shutdown operations are serialized through the boost::asio::strand to + * ensure thread safety. The strand guarantees that shutdown state changes + * and I/O operation callbacks are executed sequentially. + * + * @note This class requires careful coordination between async operations, + * timer management, and shutdown procedures to ensure no resource leaks + * or hanging connections in high-throughput networking scenarios. + */ class PeerImp : public Peer, public std::enable_shared_from_this, public OverlayImpl::Child @@ -79,6 +142,8 @@ private: socket_type& socket_; stream_type& stream_; boost::asio::strand strand_; + + // Multi-purpose timer for peer activity monitoring and shutdown safety waitable_timer timer_; // Updated at each stage of the connection process to reflect @@ -95,7 +160,6 @@ private: std::atomic tracking_; clock_type::time_point trackingTime_; - bool detaching_ = false; // Node public key of peer. PublicKey const publicKey_; std::string name_; @@ -175,7 +239,19 @@ private: http_response_type response_; boost::beast::http::fields const& headers_; std::queue> send_queue_; - bool gracefulClose_ = false; + + // Primary shutdown flag set when shutdown is requested + bool shutdown_ = false; + + // SSL shutdown coordination flag + bool shutdownStarted_ = false; + + // Indicates a read operation is currently pending + bool readPending_ = false; + + // Indicates a write operation is currently pending + bool writePending_ = false; + int large_sendq_ = 0; std::unique_ptr load_event_; // The highest sequence of each PublisherList that has @@ -425,9 +501,6 @@ public: bool isHighLatency() const override; - void - fail(std::string const& reason); - bool compressionEnabled() const override { @@ -441,32 +514,129 @@ public: } private: - void - close(); - + /** + * @brief Handles a failure associated with a specific error code. + * + * This function is called when an operation fails with an error code. It + * logs the warning message and gracefully shutdowns the connection. + * + * The function will do nothing if the connection is already closed or if a + * shutdown is already in progress. + * + * @param name The name of the operation that failed (e.g., "read", + * "write"). + * @param ec The error code associated with the failure. + * @note This function must be called from within the object's strand. + */ void fail(std::string const& name, error_code ec); + /** + * @brief Handles a failure described by a reason string. + * + * This overload is used for logical errors or protocol violations not + * associated with a specific error code. It logs a warning with the + * given reason, then initiates a graceful shutdown. + * + * The function will do nothing if the connection is already closed or if a + * shutdown is already in progress. + * + * @param reason A descriptive string explaining the reason for the failure. + * @note This function must be called from within the object's strand. + */ void - gracefulClose(); + fail(std::string const& reason); + /** @brief Initiates the peer disconnection sequence. + * + * This is the primary entry point to start closing a peer connection. It + * marks the peer for shutdown and cancels any outstanding asynchronous + * operations. This cancellation allows the graceful shutdown to proceed + * once the handlers for the cancelled operations have completed. + * + * @note This method must be called on the peer's strand. + */ void - setTimer(); + shutdown(); + /** @brief Attempts to perform a graceful SSL shutdown if conditions are + * met. + * + * This helper function checks if the peer is in a state where a graceful + * SSL shutdown can be performed (i.e., shutdown has been requested and no + * I/O operations are currently in progress). + * + * @note This method must be called on the peer's strand. + */ void - cancelTimer(); + tryAsyncShutdown(); + + /** + * @brief Handles the completion of the asynchronous SSL shutdown. + * + * This function is the callback for the `async_shutdown` operation started + * in `shutdown()`. Its first action is to cancel the timer. It + * then inspects the error code to determine the outcome. + * + * Regardless of the result, this function proceeds to call `close()` to + * ensure the underlying socket is fully closed. + * + * @param ec The error code resulting from the `async_shutdown` operation. + */ + void + onShutdown(error_code ec); + + /** + * @brief Forcibly closes the underlying socket connection. + * + * This function provides the final, non-graceful shutdown of the peer + * connection. It ensures any pending timers are cancelled and then + * immediately closes the TCP socket, bypassing the SSL shutdown handshake. + * + * After closing, it notifies the overlay manager of the disconnection. + * + * @note This function must be called from within the object's strand. + */ + void + close(); + + /** + * @brief Sets and starts the peer timer. + * + * This function starts timer, which is used to detect inactivity + * and prevent stalled connections. It sets the timer to expire after the + * predefined `peerTimerInterval`. + * + * @note This function will terminate the connection in case of any errors. + */ + void + setTimer(std::chrono::seconds interval); + + /** + * @brief Handles the expiration of the peer activity timer. + * + * This callback is invoked when the timer set by `setTimer` expires. It + * watches the peer connection, checking for various timeout and health + * conditions. + * + * @param ec The error code associated with the timer's expiration. + * `operation_aborted` is expected if the timer was cancelled. + */ + void + onTimer(error_code const& ec); + + /** + * @brief Cancels any pending wait on the peer activity timer. + * + * This function is called to stop the timer. It gracefully manages any + * errors that might occur during the cancellation process. + */ + void + cancelTimer() noexcept; static std::string makePrefix(id_t id); - // Called when the timer wait completes - void - onTimer(boost::system::error_code const& ec); - - // Called when SSL shutdown completes - void - onShutdown(error_code ec); - void doAccept(); From 1020a32d76d1a91eb7ad11d1497f731edb36e05f Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Wed, 3 Sep 2025 11:12:43 +0100 Subject: [PATCH 15/24] Downgrade to boost 1.83 --- conan/profiles/default | 3 +++ conanfile.py | 2 +- src/test/server/ServerStatus_test.cpp | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/conan/profiles/default b/conan/profiles/default index 3a7bcda1c6..0417704f8a 100644 --- a/conan/profiles/default +++ b/conan/profiles/default @@ -26,6 +26,9 @@ tools.build:cxxflags=['-Wno-missing-template-arg-list-after-template-kw'] {% if compiler == "apple-clang" and compiler_version >= 17 %} tools.build:cxxflags=['-Wno-missing-template-arg-list-after-template-kw'] {% endif %} +{% if compiler == "clang" and compiler_version == 16 %} +tools.build:cxxflags=['-DBOOST_ASIO_DISABLE_CONCEPTS'] +{% endif %} {% if compiler == "gcc" and compiler_version < 13 %} tools.build:cxxflags=['-Wno-restrict'] {% endif %} diff --git a/conanfile.py b/conanfile.py index ab4657277c..bff4c93aa3 100644 --- a/conanfile.py +++ b/conanfile.py @@ -104,7 +104,7 @@ class Xrpl(ConanFile): def requirements(self): # Conan 2 requires transitive headers to be specified transitive_headers_opt = {'transitive_headers': True} if conan_version.split('.')[0] == '2' else {} - self.requires('boost/1.86.0', force=True, **transitive_headers_opt) + self.requires('boost/1.83.0', force=True, **transitive_headers_opt) self.requires('date/3.0.4', **transitive_headers_opt) self.requires('lz4/1.10.0', force=True) self.requires('protobuf/3.21.12', force=True) diff --git a/src/test/server/ServerStatus_test.cpp b/src/test/server/ServerStatus_test.cpp index b27dee6e0a..bcd355e301 100644 --- a/src/test/server/ServerStatus_test.cpp +++ b/src/test/server/ServerStatus_test.cpp @@ -681,7 +681,7 @@ class ServerStatus_test : public beast::unit_test::suite, resp["Upgrade"] == "websocket"); BEAST_EXPECT( resp.find("Connection") != resp.end() && - resp["Connection"] == "Upgrade"); + resp["Connection"] == "upgrade"); } void From 8d01f35eb9c516aab4c93a0288f8d599370501af Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Wed, 3 Sep 2025 15:39:50 +0100 Subject: [PATCH 16/24] Set version to 2.6.1-rc1 --- src/libxrpl/protocol/BuildInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libxrpl/protocol/BuildInfo.cpp b/src/libxrpl/protocol/BuildInfo.cpp index d5077aa44d..ae15ea6dec 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -36,7 +36,7 @@ namespace BuildInfo { // and follow the format described at http://semver.org/ //------------------------------------------------------------------------------ // clang-format off -char const* const versionString = "2.6.0" +char const* const versionString = "2.6.1-rc1" // clang-format on #if defined(DEBUG) || defined(SANITIZER) From 9494fc9668707b767ed0897f20fe53a33b30d726 Mon Sep 17 00:00:00 2001 From: Jingchen Date: Wed, 17 Sep 2025 14:29:15 +0100 Subject: [PATCH 17/24] chore: Use self hosted windows runners (#5780) This changes switches from the GitHub-managed Windows runners to self-hosted runners to significantly reduce build time. --- .github/scripts/strategy-matrix/windows.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/strategy-matrix/windows.json b/.github/scripts/strategy-matrix/windows.json index 5e6e536750..08b41e3f89 100644 --- a/.github/scripts/strategy-matrix/windows.json +++ b/.github/scripts/strategy-matrix/windows.json @@ -2,7 +2,7 @@ "architecture": [ { "platform": "windows/amd64", - "runner": ["windows-latest"] + "runner": ["self-hosted", "Windows", "devbox"] } ], "os": [ From 37b951859ccf490354a620b83637ed9cefa24f68 Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Wed, 17 Sep 2025 16:43:04 -0400 Subject: [PATCH 18/24] Rename mutable flags (#5797) This is a minor change on top of #5705 --- include/xrpl/protocol/LedgerFormats.h | 16 +- include/xrpl/protocol/TxFlags.h | 54 ++--- src/test/app/MPToken_test.cpp | 199 +++++++++--------- src/test/jtx/impl/mpt.cpp | 24 +-- .../app/tx/detail/MPTokenIssuanceCreate.cpp | 2 +- .../app/tx/detail/MPTokenIssuanceSet.cpp | 26 +-- 6 files changed, 166 insertions(+), 155 deletions(-) diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index 711754df94..7cf92d0822 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -188,14 +188,14 @@ enum LedgerSpecificFlags { lsfMPTCanTransfer = 0x00000020, lsfMPTCanClawback = 0x00000040, - lsfMPTCanMutateCanLock = 0x00000002, - lsfMPTCanMutateRequireAuth = 0x00000004, - lsfMPTCanMutateCanEscrow = 0x00000008, - lsfMPTCanMutateCanTrade = 0x00000010, - lsfMPTCanMutateCanTransfer = 0x00000020, - lsfMPTCanMutateCanClawback = 0x00000040, - lsfMPTCanMutateMetadata = 0x00010000, - lsfMPTCanMutateTransferFee = 0x00020000, + lmfMPTCanMutateCanLock = 0x00000002, + lmfMPTCanMutateRequireAuth = 0x00000004, + lmfMPTCanMutateCanEscrow = 0x00000008, + lmfMPTCanMutateCanTrade = 0x00000010, + lmfMPTCanMutateCanTransfer = 0x00000020, + lmfMPTCanMutateCanClawback = 0x00000040, + lmfMPTCanMutateMetadata = 0x00010000, + lmfMPTCanMutateTransferFee = 0x00020000, // ltMPTOKEN lsfMPTAuthorized = 0x00000002, diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index c376180ac0..70c6833d3a 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -153,17 +153,17 @@ constexpr std::uint32_t const tfMPTokenIssuanceCreateMask = // MPTokenIssuanceCreate MutableFlags: // Indicating specific fields or flags may be changed after issuance. -constexpr std::uint32_t const tfMPTCanMutateCanLock = lsfMPTCanMutateCanLock; -constexpr std::uint32_t const tfMPTCanMutateRequireAuth = lsfMPTCanMutateRequireAuth; -constexpr std::uint32_t const tfMPTCanMutateCanEscrow = lsfMPTCanMutateCanEscrow; -constexpr std::uint32_t const tfMPTCanMutateCanTrade = lsfMPTCanMutateCanTrade; -constexpr std::uint32_t const tfMPTCanMutateCanTransfer = lsfMPTCanMutateCanTransfer; -constexpr std::uint32_t const tfMPTCanMutateCanClawback = lsfMPTCanMutateCanClawback; -constexpr std::uint32_t const tfMPTCanMutateMetadata = lsfMPTCanMutateMetadata; -constexpr std::uint32_t const tfMPTCanMutateTransferFee = lsfMPTCanMutateTransferFee; -constexpr std::uint32_t const tfMPTokenIssuanceCreateMutableMask = - ~(tfMPTCanMutateCanLock | tfMPTCanMutateRequireAuth | tfMPTCanMutateCanEscrow | tfMPTCanMutateCanTrade - | tfMPTCanMutateCanTransfer | tfMPTCanMutateCanClawback | tfMPTCanMutateMetadata | tfMPTCanMutateTransferFee); +constexpr std::uint32_t const tmfMPTCanMutateCanLock = lmfMPTCanMutateCanLock; +constexpr std::uint32_t const tmfMPTCanMutateRequireAuth = lmfMPTCanMutateRequireAuth; +constexpr std::uint32_t const tmfMPTCanMutateCanEscrow = lmfMPTCanMutateCanEscrow; +constexpr std::uint32_t const tmfMPTCanMutateCanTrade = lmfMPTCanMutateCanTrade; +constexpr std::uint32_t const tmfMPTCanMutateCanTransfer = lmfMPTCanMutateCanTransfer; +constexpr std::uint32_t const tmfMPTCanMutateCanClawback = lmfMPTCanMutateCanClawback; +constexpr std::uint32_t const tmfMPTCanMutateMetadata = lmfMPTCanMutateMetadata; +constexpr std::uint32_t const tmfMPTCanMutateTransferFee = lmfMPTCanMutateTransferFee; +constexpr std::uint32_t const tmfMPTokenIssuanceCreateMutableMask = + ~(tmfMPTCanMutateCanLock | tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanEscrow | tmfMPTCanMutateCanTrade + | tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanClawback | tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee); // MPTokenAuthorize flags: constexpr std::uint32_t const tfMPTUnauthorize = 0x00000001; @@ -177,22 +177,22 @@ constexpr std::uint32_t const tfMPTokenIssuanceSetPermissionMask = ~(tfUniversal // MPTokenIssuanceSet MutableFlags: // Set or Clear flags. -constexpr std::uint32_t const tfMPTSetCanLock = 0x00000001; -constexpr std::uint32_t const tfMPTClearCanLock = 0x00000002; -constexpr std::uint32_t const tfMPTSetRequireAuth = 0x00000004; -constexpr std::uint32_t const tfMPTClearRequireAuth = 0x00000008; -constexpr std::uint32_t const tfMPTSetCanEscrow = 0x00000010; -constexpr std::uint32_t const tfMPTClearCanEscrow = 0x00000020; -constexpr std::uint32_t const tfMPTSetCanTrade = 0x00000040; -constexpr std::uint32_t const tfMPTClearCanTrade = 0x00000080; -constexpr std::uint32_t const tfMPTSetCanTransfer = 0x00000100; -constexpr std::uint32_t const tfMPTClearCanTransfer = 0x00000200; -constexpr std::uint32_t const tfMPTSetCanClawback = 0x00000400; -constexpr std::uint32_t const tfMPTClearCanClawback = 0x00000800; -constexpr std::uint32_t const tfMPTokenIssuanceSetMutableMask = ~(tfMPTSetCanLock | tfMPTClearCanLock | - tfMPTSetRequireAuth | tfMPTClearRequireAuth | tfMPTSetCanEscrow | tfMPTClearCanEscrow | - tfMPTSetCanTrade | tfMPTClearCanTrade | tfMPTSetCanTransfer | tfMPTClearCanTransfer | - tfMPTSetCanClawback | tfMPTClearCanClawback); +constexpr std::uint32_t const tmfMPTSetCanLock = 0x00000001; +constexpr std::uint32_t const tmfMPTClearCanLock = 0x00000002; +constexpr std::uint32_t const tmfMPTSetRequireAuth = 0x00000004; +constexpr std::uint32_t const tmfMPTClearRequireAuth = 0x00000008; +constexpr std::uint32_t const tmfMPTSetCanEscrow = 0x00000010; +constexpr std::uint32_t const tmfMPTClearCanEscrow = 0x00000020; +constexpr std::uint32_t const tmfMPTSetCanTrade = 0x00000040; +constexpr std::uint32_t const tmfMPTClearCanTrade = 0x00000080; +constexpr std::uint32_t const tmfMPTSetCanTransfer = 0x00000100; +constexpr std::uint32_t const tmfMPTClearCanTransfer = 0x00000200; +constexpr std::uint32_t const tmfMPTSetCanClawback = 0x00000400; +constexpr std::uint32_t const tmfMPTClearCanClawback = 0x00000800; +constexpr std::uint32_t const tmfMPTokenIssuanceSetMutableMask = ~(tmfMPTSetCanLock | tmfMPTClearCanLock | + tmfMPTSetRequireAuth | tmfMPTClearRequireAuth | tmfMPTSetCanEscrow | tmfMPTClearCanEscrow | + tmfMPTSetCanTrade | tmfMPTClearCanTrade | tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | + tmfMPTSetCanClawback | tmfMPTClearCanClawback); // MPTokenIssuanceDestroy flags: constexpr std::uint32_t const tfMPTokenIssuanceDestroyMask = ~tfUniversal; diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 1410370c33..e9740e67de 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -2871,8 +2871,8 @@ class MPToken_test : public beast::unit_test::suite MPTTester mptAlice(env, alice, {.holders = {bob}}); mptAlice.create( {.ownerCount = 1, - .mutableFlags = tfMPTCanMutateMetadata | - tfMPTCanMutateCanLock | tfMPTCanMutateTransferFee}); + .mutableFlags = tmfMPTCanMutateMetadata | + tmfMPTCanMutateCanLock | tmfMPTCanMutateTransferFee}); // Setting flags is not allowed when MutableFlags is present mptAlice.set( @@ -2906,7 +2906,7 @@ class MPToken_test : public beast::unit_test::suite .ownerCount = 1, .flags = tfMPTCanTransfer, .mutableFlags = - tfMPTCanMutateTransferFee | tfMPTCanMutateMetadata}); + tmfMPTCanMutateTransferFee | tmfMPTCanMutateMetadata}); mptAlice.set( {.account = alice, @@ -2943,15 +2943,15 @@ class MPToken_test : public beast::unit_test::suite auto const mptID = makeMptID(env.seq(alice), alice); auto const flagCombinations = { - tfMPTSetCanLock | tfMPTClearCanLock, - tfMPTSetRequireAuth | tfMPTClearRequireAuth, - tfMPTSetCanEscrow | tfMPTClearCanEscrow, - tfMPTSetCanTrade | tfMPTClearCanTrade, - tfMPTSetCanTransfer | tfMPTClearCanTransfer, - tfMPTSetCanClawback | tfMPTClearCanClawback, - tfMPTSetCanLock | tfMPTClearCanLock | tfMPTClearCanTrade, - tfMPTSetCanTransfer | tfMPTClearCanTransfer | - tfMPTSetCanEscrow | tfMPTClearCanClawback}; + tmfMPTSetCanLock | tmfMPTClearCanLock, + tmfMPTSetRequireAuth | tmfMPTClearRequireAuth, + tmfMPTSetCanEscrow | tmfMPTClearCanEscrow, + tmfMPTSetCanTrade | tmfMPTClearCanTrade, + tmfMPTSetCanTransfer | tmfMPTClearCanTransfer, + tmfMPTSetCanClawback | tmfMPTClearCanClawback, + tmfMPTSetCanLock | tmfMPTClearCanLock | tmfMPTClearCanTrade, + tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | + tmfMPTSetCanEscrow | tmfMPTClearCanClawback}; for (auto const& mutableFlags : flagCombinations) { @@ -2971,18 +2971,18 @@ class MPToken_test : public beast::unit_test::suite mptAlice.create({.ownerCount = 1}); auto const mutableFlags = { - tfMPTSetCanLock, - tfMPTClearCanLock, - tfMPTSetRequireAuth, - tfMPTClearRequireAuth, - tfMPTSetCanEscrow, - tfMPTClearCanEscrow, - tfMPTSetCanTrade, - tfMPTClearCanTrade, - tfMPTSetCanTransfer, - tfMPTClearCanTransfer, - tfMPTSetCanClawback, - tfMPTClearCanClawback}; + tmfMPTSetCanLock, + tmfMPTClearCanLock, + tmfMPTSetRequireAuth, + tmfMPTClearRequireAuth, + tmfMPTSetCanEscrow, + tmfMPTClearCanEscrow, + tmfMPTSetCanTrade, + tmfMPTClearCanTrade, + tmfMPTSetCanTransfer, + tmfMPTClearCanTransfer, + tmfMPTSetCanClawback, + tmfMPTClearCanClawback}; for (auto const& mutableFlag : mutableFlags) { @@ -2999,7 +2999,7 @@ class MPToken_test : public beast::unit_test::suite MPTTester mptAlice(env, alice, {.holders = {bob}}); mptAlice.create( - {.ownerCount = 1, .mutableFlags = tfMPTCanMutateMetadata}); + {.ownerCount = 1, .mutableFlags = tmfMPTCanMutateMetadata}); std::string metadata(maxMPTokenMetadataLength + 1, 'a'); mptAlice.set( @@ -3025,7 +3025,7 @@ class MPToken_test : public beast::unit_test::suite auto const mptID = makeMptID(env.seq(alice), alice); mptAlice.create( - {.ownerCount = 1, .mutableFlags = tfMPTCanMutateTransferFee}); + {.ownerCount = 1, .mutableFlags = tmfMPTCanMutateTransferFee}); mptAlice.set( {.account = alice, @@ -3045,13 +3045,13 @@ class MPToken_test : public beast::unit_test::suite .ownerCount = 1, .flags = tfMPTCanTransfer, .mutableFlags = - tfMPTCanMutateTransferFee | tfMPTCanMutateCanTransfer}); + tmfMPTCanMutateTransferFee | tmfMPTCanMutateCanTransfer}); // Can not set non-zero transfer fee and clear MPTCanTransfer at the // same time mptAlice.set( {.account = alice, - .mutableFlags = tfMPTClearCanTransfer, + .mutableFlags = tmfMPTClearCanTransfer, .transferFee = 1, .err = temMALFORMED}); @@ -3060,7 +3060,7 @@ class MPToken_test : public beast::unit_test::suite // be removed. mptAlice.set( {.account = alice, - .mutableFlags = tfMPTClearCanTransfer, + .mutableFlags = tmfMPTClearCanTransfer, .transferFee = 0}); BEAST_EXPECT(!mptAlice.isTransferFeePresent()); } @@ -3073,7 +3073,7 @@ class MPToken_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .mutableFlags = - tfMPTCanMutateTransferFee | tfMPTCanMutateCanTransfer}); + tmfMPTCanMutateTransferFee | tmfMPTCanMutateCanTransfer}); mptAlice.set( {.account = alice, @@ -3085,7 +3085,7 @@ class MPToken_test : public beast::unit_test::suite // fee can be set in a separate transaction. mptAlice.set( {.account = alice, - .mutableFlags = tfMPTSetCanTransfer, + .mutableFlags = tmfMPTSetCanTransfer, .transferFee = 100, .err = tecNO_PERMISSION}); } @@ -3116,8 +3116,8 @@ class MPToken_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, - .mutableFlags = tfMPTCanMutateCanTrade | - tfMPTCanMutateCanTransfer | tfMPTCanMutateMetadata}); + .mutableFlags = tmfMPTCanMutateCanTrade | + tmfMPTCanMutateCanTransfer | tmfMPTCanMutateMetadata}); // Can not mutate transfer fee mptAlice.set( @@ -3126,14 +3126,14 @@ class MPToken_test : public beast::unit_test::suite .err = tecNO_PERMISSION}); auto const invalidFlags = { - tfMPTSetCanLock, - tfMPTClearCanLock, - tfMPTSetRequireAuth, - tfMPTClearRequireAuth, - tfMPTSetCanEscrow, - tfMPTClearCanEscrow, - tfMPTSetCanClawback, - tfMPTClearCanClawback}; + tmfMPTSetCanLock, + tmfMPTClearCanLock, + tmfMPTSetRequireAuth, + tmfMPTClearRequireAuth, + tmfMPTSetCanEscrow, + tmfMPTClearCanEscrow, + tmfMPTSetCanClawback, + tmfMPTClearCanClawback}; // Can not mutate flags which are not mutable for (auto const& mutableFlag : invalidFlags) @@ -3145,15 +3145,15 @@ class MPToken_test : public beast::unit_test::suite } // Can mutate MPTCanTrade - mptAlice.set({.account = alice, .mutableFlags = tfMPTSetCanTrade}); + mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanTrade}); mptAlice.set( - {.account = alice, .mutableFlags = tfMPTClearCanTrade}); + {.account = alice, .mutableFlags = tmfMPTClearCanTrade}); // Can mutate MPTCanTransfer mptAlice.set( - {.account = alice, .mutableFlags = tfMPTSetCanTransfer}); + {.account = alice, .mutableFlags = tmfMPTSetCanTransfer}); mptAlice.set( - {.account = alice, .mutableFlags = tfMPTClearCanTransfer}); + {.account = alice, .mutableFlags = tmfMPTClearCanTransfer}); // Can mutate metadata mptAlice.set({.account = alice, .metadata = "test"}); @@ -3176,7 +3176,7 @@ class MPToken_test : public beast::unit_test::suite mptAlice.create( {.metadata = "test", .ownerCount = 1, - .mutableFlags = tfMPTCanMutateMetadata}); + .mutableFlags = tmfMPTCanMutateMetadata}); std::vector metadatas = { "mutate metadata", @@ -3206,7 +3206,7 @@ class MPToken_test : public beast::unit_test::suite .metadata = "test", .ownerCount = 1, .flags = tfMPTCanTransfer, - .mutableFlags = tfMPTCanMutateTransferFee}); + .mutableFlags = tmfMPTCanMutateTransferFee}); for (std::uint16_t const fee : std::initializer_list{ 1, 10, 100, 200, 500, 1000, maxTransferFee}) @@ -3250,25 +3250,27 @@ class MPToken_test : public beast::unit_test::suite }; testFlagToggle( - tfMPTCanMutateCanLock, tfMPTCanLock, tfMPTClearCanLock); + tmfMPTCanMutateCanLock, tfMPTCanLock, tmfMPTClearCanLock); testFlagToggle( - tfMPTCanMutateRequireAuth, - tfMPTSetRequireAuth, - tfMPTClearRequireAuth); + tmfMPTCanMutateRequireAuth, + tmfMPTSetRequireAuth, + tmfMPTClearRequireAuth); testFlagToggle( - tfMPTCanMutateCanEscrow, - tfMPTSetCanEscrow, - tfMPTClearCanEscrow); + tmfMPTCanMutateCanEscrow, + tmfMPTSetCanEscrow, + tmfMPTClearCanEscrow); testFlagToggle( - tfMPTCanMutateCanTrade, tfMPTSetCanTrade, tfMPTClearCanTrade); + tmfMPTCanMutateCanTrade, + tmfMPTSetCanTrade, + tmfMPTClearCanTrade); testFlagToggle( - tfMPTCanMutateCanTransfer, - tfMPTSetCanTransfer, - tfMPTClearCanTransfer); + tmfMPTCanMutateCanTransfer, + tmfMPTSetCanTransfer, + tmfMPTClearCanTransfer); testFlagToggle( - tfMPTCanMutateCanClawback, - tfMPTSetCanClawback, - tfMPTClearCanClawback); + tmfMPTCanMutateCanClawback, + tmfMPTSetCanClawback, + tmfMPTClearCanClawback); } } @@ -3289,20 +3291,22 @@ class MPToken_test : public beast::unit_test::suite {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock | tfMPTCanTransfer, - .mutableFlags = tfMPTCanMutateCanLock | - tfMPTCanMutateCanTrade | tfMPTCanMutateTransferFee}); + .mutableFlags = tmfMPTCanMutateCanLock | + tmfMPTCanMutateCanTrade | tmfMPTCanMutateTransferFee}); mptAlice.authorize({.account = bob, .holderCount = 1}); // Lock bob's mptoken mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); // Can mutate the mutable flags and fields - mptAlice.set({.account = alice, .mutableFlags = tfMPTClearCanLock}); - mptAlice.set({.account = alice, .mutableFlags = tfMPTSetCanLock}); - mptAlice.set({.account = alice, .mutableFlags = tfMPTClearCanLock}); - mptAlice.set({.account = alice, .mutableFlags = tfMPTSetCanTrade}); mptAlice.set( - {.account = alice, .mutableFlags = tfMPTClearCanTrade}); + {.account = alice, .mutableFlags = tmfMPTClearCanLock}); + mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanLock}); + mptAlice.set( + {.account = alice, .mutableFlags = tmfMPTClearCanLock}); + mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanTrade}); + mptAlice.set( + {.account = alice, .mutableFlags = tmfMPTClearCanTrade}); mptAlice.set({.account = alice, .transferFee = 200}); } @@ -3314,21 +3318,23 @@ class MPToken_test : public beast::unit_test::suite {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock, - .mutableFlags = tfMPTCanMutateCanLock | - tfMPTCanMutateCanClawback | tfMPTCanMutateMetadata}); + .mutableFlags = tmfMPTCanMutateCanLock | + tmfMPTCanMutateCanClawback | tmfMPTCanMutateMetadata}); mptAlice.authorize({.account = bob, .holderCount = 1}); // Lock issuance mptAlice.set({.account = alice, .flags = tfMPTLock}); // Can mutate the mutable flags and fields - mptAlice.set({.account = alice, .mutableFlags = tfMPTClearCanLock}); - mptAlice.set({.account = alice, .mutableFlags = tfMPTSetCanLock}); - mptAlice.set({.account = alice, .mutableFlags = tfMPTClearCanLock}); mptAlice.set( - {.account = alice, .mutableFlags = tfMPTSetCanClawback}); + {.account = alice, .mutableFlags = tmfMPTClearCanLock}); + mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanLock}); mptAlice.set( - {.account = alice, .mutableFlags = tfMPTClearCanClawback}); + {.account = alice, .mutableFlags = tmfMPTClearCanLock}); + mptAlice.set( + {.account = alice, .mutableFlags = tmfMPTSetCanClawback}); + mptAlice.set( + {.account = alice, .mutableFlags = tmfMPTClearCanClawback}); mptAlice.set({.account = alice, .metadata = "mutate"}); } @@ -3340,8 +3346,8 @@ class MPToken_test : public beast::unit_test::suite {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock, - .mutableFlags = tfMPTCanMutateCanLock | - tfMPTCanMutateCanClawback | tfMPTCanMutateMetadata}); + .mutableFlags = tmfMPTCanMutateCanLock | + tmfMPTCanMutateCanClawback | tmfMPTCanMutateMetadata}); mptAlice.authorize({.account = bob, .holderCount = 1}); // Can lock and unlock @@ -3352,7 +3358,8 @@ class MPToken_test : public beast::unit_test::suite {.account = alice, .holder = bob, .flags = tfMPTUnlock}); // Clear lsfMPTCanLock - mptAlice.set({.account = alice, .mutableFlags = tfMPTClearCanLock}); + mptAlice.set( + {.account = alice, .mutableFlags = tmfMPTClearCanLock}); // Can not lock or unlock mptAlice.set( @@ -3375,7 +3382,7 @@ class MPToken_test : public beast::unit_test::suite .err = tecNO_PERMISSION}); // Set MPTCanLock again - mptAlice.set({.account = alice, .mutableFlags = tfMPTSetCanLock}); + mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanLock}); // Can lock and unlock again mptAlice.set({.account = alice, .flags = tfMPTLock}); @@ -3400,7 +3407,7 @@ class MPToken_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .flags = tfMPTRequireAuth, - .mutableFlags = tfMPTCanMutateRequireAuth}); + .mutableFlags = tmfMPTCanMutateRequireAuth}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = alice, .holder = bob}); @@ -3416,13 +3423,14 @@ class MPToken_test : public beast::unit_test::suite mptAlice.pay(bob, alice, 100, tecNO_AUTH); // Clear RequireAuth - mptAlice.set({.account = alice, .mutableFlags = tfMPTClearRequireAuth}); + mptAlice.set( + {.account = alice, .mutableFlags = tmfMPTClearRequireAuth}); // Can pay to bob mptAlice.pay(alice, bob, 1000); // Set RequireAuth again - mptAlice.set({.account = alice, .mutableFlags = tfMPTSetRequireAuth}); + mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetRequireAuth}); // Can not pay to bob since he is not authorized mptAlice.pay(bob, alice, 100, tecNO_AUTH); @@ -3452,7 +3460,7 @@ class MPToken_test : public beast::unit_test::suite {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer, - .mutableFlags = tfMPTCanMutateCanEscrow}); + .mutableFlags = tmfMPTCanMutateCanEscrow}); mptAlice.authorize({.account = carol}); mptAlice.authorize({.account = bob}); @@ -3469,14 +3477,14 @@ class MPToken_test : public beast::unit_test::suite ter(tecNO_PERMISSION)); // MPTCanEscrow is enabled now - mptAlice.set({.account = alice, .mutableFlags = tfMPTSetCanEscrow}); + mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanEscrow}); env(escrow::create(carol, bob, MPT(3)), escrow::condition(escrow::cb1), escrow::finish_time(env.now() + 1s), fee(baseFee * 150)); // Clear MPTCanEscrow - mptAlice.set({.account = alice, .mutableFlags = tfMPTClearCanEscrow}); + mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanEscrow}); env(escrow::create(carol, bob, MPT(3)), escrow::condition(escrow::cb1), escrow::finish_time(env.now() + 1s), @@ -3501,7 +3509,7 @@ class MPToken_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .mutableFlags = - tfMPTCanMutateCanTransfer | tfMPTCanMutateTransferFee}); + tmfMPTCanMutateCanTransfer | tmfMPTCanMutateTransferFee}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = carol}); @@ -3522,13 +3530,13 @@ class MPToken_test : public beast::unit_test::suite // MPTCanTransfer at the same time mptAlice.set( {.account = alice, - .mutableFlags = tfMPTSetCanTransfer, + .mutableFlags = tmfMPTSetCanTransfer, .transferFee = 100, .err = tecNO_PERMISSION}); // Alice sets MPTCanTransfer mptAlice.set( - {.account = alice, .mutableFlags = tfMPTSetCanTransfer}); + {.account = alice, .mutableFlags = tmfMPTSetCanTransfer}); // Can set transfer fee now BEAST_EXPECT(!mptAlice.isTransferFeePresent()); @@ -3540,7 +3548,7 @@ class MPToken_test : public beast::unit_test::suite // Alice clears MPTCanTransfer mptAlice.set( - {.account = alice, .mutableFlags = tfMPTClearCanTransfer}); + {.account = alice, .mutableFlags = tmfMPTClearCanTransfer}); // TransferFee field is removed when MPTCanTransfer is cleared BEAST_EXPECT(!mptAlice.isTransferFeePresent()); @@ -3550,7 +3558,7 @@ class MPToken_test : public beast::unit_test::suite } // Can set transfer fee to zero when MPTCanTransfer is not set, but - // tfMPTCanMutateTransferFee is set. + // tmfMPTCanMutateTransferFee is set. { Env env{*this, features}; @@ -3560,13 +3568,13 @@ class MPToken_test : public beast::unit_test::suite .ownerCount = 1, .flags = tfMPTCanTransfer, .mutableFlags = - tfMPTCanMutateTransferFee | tfMPTCanMutateCanTransfer}); + tmfMPTCanMutateTransferFee | tmfMPTCanMutateCanTransfer}); BEAST_EXPECT(mptAlice.checkTransferFee(100)); // Clear MPTCanTransfer and transfer fee is removed mptAlice.set( - {.account = alice, .mutableFlags = tfMPTClearCanTransfer}); + {.account = alice, .mutableFlags = tmfMPTClearCanTransfer}); BEAST_EXPECT(!mptAlice.isTransferFeePresent()); // Can still set transfer fee to zero, although it is already zero @@ -3592,7 +3600,7 @@ class MPToken_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .mutableFlags = tfMPTCanMutateCanClawback}); + .mutableFlags = tmfMPTCanMutateCanClawback}); // Bob creates an MPToken mptAlice.authorize({.account = bob}); @@ -3604,13 +3612,14 @@ class MPToken_test : public beast::unit_test::suite mptAlice.claw(alice, bob, 1, tecNO_PERMISSION); // Enable MPTCanClawback - mptAlice.set({.account = alice, .mutableFlags = tfMPTSetCanClawback}); + mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanClawback}); // Can clawback now mptAlice.claw(alice, bob, 1); // Clear MPTCanClawback - mptAlice.set({.account = alice, .mutableFlags = tfMPTClearCanClawback}); + mptAlice.set( + {.account = alice, .mutableFlags = tmfMPTClearCanClawback}); // Can not clawback mptAlice.claw(alice, bob, 1, tecNO_PERMISSION); diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index f35b1b1ebb..f2f51492e3 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -265,34 +265,34 @@ MPTTester::set(MPTSet const& arg) if (arg.mutableFlags) { - if (*arg.mutableFlags & tfMPTSetCanLock) + if (*arg.mutableFlags & tmfMPTSetCanLock) flags |= lsfMPTCanLock; - else if (*arg.mutableFlags & tfMPTClearCanLock) + else if (*arg.mutableFlags & tmfMPTClearCanLock) flags &= ~lsfMPTCanLock; - if (*arg.mutableFlags & tfMPTSetRequireAuth) + if (*arg.mutableFlags & tmfMPTSetRequireAuth) flags |= lsfMPTRequireAuth; - else if (*arg.mutableFlags & tfMPTClearRequireAuth) + else if (*arg.mutableFlags & tmfMPTClearRequireAuth) flags &= ~lsfMPTRequireAuth; - if (*arg.mutableFlags & tfMPTSetCanEscrow) + if (*arg.mutableFlags & tmfMPTSetCanEscrow) flags |= lsfMPTCanEscrow; - else if (*arg.mutableFlags & tfMPTClearCanEscrow) + else if (*arg.mutableFlags & tmfMPTClearCanEscrow) flags &= ~lsfMPTCanEscrow; - if (*arg.mutableFlags & tfMPTSetCanClawback) + if (*arg.mutableFlags & tmfMPTSetCanClawback) flags |= lsfMPTCanClawback; - else if (*arg.mutableFlags & tfMPTClearCanClawback) + else if (*arg.mutableFlags & tmfMPTClearCanClawback) flags &= ~lsfMPTCanClawback; - if (*arg.mutableFlags & tfMPTSetCanTrade) + if (*arg.mutableFlags & tmfMPTSetCanTrade) flags |= lsfMPTCanTrade; - else if (*arg.mutableFlags & tfMPTClearCanTrade) + else if (*arg.mutableFlags & tmfMPTClearCanTrade) flags &= ~lsfMPTCanTrade; - if (*arg.mutableFlags & tfMPTSetCanTransfer) + if (*arg.mutableFlags & tmfMPTSetCanTransfer) flags |= lsfMPTCanTransfer; - else if (*arg.mutableFlags & tfMPTClearCanTransfer) + else if (*arg.mutableFlags & tmfMPTClearCanTransfer) flags &= ~lsfMPTCanTransfer; } } diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp index 6a6e598f42..c195e45c1d 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp @@ -44,7 +44,7 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) return ret; if (auto const mutableFlags = ctx.tx[~sfMutableFlags]; mutableFlags && - (!*mutableFlags || *mutableFlags & tfMPTokenIssuanceCreateMutableMask)) + (!*mutableFlags || *mutableFlags & tmfMPTokenIssuanceCreateMutableMask)) return temINVALID_FLAG; if (ctx.tx.getFlags() & tfMPTokenIssuanceCreateMask) diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp index 83b771c705..37c563460a 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp @@ -37,12 +37,14 @@ struct MPTMutabilityFlags }; static constexpr std::array mptMutabilityFlags = { - {{tfMPTSetCanLock, tfMPTClearCanLock, lsfMPTCanMutateCanLock}, - {tfMPTSetRequireAuth, tfMPTClearRequireAuth, lsfMPTCanMutateRequireAuth}, - {tfMPTSetCanEscrow, tfMPTClearCanEscrow, lsfMPTCanMutateCanEscrow}, - {tfMPTSetCanTrade, tfMPTClearCanTrade, lsfMPTCanMutateCanTrade}, - {tfMPTSetCanTransfer, tfMPTClearCanTransfer, lsfMPTCanMutateCanTransfer}, - {tfMPTSetCanClawback, tfMPTClearCanClawback, lsfMPTCanMutateCanClawback}}}; + {{tmfMPTSetCanLock, tmfMPTClearCanLock, lmfMPTCanMutateCanLock}, + {tmfMPTSetRequireAuth, tmfMPTClearRequireAuth, lmfMPTCanMutateRequireAuth}, + {tmfMPTSetCanEscrow, tmfMPTClearCanEscrow, lmfMPTCanMutateCanEscrow}, + {tmfMPTSetCanTrade, tmfMPTClearCanTrade, lmfMPTCanMutateCanTrade}, + {tmfMPTSetCanTransfer, tmfMPTClearCanTransfer, lmfMPTCanMutateCanTransfer}, + {tmfMPTSetCanClawback, + tmfMPTClearCanClawback, + lmfMPTCanMutateCanClawback}}}; NotTEC MPTokenIssuanceSet::preflight(PreflightContext const& ctx) @@ -110,7 +112,7 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx) if (mutableFlags) { if (!*mutableFlags || - (*mutableFlags & tfMPTokenIssuanceSetMutableMask)) + (*mutableFlags & tmfMPTokenIssuanceSetMutableMask)) return temINVALID_FLAG; // Can not set and clear the same flag @@ -126,7 +128,7 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx) // Trying to set a non-zero TransferFee and clear MPTCanTransfer // in the same transaction is not allowed. if (transferFee.value_or(0) && - (*mutableFlags & tfMPTClearCanTransfer)) + (*mutableFlags & tmfMPTClearCanTransfer)) return temMALFORMED; } } @@ -241,7 +243,7 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) return tecNO_PERMISSION; } - if (!isMutableFlag(lsfMPTCanMutateMetadata) && + if (!isMutableFlag(lmfMPTCanMutateMetadata) && ctx.tx.isFieldPresent(sfMPTokenMetadata)) return tecNO_PERMISSION; @@ -249,12 +251,12 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) { // A non-zero TransferFee is only valid if the lsfMPTCanTransfer flag // was previously enabled (at issuance or via a prior mutation). Setting - // it by tfMPTSetCanTransfer in the current transaction does not meet + // it by tmfMPTSetCanTransfer in the current transaction does not meet // this requirement. if (fee > 0u && !sleMptIssuance->isFlag(lsfMPTCanTransfer)) return tecNO_PERMISSION; - if (!isMutableFlag(lsfMPTCanMutateTransferFee)) + if (!isMutableFlag(lmfMPTCanMutateTransferFee)) return tecNO_PERMISSION; } @@ -296,7 +298,7 @@ MPTokenIssuanceSet::doApply() flagsOut &= ~f.canMutateFlag; } - if (mutableFlags & tfMPTClearCanTransfer) + if (mutableFlags & tmfMPTClearCanTransfer) { // If the lsfMPTCanTransfer flag is being cleared, then also clear // the TransferFee field. From 510314d34465324575719ce528bdda185581464b Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Wed, 17 Sep 2025 17:34:47 -0400 Subject: [PATCH 19/24] fix(amendment): Add missing fields for keylets to ledger objects (#5646) This change adds a fix amendment (`fixIncludeKeyletFields`) that adds: * `sfSequence` to `Escrow` and `PayChannel` * `sfOwner` to `SignerList` * `sfOracleDocumentID` to `Oracle` This ensures that all ledger entries hold all the information needed to determine their keylet. --- include/xrpl/protocol/Indexes.h | 2 + include/xrpl/protocol/detail/features.macro | 1 + .../xrpl/protocol/detail/ledger_entries.macro | 6 +- src/test/app/Escrow_test.cpp | 9 ++ src/test/app/MultiSign_test.cpp | 115 +++++++++++------- src/test/app/Oracle_test.cpp | 23 +++- src/test/app/PayChan_test.cpp | 9 ++ src/test/jtx/impl/Oracle.cpp | 4 +- src/xrpld/app/tx/detail/Escrow.cpp | 5 + src/xrpld/app/tx/detail/PayChan.cpp | 4 + src/xrpld/app/tx/detail/SetOracle.cpp | 9 ++ src/xrpld/app/tx/detail/SetSignerList.cpp | 8 +- 12 files changed, 144 insertions(+), 51 deletions(-) diff --git a/include/xrpl/protocol/Indexes.h b/include/xrpl/protocol/Indexes.h index 3e3f2843c1..79be15d906 100644 --- a/include/xrpl/protocol/Indexes.h +++ b/include/xrpl/protocol/Indexes.h @@ -287,9 +287,11 @@ delegate(AccountID const& account, AccountID const& authorizedAccount) noexcept; Keylet bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType); +// `seq` is stored as `sfXChainClaimID` in the object Keylet xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq); +// `seq` is stored as `sfXChainAccountCreateCount` in the object Keylet xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq); diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index a9f5d95624..9dc40dc8e5 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -32,6 +32,7 @@ // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. +XRPL_FIX (IncludeKeyletFields, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(DynamicMPT, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (TokenEscrowV1, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (DelegateV1_1, Supported::no, VoteBehavior::DefaultNo) diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 1066986223..f76188095e 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -120,6 +120,7 @@ LEDGER_ENTRY(ltNFTOKEN_PAGE, 0x0050, NFTokenPage, nft_page, ({ // All fields are soeREQUIRED because there is always a SignerEntries. // If there are no SignerEntries the node is deleted. LEDGER_ENTRY(ltSIGNER_LIST, 0x0053, SignerList, signer_list, ({ + {sfOwner, soeOPTIONAL}, {sfOwnerNode, soeREQUIRED}, {sfSignerQuorum, soeREQUIRED}, {sfSignerEntries, soeREQUIRED}, @@ -188,7 +189,7 @@ LEDGER_ENTRY(ltDIR_NODE, 0x0064, DirectoryNode, directory, ({ {sfNFTokenID, soeOPTIONAL}, {sfPreviousTxnID, soeOPTIONAL}, {sfPreviousTxnLgrSeq, soeOPTIONAL}, - {sfDomainID, soeOPTIONAL} + {sfDomainID, soeOPTIONAL} // order book directories })) /** The ledger object which lists details about amendments on the network. @@ -343,6 +344,7 @@ LEDGER_ENTRY(ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID, 0x0074, XChainOwnedCreateAc */ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({ {sfAccount, soeREQUIRED}, + {sfSequence, soeOPTIONAL}, {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED}, {sfCondition, soeOPTIONAL}, @@ -365,6 +367,7 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({ LEDGER_ENTRY(ltPAYCHAN, 0x0078, PayChannel, payment_channel, ({ {sfAccount, soeREQUIRED}, {sfDestination, soeREQUIRED}, + {sfSequence, soeOPTIONAL}, {sfAmount, soeREQUIRED}, {sfBalance, soeREQUIRED}, {sfPublicKey, soeREQUIRED}, @@ -433,6 +436,7 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({ */ LEDGER_ENTRY(ltORACLE, 0x0080, Oracle, oracle, ({ {sfOwner, soeREQUIRED}, + {sfOracleDocumentID, soeOPTIONAL}, {sfProvider, soeREQUIRED}, {sfPriceDataSeries, soeREQUIRED}, {sfAssetClass, soeREQUIRED}, diff --git a/src/test/app/Escrow_test.cpp b/src/test/app/Escrow_test.cpp index 3eaf0f13ea..19b8612ef4 100644 --- a/src/test/app/Escrow_test.cpp +++ b/src/test/app/Escrow_test.cpp @@ -253,6 +253,14 @@ struct Escrow_test : public beast::unit_test::suite BEAST_EXPECT(sle); BEAST_EXPECT((*sle)[sfSourceTag] == 1); BEAST_EXPECT((*sle)[sfDestinationTag] == 2); + if (features[fixIncludeKeyletFields]) + { + BEAST_EXPECT((*sle)[sfSequence] == seq); + } + else + { + BEAST_EXPECT(!sle->isFieldPresent(sfSequence)); + } } void @@ -1718,6 +1726,7 @@ public: FeatureBitset const all{testable_amendments()}; testWithFeats(all); testWithFeats(all - featureTokenEscrow); + testTags(all - fixIncludeKeyletFields); } }; diff --git a/src/test/app/MultiSign_test.cpp b/src/test/app/MultiSign_test.cpp index 571ec33417..776e163cd4 100644 --- a/src/test/app/MultiSign_test.cpp +++ b/src/test/app/MultiSign_test.cpp @@ -63,7 +63,7 @@ class MultiSign_test : public beast::unit_test::suite public: void - test_noReserve(FeatureBitset features) + testNoReserve(FeatureBitset features) { testcase("No Reserve"); @@ -133,7 +133,7 @@ public: } void - test_signerListSet(FeatureBitset features) + testSignerListSet(FeatureBitset features) { testcase("SignerListSet"); @@ -215,7 +215,7 @@ public: } void - test_phantomSigners(FeatureBitset features) + testPhantomSigners(FeatureBitset features) { testcase("Phantom Signers"); @@ -282,7 +282,7 @@ public: } void - test_fee(FeatureBitset features) + testFee(FeatureBitset features) { testcase("Fee"); @@ -346,7 +346,7 @@ public: } void - test_misorderedSigners(FeatureBitset features) + testMisorderedSigners(FeatureBitset features) { testcase("Misordered Signers"); @@ -374,7 +374,7 @@ public: } void - test_masterSigners(FeatureBitset features) + testMasterSigners(FeatureBitset features) { testcase("Master Signers"); @@ -429,7 +429,7 @@ public: } void - test_regularSigners(FeatureBitset features) + testRegularSigners(FeatureBitset features) { testcase("Regular Signers"); @@ -494,7 +494,7 @@ public: } void - test_regularSignersUsingSubmitMulti(FeatureBitset features) + testRegularSignersUsingSubmitMulti(FeatureBitset features) { testcase("Regular Signers Using submit_multisigned"); @@ -734,7 +734,7 @@ public: } void - test_heterogeneousSigners(FeatureBitset features) + testHeterogeneousSigners(FeatureBitset features) { testcase("Heterogenious Signers"); @@ -881,7 +881,7 @@ public: // We want to always leave an account signable. Make sure the that we // disallow removing the last way a transaction may be signed. void - test_keyDisable(FeatureBitset features) + testKeyDisable(FeatureBitset features) { testcase("Key Disable"); @@ -963,7 +963,7 @@ public: // Verify that the first regular key can be made for free using the // master key, but not when multisigning. void - test_regKey(FeatureBitset features) + testRegKey(FeatureBitset features) { testcase("Regular Key"); @@ -1000,7 +1000,7 @@ public: // See if every kind of transaction can be successfully multi-signed. void - test_txTypes(FeatureBitset features) + testTxTypes(FeatureBitset features) { testcase("Transaction Types"); @@ -1089,7 +1089,7 @@ public: } void - test_badSignatureText(FeatureBitset features) + testBadSignatureText(FeatureBitset features) { testcase("Bad Signature Text"); @@ -1285,7 +1285,7 @@ public: } void - test_noMultiSigners(FeatureBitset features) + testNoMultiSigners(FeatureBitset features) { testcase("No Multisigners"); @@ -1304,7 +1304,7 @@ public: } void - test_multisigningMultisigner(FeatureBitset features) + testMultisigningMultisigner(FeatureBitset features) { testcase("Multisigning multisigner"); @@ -1381,7 +1381,7 @@ public: } void - test_signForHash(FeatureBitset features) + testSignForHash(FeatureBitset features) { testcase("sign_for Hash"); @@ -1464,7 +1464,7 @@ public: } void - test_amendmentTransition() + testAmendmentTransition() { testcase("Amendment Transition"); @@ -1559,7 +1559,7 @@ public: } void - test_signersWithTickets(FeatureBitset features) + testSignersWithTickets(FeatureBitset features) { testcase("Signers With Tickets"); @@ -1600,7 +1600,7 @@ public: } void - test_signersWithTags(FeatureBitset features) + testSignersWithTags(FeatureBitset features) { if (!features[featureExpandedSignerList]) return; @@ -1680,7 +1680,7 @@ public: } void - test_signerListSetFlags(FeatureBitset features) + testSignerListSetFlags(FeatureBitset features) { using namespace test::jtx; @@ -1702,27 +1702,57 @@ public: env.close(); } + void + testSignerListObject(FeatureBitset features) + { + testcase("SignerList Object"); + + // Verify that the SignerList object is created correctly. + using namespace jtx; + Env env{*this, features}; + Account const alice{"alice", KeyType::ed25519}; + env.fund(XRP(1000), alice); + env.close(); + + // Attach phantom signers to alice. + env(signers(alice, 1, {{bogie, 1}, {demon, 1}})); + env.close(); + + // Verify that the SignerList object was created correctly. + auto const& sle = env.le(keylet::signers(alice.id())); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->getFieldArray(sfSignerEntries).size() == 2); + if (features[fixIncludeKeyletFields]) + { + BEAST_EXPECT((*sle)[sfOwner] == alice.id()); + } + else + { + BEAST_EXPECT(!sle->isFieldPresent(sfOwner)); + } + } + void testAll(FeatureBitset features) { - test_noReserve(features); - test_signerListSet(features); - test_phantomSigners(features); - test_fee(features); - test_misorderedSigners(features); - test_masterSigners(features); - test_regularSigners(features); - test_regularSignersUsingSubmitMulti(features); - test_heterogeneousSigners(features); - test_keyDisable(features); - test_regKey(features); - test_txTypes(features); - test_badSignatureText(features); - test_noMultiSigners(features); - test_multisigningMultisigner(features); - test_signForHash(features); - test_signersWithTickets(features); - test_signersWithTags(features); + testNoReserve(features); + testSignerListSet(features); + testPhantomSigners(features); + testFee(features); + testMisorderedSigners(features); + testMasterSigners(features); + testRegularSigners(features); + testRegularSignersUsingSubmitMulti(features); + testHeterogeneousSigners(features); + testKeyDisable(features); + testRegKey(features); + testTxTypes(features); + testBadSignatureText(features); + testNoMultiSigners(features); + testMultisigningMultisigner(features); + testSignForHash(features); + testSignersWithTickets(features); + testSignersWithTags(features); } void @@ -1739,10 +1769,13 @@ public: testAll(all - featureExpandedSignerList); testAll(all); - test_signerListSetFlags(all - fixInvalidTxFlags); - test_signerListSetFlags(all); + testSignerListSetFlags(all - fixInvalidTxFlags); + testSignerListSetFlags(all); - test_amendmentTransition(); + testSignerListObject(all - fixIncludeKeyletFields); + testSignerListObject(all); + + testAmendmentTransition(); } }; diff --git a/src/test/app/Oracle_test.cpp b/src/test/app/Oracle_test.cpp index fdd7ad941e..f0cde41394 100644 --- a/src/test/app/Oracle_test.cpp +++ b/src/test/app/Oracle_test.cpp @@ -398,7 +398,7 @@ private: } void - testCreate() + testCreate(FeatureBitset features) { testcase("Create"); using namespace jtx; @@ -413,18 +413,30 @@ private: env, {.owner = owner, .series = series, .fee = baseFee}); BEAST_EXPECT(oracle.exists()); BEAST_EXPECT(ownerCount(env, owner) == (count + adj)); + auto const entry = oracle.ledgerEntry(); + BEAST_EXPECT(entry[jss::node][jss::Owner] == owner.human()); + if (features[fixIncludeKeyletFields]) + { + BEAST_EXPECT( + entry[jss::node][jss::OracleDocumentID] == + oracle.documentID()); + } + else + { + BEAST_EXPECT(!entry[jss::node].isMember(jss::OracleDocumentID)); + } BEAST_EXPECT(oracle.expectLastUpdateTime(946694810)); }; { // owner count is adjusted by 1 - Env env(*this); + Env env(*this, features); test(env, {{"XRP", "USD", 740, 1}}, 1); } { // owner count is adjusted by 2 - Env env(*this); + Env env(*this, features); test( env, {{"XRP", "USD", 740, 1}, @@ -438,7 +450,7 @@ private: { // Different owner creates a new object - Env env(*this); + Env env(*this, features); auto const baseFee = static_cast(env.current()->fees().base.drops()); Account const some("some"); @@ -864,7 +876,8 @@ public: auto const all = testable_amendments(); testInvalidSet(); testInvalidDelete(); - testCreate(); + testCreate(all); + testCreate(all - fixIncludeKeyletFields); testDelete(); testUpdate(); testAmendment(); diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index 3a5d3d6ff5..3d0557fd5c 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -1852,6 +1852,14 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(ownerDirCount(*env.current(), alice) == 1); BEAST_EXPECT(!inOwnerDir(*env.current(), bob, chanSle)); BEAST_EXPECT(ownerDirCount(*env.current(), bob) == 0); + if (features[fixIncludeKeyletFields]) + { + BEAST_EXPECT((*chanSle)[sfSequence] == env.seq(alice) - 1); + } + else + { + BEAST_EXPECT(!chanSle->isFieldPresent(sfSequence)); + } // close the channel env(claim(bob, chan), txflags(tfClose)); BEAST_EXPECT(!channelExists(*env.current(), chan)); @@ -2348,6 +2356,7 @@ public: testWithFeats(all - disallowIncoming); testWithFeats(all); testDepositAuthCreds(); + testMetaAndOwnership(all - fixIncludeKeyletFields); } }; diff --git a/src/test/jtx/impl/Oracle.cpp b/src/test/jtx/impl/Oracle.cpp index 721a1c299d..97a31cbb0c 100644 --- a/src/test/jtx/impl/Oracle.cpp +++ b/src/test/jtx/impl/Oracle.cpp @@ -317,10 +317,10 @@ Oracle::ledgerEntry( if (jr.isObject()) { + if (jr.isMember(jss::error)) + return jr; if (jr.isMember(jss::result) && jr[jss::result].isMember(jss::status)) return jr[jss::result]; - else if (jr.isMember(jss::error)) - return jr; } return Json::nullValue; } diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index 3b05aa0007..3c15278efc 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -538,6 +538,11 @@ EscrowCreate::doApply() (*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter]; (*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag]; + if (ctx_.view().rules().enabled(fixIncludeKeyletFields)) + { + (*slep)[sfSequence] = ctx_.tx.getSeqValue(); + } + if (ctx_.view().rules().enabled(featureTokenEscrow) && !isXRP(amount)) { auto const xferRate = transferRate(ctx_.view(), amount); diff --git a/src/xrpld/app/tx/detail/PayChan.cpp b/src/xrpld/app/tx/detail/PayChan.cpp index d9e53ac75c..12a9d0cb75 100644 --- a/src/xrpld/app/tx/detail/PayChan.cpp +++ b/src/xrpld/app/tx/detail/PayChan.cpp @@ -286,6 +286,10 @@ PayChanCreate::doApply() (*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter]; (*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag]; (*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag]; + if (ctx_.view().rules().enabled(fixIncludeKeyletFields)) + { + (*slep)[sfSequence] = ctx_.tx.getSeqValue(); + } ctx_.view().insert(slep); diff --git a/src/xrpld/app/tx/detail/SetOracle.cpp b/src/xrpld/app/tx/detail/SetOracle.cpp index d598507cb7..ba1d4a2e47 100644 --- a/src/xrpld/app/tx/detail/SetOracle.cpp +++ b/src/xrpld/app/tx/detail/SetOracle.cpp @@ -271,6 +271,11 @@ SetOracle::doApply() if (ctx_.tx.isFieldPresent(sfURI)) sle->setFieldVL(sfURI, ctx_.tx[sfURI]); sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]); + if (!sle->isFieldPresent(sfOracleDocumentID) && + ctx_.view().rules().enabled(fixIncludeKeyletFields)) + { + (*sle)[sfOracleDocumentID] = ctx_.tx[sfOracleDocumentID]; + } auto const newCount = pairs.size() > 5 ? 2 : 1; auto const adjust = newCount - oldCount; @@ -285,6 +290,10 @@ SetOracle::doApply() sle = std::make_shared(oracleID); sle->setAccountID(sfOwner, ctx_.tx.getAccountID(sfAccount)); + if (ctx_.view().rules().enabled(fixIncludeKeyletFields)) + { + (*sle)[sfOracleDocumentID] = ctx_.tx[sfOracleDocumentID]; + } sle->setFieldVL(sfProvider, ctx_.tx[sfProvider]); if (ctx_.tx.isFieldPresent(sfURI)) sle->setFieldVL(sfURI, ctx_.tx[sfURI]); diff --git a/src/xrpld/app/tx/detail/SetSignerList.cpp b/src/xrpld/app/tx/detail/SetSignerList.cpp index 4a1ee703a0..b52130e2fa 100644 --- a/src/xrpld/app/tx/detail/SetSignerList.cpp +++ b/src/xrpld/app/tx/detail/SetSignerList.cpp @@ -37,7 +37,7 @@ namespace ripple { // We're prepared for there to be multiple signer lists in the future, // but we don't need them yet. So for the time being we're manually // setting the sfSignerListID to zero in all cases. -static std::uint32_t const defaultSignerListID_ = 0; +static std::uint32_t const DEFAULT_SIGNER_LIST_ID = 0; std::tuple< NotTEC, @@ -424,8 +424,12 @@ SetSignerList::writeSignersToSLE( std::uint32_t flags) const { // Assign the quorum, default SignerListID, and flags. + if (ctx_.view().rules().enabled(fixIncludeKeyletFields)) + { + ledgerEntry->setAccountID(sfOwner, account_); + } ledgerEntry->setFieldU32(sfSignerQuorum, quorum_); - ledgerEntry->setFieldU32(sfSignerListID, defaultSignerListID_); + ledgerEntry->setFieldU32(sfSignerListID, DEFAULT_SIGNER_LIST_ID); if (flags) // Only set flags if they are non-default (default is zero). ledgerEntry->setFieldU32(sfFlags, flags); From e66558a883e23f45ff1d2299eaa9e900606b2132 Mon Sep 17 00:00:00 2001 From: Bart Date: Wed, 17 Sep 2025 18:55:00 -0400 Subject: [PATCH 20/24] chore: Limits CI build and test parallelism to reduce resource contention (#5799) GitHub runners have a limit on how many concurrent jobs they can actually process (even though they will try to run them all at the same time), and similarly the Conan remote cannot handle hundreds of concurrent requests. Previously, the Conan dependency uploading was already limited to max 10 jobs running in parallel, and this change makes the same change to the build+test workflow. --- .github/workflows/build-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 69ff986f98..634ed42690 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -61,6 +61,7 @@ jobs: strategy: fail-fast: false matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} + max-parallel: 10 runs-on: ${{ matrix.architecture.runner }} container: ${{ inputs.os == 'linux' && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version) || null }} steps: From 1af1048c581a509f067e6458d617fdbaf4a05601 Mon Sep 17 00:00:00 2001 From: Bart Date: Wed, 17 Sep 2025 19:17:48 -0400 Subject: [PATCH 21/24] chore: Build and test all configs for daily scheduled run (#5801) This change re-enables building and testing all configurations, but only for the daily scheduled run. Previously all configurations were run for each merge into the develop branch, but that overwhelmed both the GitHub runners and the Conan remote, and thus they were limited to just a subset of configurations. Now that the number of jobs is limited via `max-parallel: 10`, we should be able to safely enable building all configurations again. However, building them all once a day instead of for each PR merge should be sufficient. --- .github/workflows/on-trigger.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index 7c17621d67..06abbd3f17 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -80,6 +80,6 @@ jobs: os: [linux, macos, windows] with: os: ${{ matrix.os }} - strategy_matrix: "minimal" + strategy_matrix: ${{ github.event_name == 'schedule' && 'all' || 'minimal' }} secrets: codecov_token: ${{ secrets.CODECOV_TOKEN }} From 617a895af5e43e80fd129dcd265d510057815635 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Thu, 18 Sep 2025 11:30:34 +0100 Subject: [PATCH 22/24] chore: Add unit tests dir to code coverage excludes (#5803) This change excludes unit test code from code coverage reporting. --- .codecov.yml | 1 + cmake/RippledCov.cmake | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index d28d7c80df..cd52e2604d 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -33,5 +33,6 @@ slack_app: false ignore: - "src/test/" + - "src/tests/" - "include/xrpl/beast/test/" - "include/xrpl/beast/unit_test/" diff --git a/cmake/RippledCov.cmake b/cmake/RippledCov.cmake index 847915a51a..6cbe2ff921 100644 --- a/cmake/RippledCov.cmake +++ b/cmake/RippledCov.cmake @@ -33,7 +33,7 @@ setup_target_for_coverage_gcovr( FORMAT ${coverage_format} EXECUTABLE rippled EXECUTABLE_ARGS --unittest$<$:=${coverage_test}> --unittest-jobs ${coverage_test_parallelism} --quiet --unittest-log - EXCLUDE "src/test" "include/xrpl/beast/test" "include/xrpl/beast/unit_test" "${CMAKE_BINARY_DIR}/pb-xrpl.libpb" + EXCLUDE "src/test" "src/tests" "include/xrpl/beast/test" "include/xrpl/beast/unit_test" "${CMAKE_BINARY_DIR}/pb-xrpl.libpb" DEPENDENCIES rippled ) From dc8b37a52448b005153c13a7f046ad494128cf94 Mon Sep 17 00:00:00 2001 From: Jingchen Date: Thu, 18 Sep 2025 16:12:24 +0100 Subject: [PATCH 23/24] refactor: Modularise ledger (#5493) This change moves the ledger code to libxrpl. --- .../scripts/levelization/results/loops.txt | 3 --- .../scripts/levelization/results/ordering.txt | 20 +++++++++++-------- cmake/RippledCore.cmake | 7 +++++++ cmake/RippledInstall.cmake | 1 + .../xrpld => include/xrpl}/ledger/ApplyView.h | 5 ++--- .../xrpl}/ledger/ApplyViewImpl.h | 5 ++--- {src/xrpld => include/xrpl}/ledger/BookDirs.h | 3 +-- .../xrpl}/ledger/CachedSLEs.h | 0 .../xrpl}/ledger/CachedView.h | 5 ++--- .../xrpl/ledger}/CredentialHelpers.h | 5 ++--- {src/xrpld => include/xrpl}/ledger/Dir.h | 3 +-- {src/xrpld => include/xrpl}/ledger/OpenView.h | 7 +++---- .../xrpl}/ledger/PaymentSandbox.h | 7 +++---- {src/xrpld => include/xrpl}/ledger/RawView.h | 3 +-- {src/xrpld => include/xrpl}/ledger/ReadView.h | 5 ++--- {src/xrpld => include/xrpl}/ledger/Sandbox.h | 4 ++-- {src/xrpld => include/xrpl}/ledger/View.h | 7 +++---- .../xrpl}/ledger/detail/ApplyStateTable.h | 7 +++---- .../xrpl}/ledger/detail/ApplyViewBase.h | 7 +++---- .../xrpl}/ledger/detail/RawStateTable.h | 4 ++-- .../xrpl}/ledger/detail/ReadViewFwdRange.h | 0 .../xrpl}/ledger/detail/ReadViewFwdRange.ipp | 0 .../ledger}/ApplyStateTable.cpp | 3 +-- .../detail => libxrpl/ledger}/ApplyView.cpp | 3 +-- .../ledger}/ApplyViewBase.cpp | 2 +- .../ledger}/ApplyViewImpl.cpp | 2 +- .../detail => libxrpl/ledger}/BookDirs.cpp | 5 ++--- .../detail => libxrpl/ledger}/CachedView.cpp | 3 +-- .../ledger}/CredentialHelpers.cpp | 5 ++--- .../ledger/detail => libxrpl/ledger}/Dir.cpp | 2 +- .../detail => libxrpl/ledger}/OpenView.cpp | 3 +-- .../ledger}/PaymentSandbox.cpp | 6 ++---- .../ledger}/RawStateTable.cpp | 3 +-- .../detail => libxrpl/ledger}/ReadView.cpp | 2 +- .../ledger/detail => libxrpl/ledger}/View.cpp | 7 +++---- src/test/app/AMMExtended_test.cpp | 2 +- src/test/app/Credentials_test.cpp | 5 ++--- src/test/app/EscrowToken_test.cpp | 4 ++-- src/test/app/Escrow_test.cpp | 2 +- src/test/app/Flow_test.cpp | 4 ++-- src/test/app/LedgerHistory_test.cpp | 2 +- src/test/app/LoadFeeTrack_test.cpp | 2 +- src/test/app/PayChan_test.cpp | 2 +- src/test/app/PayStrand_test.cpp | 2 +- src/test/app/PermissionedDEX_test.cpp | 2 +- src/test/app/TheoreticalQuality_test.cpp | 2 +- src/test/app/Vault_test.cpp | 5 ++--- src/test/consensus/NegativeUNL_test.cpp | 2 +- src/test/jtx/owners.h | 3 +-- src/test/ledger/BookDirs_test.cpp | 3 +-- src/test/ledger/Directory_test.cpp | 5 ++--- src/test/ledger/PaymentSandbox_test.cpp | 7 +++---- src/test/ledger/SkipList_test.cpp | 2 +- src/test/ledger/View_test.cpp | 8 ++++---- src/xrpld/app/consensus/RCLCxLedger.h | 2 +- src/xrpld/app/ledger/BuildLedger.h | 3 +-- src/xrpld/app/ledger/Ledger.h | 4 ++-- src/xrpld/app/ledger/LocalTxs.h | 3 ++- src/xrpld/app/ledger/OpenLedger.h | 4 ++-- src/xrpld/app/ledger/detail/OpenLedger.cpp | 2 +- src/xrpld/app/misc/AMMUtils.h | 3 +-- src/xrpld/app/misc/FeeVote.h | 2 +- src/xrpld/app/misc/NetworkOPs.h | 2 +- src/xrpld/app/misc/PermissionedDEXHelpers.cpp | 3 ++- src/xrpld/app/misc/PermissionedDEXHelpers.h | 2 +- src/xrpld/app/misc/TxQ.h | 4 ++-- src/xrpld/app/misc/detail/AMMUtils.cpp | 2 +- src/xrpld/app/misc/detail/LoadFeeTrack.cpp | 2 +- src/xrpld/app/paths/AMMLiquidity.h | 4 ++-- src/xrpld/app/paths/AMMOffer.h | 5 ++--- src/xrpld/app/paths/Credit.cpp | 3 +-- src/xrpld/app/paths/Credit.h | 3 +-- src/xrpld/app/paths/Pathfinder.cpp | 2 +- src/xrpld/app/paths/RippleCalc.cpp | 2 +- src/xrpld/app/paths/RippleCalc.h | 3 +-- src/xrpld/app/paths/TrustLine.h | 3 +-- src/xrpld/app/paths/detail/BookStep.cpp | 2 +- src/xrpld/app/paths/detail/DirectStep.cpp | 2 +- src/xrpld/app/paths/detail/FlowDebugInfo.h | 2 +- src/xrpld/app/paths/detail/PaySteps.cpp | 2 +- src/xrpld/app/paths/detail/StepChecks.h | 5 ++--- .../app/paths/detail/XRPEndpointStep.cpp | 2 +- src/xrpld/app/tx/apply.h | 2 +- src/xrpld/app/tx/applySteps.h | 3 +-- src/xrpld/app/tx/detail/AMMBid.cpp | 4 ++-- src/xrpld/app/tx/detail/AMMClawback.cpp | 4 ++-- src/xrpld/app/tx/detail/AMMCreate.cpp | 4 ++-- src/xrpld/app/tx/detail/AMMDelete.cpp | 2 +- src/xrpld/app/tx/detail/AMMDeposit.cpp | 4 ++-- src/xrpld/app/tx/detail/AMMVote.cpp | 2 +- src/xrpld/app/tx/detail/AMMWithdraw.cpp | 2 +- src/xrpld/app/tx/detail/AMMWithdraw.h | 3 ++- src/xrpld/app/tx/detail/ApplyContext.h | 2 +- src/xrpld/app/tx/detail/Batch.cpp | 4 ++-- src/xrpld/app/tx/detail/BookTip.h | 3 +-- src/xrpld/app/tx/detail/CancelCheck.cpp | 2 +- src/xrpld/app/tx/detail/CancelOffer.cpp | 2 +- src/xrpld/app/tx/detail/Change.cpp | 2 +- src/xrpld/app/tx/detail/Clawback.cpp | 2 +- src/xrpld/app/tx/detail/CreateCheck.cpp | 2 +- src/xrpld/app/tx/detail/CreateOffer.cpp | 2 +- src/xrpld/app/tx/detail/Credentials.cpp | 6 +++--- src/xrpld/app/tx/detail/DID.cpp | 4 ++-- src/xrpld/app/tx/detail/DelegateSet.cpp | 2 +- src/xrpld/app/tx/detail/DeleteAccount.cpp | 4 ++-- src/xrpld/app/tx/detail/DeleteOracle.cpp | 2 +- src/xrpld/app/tx/detail/DepositPreauth.cpp | 4 ++-- src/xrpld/app/tx/detail/Escrow.cpp | 6 +++--- src/xrpld/app/tx/detail/InvariantCheck.cpp | 6 +++--- src/xrpld/app/tx/detail/LedgerStateFix.cpp | 2 +- src/xrpld/app/tx/detail/MPTokenAuthorize.cpp | 2 +- .../app/tx/detail/MPTokenIssuanceCreate.cpp | 2 +- .../app/tx/detail/MPTokenIssuanceDestroy.cpp | 2 +- .../app/tx/detail/NFTokenAcceptOffer.cpp | 2 +- .../app/tx/detail/NFTokenCancelOffer.cpp | 2 +- .../app/tx/detail/NFTokenCreateOffer.cpp | 2 +- src/xrpld/app/tx/detail/NFTokenMint.cpp | 2 +- src/xrpld/app/tx/detail/NFTokenUtils.cpp | 4 ++-- src/xrpld/app/tx/detail/NFTokenUtils.h | 2 +- src/xrpld/app/tx/detail/Offer.h | 3 +-- src/xrpld/app/tx/detail/OfferStream.cpp | 2 +- src/xrpld/app/tx/detail/OfferStream.h | 2 +- src/xrpld/app/tx/detail/PayChan.cpp | 6 +++--- src/xrpld/app/tx/detail/Payment.cpp | 4 ++-- .../tx/detail/PermissionedDomainDelete.cpp | 2 +- .../app/tx/detail/PermissionedDomainSet.cpp | 4 ++-- src/xrpld/app/tx/detail/SetAccount.cpp | 2 +- src/xrpld/app/tx/detail/SetOracle.cpp | 4 ++-- src/xrpld/app/tx/detail/SetSignerList.cpp | 2 +- src/xrpld/app/tx/detail/SetTrust.cpp | 2 +- src/xrpld/app/tx/detail/Taker.h | 0 src/xrpld/app/tx/detail/Transactor.cpp | 4 ++-- src/xrpld/app/tx/detail/VaultClawback.cpp | 2 +- src/xrpld/app/tx/detail/VaultCreate.cpp | 2 +- src/xrpld/app/tx/detail/VaultDelete.cpp | 2 +- src/xrpld/app/tx/detail/VaultDeposit.cpp | 4 ++-- src/xrpld/app/tx/detail/VaultSet.cpp | 2 +- src/xrpld/app/tx/detail/VaultWithdraw.cpp | 4 ++-- src/xrpld/app/tx/detail/XChainBridge.cpp | 6 +++--- src/xrpld/rpc/detail/RPCHelpers.cpp | 2 +- src/xrpld/rpc/handlers/AMMInfo.cpp | 2 +- src/xrpld/rpc/handlers/AccountChannels.cpp | 4 ++-- .../rpc/handlers/AccountCurrenciesHandler.cpp | 2 +- src/xrpld/rpc/handlers/AccountInfo.cpp | 2 +- src/xrpld/rpc/handlers/AccountLines.cpp | 2 +- src/xrpld/rpc/handlers/AccountObjects.cpp | 2 +- src/xrpld/rpc/handlers/AccountOffers.cpp | 4 ++-- src/xrpld/rpc/handlers/AccountTx.cpp | 2 +- src/xrpld/rpc/handlers/BookOffers.cpp | 2 +- src/xrpld/rpc/handlers/DepositAuthorized.cpp | 4 ++-- src/xrpld/rpc/handlers/GatewayBalances.cpp | 2 +- src/xrpld/rpc/handlers/GetAggregatePrice.cpp | 2 +- src/xrpld/rpc/handlers/LedgerData.cpp | 2 +- src/xrpld/rpc/handlers/LedgerEntry.cpp | 4 ++-- src/xrpld/rpc/handlers/LedgerHandler.h | 2 +- src/xrpld/rpc/handlers/LedgerHeader.cpp | 2 +- src/xrpld/rpc/handlers/NFTOffers.cpp | 4 ++-- src/xrpld/rpc/handlers/NoRippleCheck.cpp | 2 +- src/xrpld/rpc/handlers/PayChanClaim.cpp | 2 +- src/xrpld/rpc/handlers/Subscribe.cpp | 2 +- src/xrpld/rpc/handlers/TransactionEntry.cpp | 2 +- 161 files changed, 244 insertions(+), 272 deletions(-) rename {src/xrpld => include/xrpl}/ledger/ApplyView.h (99%) rename {src/xrpld => include/xrpl}/ledger/ApplyViewImpl.h (97%) rename {src/xrpld => include/xrpl}/ledger/BookDirs.h (98%) rename {src/xrpld => include/xrpl}/ledger/CachedSLEs.h (100%) rename {src/xrpld => include/xrpl}/ledger/CachedView.h (98%) rename {src/xrpld/app/misc => include/xrpl/ledger}/CredentialHelpers.h (98%) rename {src/xrpld => include/xrpl}/ledger/Dir.h (98%) rename {src/xrpld => include/xrpl}/ledger/OpenView.h (98%) rename {src/xrpld => include/xrpl}/ledger/PaymentSandbox.h (98%) rename {src/xrpld => include/xrpl}/ledger/RawView.h (98%) rename {src/xrpld => include/xrpl}/ledger/ReadView.h (98%) rename {src/xrpld => include/xrpl}/ledger/Sandbox.h (95%) rename {src/xrpld => include/xrpl}/ledger/View.h (99%) rename {src/xrpld => include/xrpl}/ledger/detail/ApplyStateTable.h (97%) rename {src/xrpld => include/xrpl}/ledger/detail/ApplyViewBase.h (96%) rename {src/xrpld => include/xrpl}/ledger/detail/RawStateTable.h (98%) rename {src/xrpld => include/xrpl}/ledger/detail/ReadViewFwdRange.h (100%) rename {src/xrpld => include/xrpl}/ledger/detail/ReadViewFwdRange.ipp (100%) rename src/{xrpld/ledger/detail => libxrpl/ledger}/ApplyStateTable.cpp (99%) rename src/{xrpld/ledger/detail => libxrpl/ledger}/ApplyView.cpp (99%) rename src/{xrpld/ledger/detail => libxrpl/ledger}/ApplyViewBase.cpp (98%) rename src/{xrpld/ledger/detail => libxrpl/ledger}/ApplyViewImpl.cpp (97%) rename src/{xrpld/ledger/detail => libxrpl/ledger}/BookDirs.cpp (98%) rename src/{xrpld/ledger/detail => libxrpl/ledger}/CachedView.cpp (98%) rename src/{xrpld/app/misc => libxrpl/ledger}/CredentialHelpers.cpp (99%) rename src/{xrpld/ledger/detail => libxrpl/ledger}/Dir.cpp (99%) rename src/{xrpld/ledger/detail => libxrpl/ledger}/OpenView.cpp (99%) rename src/{xrpld/ledger/detail => libxrpl/ledger}/PaymentSandbox.cpp (99%) rename src/{xrpld/ledger/detail => libxrpl/ledger}/RawStateTable.cpp (99%) rename src/{xrpld/ledger/detail => libxrpl/ledger}/ReadView.cpp (98%) rename src/{xrpld/ledger/detail => libxrpl/ledger}/View.cpp (99%) create mode 100644 src/xrpld/app/tx/detail/Taker.h diff --git a/.github/scripts/levelization/results/loops.txt b/.github/scripts/levelization/results/loops.txt index 0bbd65a9e4..e998e962d4 100644 --- a/.github/scripts/levelization/results/loops.txt +++ b/.github/scripts/levelization/results/loops.txt @@ -7,9 +7,6 @@ Loop: test.jtx test.unit_test Loop: xrpld.app xrpld.core xrpld.app > xrpld.core -Loop: xrpld.app xrpld.ledger - xrpld.app > xrpld.ledger - Loop: xrpld.app xrpld.overlay xrpld.overlay > xrpld.app diff --git a/.github/scripts/levelization/results/ordering.txt b/.github/scripts/levelization/results/ordering.txt index bf2d1db693..13de36e2a5 100644 --- a/.github/scripts/levelization/results/ordering.txt +++ b/.github/scripts/levelization/results/ordering.txt @@ -2,6 +2,10 @@ libxrpl.basics > xrpl.basics libxrpl.crypto > xrpl.basics libxrpl.json > xrpl.basics libxrpl.json > xrpl.json +libxrpl.ledger > xrpl.basics +libxrpl.ledger > xrpl.json +libxrpl.ledger > xrpl.ledger +libxrpl.ledger > xrpl.protocol libxrpl.net > xrpl.basics libxrpl.net > xrpl.net libxrpl.protocol > xrpl.basics @@ -21,11 +25,11 @@ test.app > test.unit_test test.app > xrpl.basics test.app > xrpld.app test.app > xrpld.core -test.app > xrpld.ledger test.app > xrpld.nodestore test.app > xrpld.overlay test.app > xrpld.rpc test.app > xrpl.json +test.app > xrpl.ledger test.app > xrpl.protocol test.app > xrpl.resource test.basics > test.jtx @@ -44,8 +48,8 @@ test.consensus > test.unit_test test.consensus > xrpl.basics test.consensus > xrpld.app test.consensus > xrpld.consensus -test.consensus > xrpld.ledger test.consensus > xrpl.json +test.consensus > xrpl.ledger test.core > test.jtx test.core > test.toplevel test.core > test.unit_test @@ -63,9 +67,9 @@ test.json > xrpl.json test.jtx > xrpl.basics test.jtx > xrpld.app test.jtx > xrpld.core -test.jtx > xrpld.ledger test.jtx > xrpld.rpc test.jtx > xrpl.json +test.jtx > xrpl.ledger test.jtx > xrpl.net test.jtx > xrpl.protocol test.jtx > xrpl.resource @@ -75,7 +79,7 @@ test.ledger > test.toplevel test.ledger > xrpl.basics test.ledger > xrpld.app test.ledger > xrpld.core -test.ledger > xrpld.ledger +test.ledger > xrpl.ledger test.ledger > xrpl.protocol test.nodestore > test.jtx test.nodestore > test.toplevel @@ -135,6 +139,8 @@ test.toplevel > xrpl.json test.unit_test > xrpl.basics tests.libxrpl > xrpl.basics xrpl.json > xrpl.basics +xrpl.ledger > xrpl.basics +xrpl.ledger > xrpl.protocol xrpl.net > xrpl.basics xrpl.protocol > xrpl.basics xrpl.protocol > xrpl.json @@ -151,6 +157,7 @@ xrpld.app > xrpld.consensus xrpld.app > xrpld.nodestore xrpld.app > xrpld.perflog xrpld.app > xrpl.json +xrpld.app > xrpl.ledger xrpld.app > xrpl.net xrpld.app > xrpl.protocol xrpld.app > xrpl.resource @@ -163,9 +170,6 @@ xrpld.core > xrpl.basics xrpld.core > xrpl.json xrpld.core > xrpl.net xrpld.core > xrpl.protocol -xrpld.ledger > xrpl.basics -xrpld.ledger > xrpl.json -xrpld.ledger > xrpl.protocol xrpld.nodestore > xrpl.basics xrpld.nodestore > xrpld.core xrpld.nodestore > xrpld.unity @@ -186,9 +190,9 @@ xrpld.perflog > xrpl.basics xrpld.perflog > xrpl.json xrpld.rpc > xrpl.basics xrpld.rpc > xrpld.core -xrpld.rpc > xrpld.ledger xrpld.rpc > xrpld.nodestore xrpld.rpc > xrpl.json +xrpld.rpc > xrpl.ledger xrpld.rpc > xrpl.net xrpld.rpc > xrpl.protocol xrpld.rpc > xrpl.resource diff --git a/cmake/RippledCore.cmake b/cmake/RippledCore.cmake index 7d3561675a..481b6e3cea 100644 --- a/cmake/RippledCore.cmake +++ b/cmake/RippledCore.cmake @@ -111,6 +111,12 @@ target_link_libraries(xrpl.libxrpl.net PUBLIC add_module(xrpl server) target_link_libraries(xrpl.libxrpl.server PUBLIC xrpl.libxrpl.protocol) +add_module(xrpl ledger) +target_link_libraries(xrpl.libxrpl.ledger PUBLIC + xrpl.libxrpl.basics + xrpl.libxrpl.json + xrpl.libxrpl.protocol +) add_library(xrpl.libxrpl) set_target_properties(xrpl.libxrpl PROPERTIES OUTPUT_NAME xrpl) @@ -131,6 +137,7 @@ target_link_modules(xrpl PUBLIC resource server net + ledger ) # All headers in libxrpl are in modules. diff --git a/cmake/RippledInstall.cmake b/cmake/RippledInstall.cmake index f32781f596..95c25a212f 100644 --- a/cmake/RippledInstall.cmake +++ b/cmake/RippledInstall.cmake @@ -18,6 +18,7 @@ install ( xrpl.libxrpl.json xrpl.libxrpl.protocol xrpl.libxrpl.resource + xrpl.libxrpl.ledger xrpl.libxrpl.server xrpl.libxrpl.net xrpl.libxrpl diff --git a/src/xrpld/ledger/ApplyView.h b/include/xrpl/ledger/ApplyView.h similarity index 99% rename from src/xrpld/ledger/ApplyView.h rename to include/xrpl/ledger/ApplyView.h index 1e4a5a112a..d8b9028d7c 100644 --- a/src/xrpld/ledger/ApplyView.h +++ b/include/xrpl/ledger/ApplyView.h @@ -20,11 +20,10 @@ #ifndef RIPPLE_LEDGER_APPLYVIEW_H_INCLUDED #define RIPPLE_LEDGER_APPLYVIEW_H_INCLUDED -#include -#include - #include #include +#include +#include namespace ripple { diff --git a/src/xrpld/ledger/ApplyViewImpl.h b/include/xrpl/ledger/ApplyViewImpl.h similarity index 97% rename from src/xrpld/ledger/ApplyViewImpl.h rename to include/xrpl/ledger/ApplyViewImpl.h index d170cf71ff..eadcd8acb5 100644 --- a/src/xrpld/ledger/ApplyViewImpl.h +++ b/include/xrpl/ledger/ApplyViewImpl.h @@ -20,9 +20,8 @@ #ifndef RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED #define RIPPLE_LEDGER_APPLYVIEWIMPL_H_INCLUDED -#include -#include - +#include +#include #include #include diff --git a/src/xrpld/ledger/BookDirs.h b/include/xrpl/ledger/BookDirs.h similarity index 98% rename from src/xrpld/ledger/BookDirs.h rename to include/xrpl/ledger/BookDirs.h index dc58905c5a..95cd41e044 100644 --- a/src/xrpld/ledger/BookDirs.h +++ b/include/xrpl/ledger/BookDirs.h @@ -20,9 +20,8 @@ #ifndef RIPPLE_LEDGER_BOOK_DIRS_H_INCLUDED #define RIPPLE_LEDGER_BOOK_DIRS_H_INCLUDED -#include - #include +#include namespace ripple { diff --git a/src/xrpld/ledger/CachedSLEs.h b/include/xrpl/ledger/CachedSLEs.h similarity index 100% rename from src/xrpld/ledger/CachedSLEs.h rename to include/xrpl/ledger/CachedSLEs.h diff --git a/src/xrpld/ledger/CachedView.h b/include/xrpl/ledger/CachedView.h similarity index 98% rename from src/xrpld/ledger/CachedView.h rename to include/xrpl/ledger/CachedView.h index ae59312f98..3e1fb9cc72 100644 --- a/src/xrpld/ledger/CachedView.h +++ b/include/xrpl/ledger/CachedView.h @@ -20,10 +20,9 @@ #ifndef RIPPLE_LEDGER_CACHEDVIEW_H_INCLUDED #define RIPPLE_LEDGER_CACHEDVIEW_H_INCLUDED -#include -#include - #include +#include +#include #include #include diff --git a/src/xrpld/app/misc/CredentialHelpers.h b/include/xrpl/ledger/CredentialHelpers.h similarity index 98% rename from src/xrpld/app/misc/CredentialHelpers.h rename to include/xrpl/ledger/CredentialHelpers.h index 84938180ce..cb6b3c98ee 100644 --- a/src/xrpld/app/misc/CredentialHelpers.h +++ b/include/xrpl/ledger/CredentialHelpers.h @@ -20,12 +20,11 @@ #ifndef RIPPLE_APP_MISC_CREDENTIALHELPERS_H_INCLUDED #define RIPPLE_APP_MISC_CREDENTIALHELPERS_H_INCLUDED -#include -#include - #include #include #include +#include +#include #include #include #include diff --git a/src/xrpld/ledger/Dir.h b/include/xrpl/ledger/Dir.h similarity index 98% rename from src/xrpld/ledger/Dir.h rename to include/xrpl/ledger/Dir.h index d3a52558fd..0e9e5e998f 100644 --- a/src/xrpld/ledger/Dir.h +++ b/include/xrpl/ledger/Dir.h @@ -20,8 +20,7 @@ #ifndef RIPPLE_LEDGER_DIR_H_INCLUDED #define RIPPLE_LEDGER_DIR_H_INCLUDED -#include - +#include #include namespace ripple { diff --git a/src/xrpld/ledger/OpenView.h b/include/xrpl/ledger/OpenView.h similarity index 98% rename from src/xrpld/ledger/OpenView.h rename to include/xrpl/ledger/OpenView.h index a1fa195a69..e856e03764 100644 --- a/src/xrpld/ledger/OpenView.h +++ b/include/xrpl/ledger/OpenView.h @@ -20,10 +20,9 @@ #ifndef RIPPLE_LEDGER_OPENVIEW_H_INCLUDED #define RIPPLE_LEDGER_OPENVIEW_H_INCLUDED -#include -#include -#include - +#include +#include +#include #include #include diff --git a/src/xrpld/ledger/PaymentSandbox.h b/include/xrpl/ledger/PaymentSandbox.h similarity index 98% rename from src/xrpld/ledger/PaymentSandbox.h rename to include/xrpl/ledger/PaymentSandbox.h index 2cd31ea490..c5bf28d1f9 100644 --- a/src/xrpld/ledger/PaymentSandbox.h +++ b/include/xrpl/ledger/PaymentSandbox.h @@ -20,10 +20,9 @@ #ifndef RIPPLE_LEDGER_PAYMENTSANDBOX_H_INCLUDED #define RIPPLE_LEDGER_PAYMENTSANDBOX_H_INCLUDED -#include -#include -#include - +#include +#include +#include #include #include diff --git a/src/xrpld/ledger/RawView.h b/include/xrpl/ledger/RawView.h similarity index 98% rename from src/xrpld/ledger/RawView.h rename to include/xrpl/ledger/RawView.h index fb6dcffe06..c22fd40132 100644 --- a/src/xrpld/ledger/RawView.h +++ b/include/xrpl/ledger/RawView.h @@ -20,8 +20,7 @@ #ifndef RIPPLE_LEDGER_RAWVIEW_H_INCLUDED #define RIPPLE_LEDGER_RAWVIEW_H_INCLUDED -#include - +#include #include #include diff --git a/src/xrpld/ledger/ReadView.h b/include/xrpl/ledger/ReadView.h similarity index 98% rename from src/xrpld/ledger/ReadView.h rename to include/xrpl/ledger/ReadView.h index 4c1986be4e..2c87dbbf6a 100644 --- a/src/xrpld/ledger/ReadView.h +++ b/include/xrpl/ledger/ReadView.h @@ -20,10 +20,9 @@ #ifndef RIPPLE_LEDGER_READVIEW_H_INCLUDED #define RIPPLE_LEDGER_READVIEW_H_INCLUDED -#include - #include #include +#include #include #include #include @@ -280,6 +279,6 @@ makeRulesGivenLedger( } // namespace ripple -#include +#include #endif diff --git a/src/xrpld/ledger/Sandbox.h b/include/xrpl/ledger/Sandbox.h similarity index 95% rename from src/xrpld/ledger/Sandbox.h rename to include/xrpl/ledger/Sandbox.h index 22b8dbecfb..495da120c6 100644 --- a/src/xrpld/ledger/Sandbox.h +++ b/include/xrpl/ledger/Sandbox.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_LEDGER_SANDBOX_H_INCLUDED #define RIPPLE_LEDGER_SANDBOX_H_INCLUDED -#include -#include +#include +#include namespace ripple { diff --git a/src/xrpld/ledger/View.h b/include/xrpl/ledger/View.h similarity index 99% rename from src/xrpld/ledger/View.h rename to include/xrpl/ledger/View.h index faad633e00..3d67e25a22 100644 --- a/src/xrpld/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -20,11 +20,10 @@ #ifndef RIPPLE_LEDGER_VIEW_H_INCLUDED #define RIPPLE_LEDGER_VIEW_H_INCLUDED -#include -#include -#include - #include +#include +#include +#include #include #include #include diff --git a/src/xrpld/ledger/detail/ApplyStateTable.h b/include/xrpl/ledger/detail/ApplyStateTable.h similarity index 97% rename from src/xrpld/ledger/detail/ApplyStateTable.h rename to include/xrpl/ledger/detail/ApplyStateTable.h index 5a2e0bcf54..01ab1e07ab 100644 --- a/src/xrpld/ledger/detail/ApplyStateTable.h +++ b/include/xrpl/ledger/detail/ApplyStateTable.h @@ -20,11 +20,10 @@ #ifndef RIPPLE_LEDGER_APPLYSTATETABLE_H_INCLUDED #define RIPPLE_LEDGER_APPLYSTATETABLE_H_INCLUDED -#include -#include -#include - #include +#include +#include +#include #include #include #include diff --git a/src/xrpld/ledger/detail/ApplyViewBase.h b/include/xrpl/ledger/detail/ApplyViewBase.h similarity index 96% rename from src/xrpld/ledger/detail/ApplyViewBase.h rename to include/xrpl/ledger/detail/ApplyViewBase.h index f9c9c80af0..af86fc23b4 100644 --- a/src/xrpld/ledger/detail/ApplyViewBase.h +++ b/include/xrpl/ledger/detail/ApplyViewBase.h @@ -20,10 +20,9 @@ #ifndef RIPPLE_LEDGER_APPLYVIEWBASE_H_INCLUDED #define RIPPLE_LEDGER_APPLYVIEWBASE_H_INCLUDED -#include -#include -#include - +#include +#include +#include #include namespace ripple { diff --git a/src/xrpld/ledger/detail/RawStateTable.h b/include/xrpl/ledger/detail/RawStateTable.h similarity index 98% rename from src/xrpld/ledger/detail/RawStateTable.h rename to include/xrpl/ledger/detail/RawStateTable.h index 37597aa678..53a6f52208 100644 --- a/src/xrpld/ledger/detail/RawStateTable.h +++ b/include/xrpl/ledger/detail/RawStateTable.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_LEDGER_RAWSTATETABLE_H_INCLUDED #define RIPPLE_LEDGER_RAWSTATETABLE_H_INCLUDED -#include -#include +#include +#include #include #include diff --git a/src/xrpld/ledger/detail/ReadViewFwdRange.h b/include/xrpl/ledger/detail/ReadViewFwdRange.h similarity index 100% rename from src/xrpld/ledger/detail/ReadViewFwdRange.h rename to include/xrpl/ledger/detail/ReadViewFwdRange.h diff --git a/src/xrpld/ledger/detail/ReadViewFwdRange.ipp b/include/xrpl/ledger/detail/ReadViewFwdRange.ipp similarity index 100% rename from src/xrpld/ledger/detail/ReadViewFwdRange.ipp rename to include/xrpl/ledger/detail/ReadViewFwdRange.ipp diff --git a/src/xrpld/ledger/detail/ApplyStateTable.cpp b/src/libxrpl/ledger/ApplyStateTable.cpp similarity index 99% rename from src/xrpld/ledger/detail/ApplyStateTable.cpp rename to src/libxrpl/ledger/ApplyStateTable.cpp index 2a740093d9..7b041939d4 100644 --- a/src/xrpld/ledger/detail/ApplyStateTable.cpp +++ b/src/libxrpl/ledger/ApplyStateTable.cpp @@ -17,11 +17,10 @@ */ //============================================================================== -#include - #include #include #include +#include #include #include diff --git a/src/xrpld/ledger/detail/ApplyView.cpp b/src/libxrpl/ledger/ApplyView.cpp similarity index 99% rename from src/xrpld/ledger/detail/ApplyView.cpp rename to src/libxrpl/ledger/ApplyView.cpp index 3191b47cbb..8a0fd51bd8 100644 --- a/src/xrpld/ledger/detail/ApplyView.cpp +++ b/src/libxrpl/ledger/ApplyView.cpp @@ -17,10 +17,9 @@ */ //============================================================================== -#include - #include #include +#include #include namespace ripple { diff --git a/src/xrpld/ledger/detail/ApplyViewBase.cpp b/src/libxrpl/ledger/ApplyViewBase.cpp similarity index 98% rename from src/xrpld/ledger/detail/ApplyViewBase.cpp rename to src/libxrpl/ledger/ApplyViewBase.cpp index 1d93eae7aa..dd0e043474 100644 --- a/src/xrpld/ledger/detail/ApplyViewBase.cpp +++ b/src/libxrpl/ledger/ApplyViewBase.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { namespace detail { diff --git a/src/xrpld/ledger/detail/ApplyViewImpl.cpp b/src/libxrpl/ledger/ApplyViewImpl.cpp similarity index 97% rename from src/xrpld/ledger/detail/ApplyViewImpl.cpp rename to src/libxrpl/ledger/ApplyViewImpl.cpp index 3fd9478b54..9429bcee6e 100644 --- a/src/xrpld/ledger/detail/ApplyViewImpl.cpp +++ b/src/libxrpl/ledger/ApplyViewImpl.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/xrpld/ledger/detail/BookDirs.cpp b/src/libxrpl/ledger/BookDirs.cpp similarity index 98% rename from src/xrpld/ledger/detail/BookDirs.cpp rename to src/libxrpl/ledger/BookDirs.cpp index 41a14945a6..f777d23aca 100644 --- a/src/xrpld/ledger/detail/BookDirs.cpp +++ b/src/libxrpl/ledger/BookDirs.cpp @@ -18,9 +18,8 @@ */ //============================================================================== -#include -#include - +#include +#include #include namespace ripple { diff --git a/src/xrpld/ledger/detail/CachedView.cpp b/src/libxrpl/ledger/CachedView.cpp similarity index 98% rename from src/xrpld/ledger/detail/CachedView.cpp rename to src/libxrpl/ledger/CachedView.cpp index 365d63e400..0bec9094d2 100644 --- a/src/xrpld/ledger/detail/CachedView.cpp +++ b/src/libxrpl/ledger/CachedView.cpp @@ -17,9 +17,8 @@ */ //============================================================================== -#include - #include +#include namespace ripple { namespace detail { diff --git a/src/xrpld/app/misc/CredentialHelpers.cpp b/src/libxrpl/ledger/CredentialHelpers.cpp similarity index 99% rename from src/xrpld/app/misc/CredentialHelpers.cpp rename to src/libxrpl/ledger/CredentialHelpers.cpp index 6d1f9f78c5..965d6f6911 100644 --- a/src/xrpld/app/misc/CredentialHelpers.cpp +++ b/src/libxrpl/ledger/CredentialHelpers.cpp @@ -17,9 +17,8 @@ */ //============================================================================== -#include -#include - +#include +#include #include #include diff --git a/src/xrpld/ledger/detail/Dir.cpp b/src/libxrpl/ledger/Dir.cpp similarity index 99% rename from src/xrpld/ledger/detail/Dir.cpp rename to src/libxrpl/ledger/Dir.cpp index 01d4487276..ea57369495 100644 --- a/src/xrpld/ledger/detail/Dir.cpp +++ b/src/libxrpl/ledger/Dir.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/xrpld/ledger/detail/OpenView.cpp b/src/libxrpl/ledger/OpenView.cpp similarity index 99% rename from src/xrpld/ledger/detail/OpenView.cpp rename to src/libxrpl/ledger/OpenView.cpp index 73e502a5e2..c40434b38d 100644 --- a/src/xrpld/ledger/detail/OpenView.cpp +++ b/src/libxrpl/ledger/OpenView.cpp @@ -17,9 +17,8 @@ */ //============================================================================== -#include - #include +#include namespace ripple { diff --git a/src/xrpld/ledger/detail/PaymentSandbox.cpp b/src/libxrpl/ledger/PaymentSandbox.cpp similarity index 99% rename from src/xrpld/ledger/detail/PaymentSandbox.cpp rename to src/libxrpl/ledger/PaymentSandbox.cpp index 3eab845472..ba59b573fa 100644 --- a/src/xrpld/ledger/detail/PaymentSandbox.cpp +++ b/src/libxrpl/ledger/PaymentSandbox.cpp @@ -17,11 +17,9 @@ */ //============================================================================== -#include -#include -#include - #include +#include +#include #include namespace ripple { diff --git a/src/xrpld/ledger/detail/RawStateTable.cpp b/src/libxrpl/ledger/RawStateTable.cpp similarity index 99% rename from src/xrpld/ledger/detail/RawStateTable.cpp rename to src/libxrpl/ledger/RawStateTable.cpp index f19eed8297..e6b1467581 100644 --- a/src/xrpld/ledger/detail/RawStateTable.cpp +++ b/src/libxrpl/ledger/RawStateTable.cpp @@ -17,9 +17,8 @@ */ //============================================================================== -#include - #include +#include namespace ripple { namespace detail { diff --git a/src/xrpld/ledger/detail/ReadView.cpp b/src/libxrpl/ledger/ReadView.cpp similarity index 98% rename from src/xrpld/ledger/detail/ReadView.cpp rename to src/libxrpl/ledger/ReadView.cpp index 69a4b5d6a9..449a5a9cec 100644 --- a/src/xrpld/ledger/detail/ReadView.cpp +++ b/src/libxrpl/ledger/ReadView.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { diff --git a/src/xrpld/ledger/detail/View.cpp b/src/libxrpl/ledger/View.cpp similarity index 99% rename from src/xrpld/ledger/detail/View.cpp rename to src/libxrpl/ledger/View.cpp index 473efa58fb..45aded0030 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -17,14 +17,13 @@ */ //============================================================================== -#include -#include -#include - #include #include #include #include +#include +#include +#include #include #include #include diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index 893e9e4f75..cb937038fe 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -29,8 +29,8 @@ #include #include #include -#include +#include #include #include diff --git a/src/test/app/Credentials_test.cpp b/src/test/app/Credentials_test.cpp index 54826cbb12..23aa7ad952 100644 --- a/src/test/app/Credentials_test.cpp +++ b/src/test/app/Credentials_test.cpp @@ -19,10 +19,9 @@ #include -#include -#include - #include +#include +#include #include #include #include diff --git a/src/test/app/EscrowToken_test.cpp b/src/test/app/EscrowToken_test.cpp index 28c9a5b167..9c1868134f 100644 --- a/src/test/app/EscrowToken_test.cpp +++ b/src/test/app/EscrowToken_test.cpp @@ -20,9 +20,9 @@ #include #include -#include -#include +#include +#include #include #include #include diff --git a/src/test/app/Escrow_test.cpp b/src/test/app/Escrow_test.cpp index 19b8612ef4..cea3a835a6 100644 --- a/src/test/app/Escrow_test.cpp +++ b/src/test/app/Escrow_test.cpp @@ -20,8 +20,8 @@ #include #include -#include +#include #include #include #include diff --git a/src/test/app/Flow_test.cpp b/src/test/app/Flow_test.cpp index 0f40d70b57..23095b0145 100644 --- a/src/test/app/Flow_test.cpp +++ b/src/test/app/Flow_test.cpp @@ -23,10 +23,10 @@ #include #include #include -#include -#include #include +#include +#include #include namespace ripple { diff --git a/src/test/app/LedgerHistory_test.cpp b/src/test/app/LedgerHistory_test.cpp index 7b1910bd4d..1d440f6420 100644 --- a/src/test/app/LedgerHistory_test.cpp +++ b/src/test/app/LedgerHistory_test.cpp @@ -23,10 +23,10 @@ #include #include #include -#include #include #include +#include #include #include diff --git a/src/test/app/LoadFeeTrack_test.cpp b/src/test/app/LoadFeeTrack_test.cpp index 8a88e0273f..80110b073d 100644 --- a/src/test/app/LoadFeeTrack_test.cpp +++ b/src/test/app/LoadFeeTrack_test.cpp @@ -19,9 +19,9 @@ #include #include -#include #include +#include namespace ripple { diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index 3d0557fd5c..fe9b70cf7f 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -19,10 +19,10 @@ #include -#include #include #include +#include #include #include #include diff --git a/src/test/app/PayStrand_test.cpp b/src/test/app/PayStrand_test.cpp index 936fe403d4..16a6861bfd 100644 --- a/src/test/app/PayStrand_test.cpp +++ b/src/test/app/PayStrand_test.cpp @@ -22,10 +22,10 @@ #include #include #include -#include #include #include +#include #include #include diff --git a/src/test/app/PermissionedDEX_test.cpp b/src/test/app/PermissionedDEX_test.cpp index 3fd3a35f45..80c75a2daf 100644 --- a/src/test/app/PermissionedDEX_test.cpp +++ b/src/test/app/PermissionedDEX_test.cpp @@ -22,11 +22,11 @@ #include #include -#include #include #include #include +#include #include #include #include diff --git a/src/test/app/TheoreticalQuality_test.cpp b/src/test/app/TheoreticalQuality_test.cpp index a8713ec69a..814e6f7136 100644 --- a/src/test/app/TheoreticalQuality_test.cpp +++ b/src/test/app/TheoreticalQuality_test.cpp @@ -24,10 +24,10 @@ #include #include #include -#include #include #include +#include #include #include diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index 2216ff6421..3cd52eaad3 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -21,14 +21,13 @@ #include #include -#include -#include - #include #include #include #include #include +#include +#include #include #include #include diff --git a/src/test/consensus/NegativeUNL_test.cpp b/src/test/consensus/NegativeUNL_test.cpp index b56b834726..cc38ea5ab6 100644 --- a/src/test/consensus/NegativeUNL_test.cpp +++ b/src/test/consensus/NegativeUNL_test.cpp @@ -24,9 +24,9 @@ #include #include #include -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/owners.h b/src/test/jtx/owners.h index fc904f9e87..9b6f6a6df5 100644 --- a/src/test/jtx/owners.h +++ b/src/test/jtx/owners.h @@ -22,8 +22,7 @@ #include -#include - +#include #include #include diff --git a/src/test/ledger/BookDirs_test.cpp b/src/test/ledger/BookDirs_test.cpp index 52b618e9a0..48ee92a6fd 100644 --- a/src/test/ledger/BookDirs_test.cpp +++ b/src/test/ledger/BookDirs_test.cpp @@ -17,8 +17,7 @@ #include -#include - +#include #include namespace ripple { diff --git a/src/test/ledger/Directory_test.cpp b/src/test/ledger/Directory_test.cpp index 9e8d40e0cc..fe8f04523f 100644 --- a/src/test/ledger/Directory_test.cpp +++ b/src/test/ledger/Directory_test.cpp @@ -17,10 +17,9 @@ #include -#include -#include - #include +#include +#include #include #include #include diff --git a/src/test/ledger/PaymentSandbox_test.cpp b/src/test/ledger/PaymentSandbox_test.cpp index 26b06a0034..db7fbed019 100644 --- a/src/test/ledger/PaymentSandbox_test.cpp +++ b/src/test/ledger/PaymentSandbox_test.cpp @@ -19,10 +19,9 @@ #include -#include -#include -#include - +#include +#include +#include #include #include diff --git a/src/test/ledger/SkipList_test.cpp b/src/test/ledger/SkipList_test.cpp index c2088d77cf..e0f4049c73 100644 --- a/src/test/ledger/SkipList_test.cpp +++ b/src/test/ledger/SkipList_test.cpp @@ -20,9 +20,9 @@ #include #include -#include #include +#include namespace ripple { namespace test { diff --git a/src/test/ledger/View_test.cpp b/src/test/ledger/View_test.cpp index 4af3e37ce2..17d3244aa2 100644 --- a/src/test/ledger/View_test.cpp +++ b/src/test/ledger/View_test.cpp @@ -21,11 +21,11 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/src/xrpld/app/consensus/RCLCxLedger.h b/src/xrpld/app/consensus/RCLCxLedger.h index cd14c30a94..f9df1fe41a 100644 --- a/src/xrpld/app/consensus/RCLCxLedger.h +++ b/src/xrpld/app/consensus/RCLCxLedger.h @@ -22,8 +22,8 @@ #include #include -#include +#include #include namespace ripple { diff --git a/src/xrpld/app/ledger/BuildLedger.h b/src/xrpld/app/ledger/BuildLedger.h index 2ec571773c..980fe82ed8 100644 --- a/src/xrpld/app/ledger/BuildLedger.h +++ b/src/xrpld/app/ledger/BuildLedger.h @@ -20,10 +20,9 @@ #ifndef RIPPLE_APP_LEDGER_BUILD_LEDGER_H_INCLUDED #define RIPPLE_APP_LEDGER_BUILD_LEDGER_H_INCLUDED -#include - #include #include +#include namespace ripple { diff --git a/src/xrpld/app/ledger/Ledger.h b/src/xrpld/app/ledger/Ledger.h index 81c26526e5..552f59ff19 100644 --- a/src/xrpld/app/ledger/Ledger.h +++ b/src/xrpld/app/ledger/Ledger.h @@ -22,12 +22,12 @@ #include #include -#include -#include #include #include #include +#include +#include #include #include #include diff --git a/src/xrpld/app/ledger/LocalTxs.h b/src/xrpld/app/ledger/LocalTxs.h index 391bb4f7ef..2d202a5b60 100644 --- a/src/xrpld/app/ledger/LocalTxs.h +++ b/src/xrpld/app/ledger/LocalTxs.h @@ -21,7 +21,8 @@ #define RIPPLE_APP_LEDGER_LOCALTXS_H_INCLUDED #include -#include + +#include #include diff --git a/src/xrpld/app/ledger/OpenLedger.h b/src/xrpld/app/ledger/OpenLedger.h index 9fe56ff488..9383a53575 100644 --- a/src/xrpld/app/ledger/OpenLedger.h +++ b/src/xrpld/app/ledger/OpenLedger.h @@ -23,13 +23,13 @@ #include #include #include -#include -#include #include #include #include #include +#include +#include #include diff --git a/src/xrpld/app/ledger/detail/OpenLedger.cpp b/src/xrpld/app/ledger/detail/OpenLedger.cpp index 2c98caaa6d..5bba544e31 100644 --- a/src/xrpld/app/ledger/detail/OpenLedger.cpp +++ b/src/xrpld/app/ledger/detail/OpenLedger.cpp @@ -22,10 +22,10 @@ #include #include #include -#include #include #include +#include #include #include diff --git a/src/xrpld/app/misc/AMMUtils.h b/src/xrpld/app/misc/AMMUtils.h index 2a9f82ae60..d89085b116 100644 --- a/src/xrpld/app/misc/AMMUtils.h +++ b/src/xrpld/app/misc/AMMUtils.h @@ -20,10 +20,9 @@ #ifndef RIPPLE_APP_MISC_AMMUTILS_H_INCLUDED #define RIPPLE_APP_MISC_AMMUTILS_H_INCLUDED -#include - #include #include +#include #include #include #include diff --git a/src/xrpld/app/misc/FeeVote.h b/src/xrpld/app/misc/FeeVote.h index 35f723aa02..543456785a 100644 --- a/src/xrpld/app/misc/FeeVote.h +++ b/src/xrpld/app/misc/FeeVote.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_MISC_FEEVOTE_H_INCLUDED #define RIPPLE_APP_MISC_FEEVOTE_H_INCLUDED -#include #include +#include #include namespace ripple { diff --git a/src/xrpld/app/misc/NetworkOPs.h b/src/xrpld/app/misc/NetworkOPs.h index 9587d63b3a..bec0ad2341 100644 --- a/src/xrpld/app/misc/NetworkOPs.h +++ b/src/xrpld/app/misc/NetworkOPs.h @@ -23,9 +23,9 @@ #include #include #include -#include #include +#include #include #include diff --git a/src/xrpld/app/misc/PermissionedDEXHelpers.cpp b/src/xrpld/app/misc/PermissionedDEXHelpers.cpp index 4251ac1519..279ef8d7e9 100644 --- a/src/xrpld/app/misc/PermissionedDEXHelpers.cpp +++ b/src/xrpld/app/misc/PermissionedDEXHelpers.cpp @@ -17,9 +17,10 @@ */ //============================================================================== -#include #include +#include + namespace ripple { namespace permissioned_dex { diff --git a/src/xrpld/app/misc/PermissionedDEXHelpers.h b/src/xrpld/app/misc/PermissionedDEXHelpers.h index 1b3a0323fd..409b32f4ba 100644 --- a/src/xrpld/app/misc/PermissionedDEXHelpers.h +++ b/src/xrpld/app/misc/PermissionedDEXHelpers.h @@ -18,7 +18,7 @@ //============================================================================== #pragma once -#include +#include namespace ripple { namespace permissioned_dex { diff --git a/src/xrpld/app/misc/TxQ.h b/src/xrpld/app/misc/TxQ.h index f6ac2c6861..6afd83b0d0 100644 --- a/src/xrpld/app/misc/TxQ.h +++ b/src/xrpld/app/misc/TxQ.h @@ -21,9 +21,9 @@ #define RIPPLE_TXQ_H_INCLUDED #include -#include -#include +#include +#include #include #include #include diff --git a/src/xrpld/app/misc/detail/AMMUtils.cpp b/src/xrpld/app/misc/detail/AMMUtils.cpp index b56ce2748e..94cdb04287 100644 --- a/src/xrpld/app/misc/detail/AMMUtils.cpp +++ b/src/xrpld/app/misc/detail/AMMUtils.cpp @@ -19,9 +19,9 @@ #include #include -#include #include +#include #include #include diff --git a/src/xrpld/app/misc/detail/LoadFeeTrack.cpp b/src/xrpld/app/misc/detail/LoadFeeTrack.cpp index 96e7555401..776e9fa50b 100644 --- a/src/xrpld/app/misc/detail/LoadFeeTrack.cpp +++ b/src/xrpld/app/misc/detail/LoadFeeTrack.cpp @@ -18,11 +18,11 @@ //============================================================================== #include -#include #include #include #include +#include #include #include diff --git a/src/xrpld/app/paths/AMMLiquidity.h b/src/xrpld/app/paths/AMMLiquidity.h index ee745b4a8a..cb1db93705 100644 --- a/src/xrpld/app/paths/AMMLiquidity.h +++ b/src/xrpld/app/paths/AMMLiquidity.h @@ -23,10 +23,10 @@ #include #include #include -#include -#include #include +#include +#include #include namespace ripple { diff --git a/src/xrpld/app/paths/AMMOffer.h b/src/xrpld/app/paths/AMMOffer.h index 9241ba2057..3c218c0f5e 100644 --- a/src/xrpld/app/paths/AMMOffer.h +++ b/src/xrpld/app/paths/AMMOffer.h @@ -20,9 +20,8 @@ #ifndef RIPPLE_APP_AMMOFFER_H_INCLUDED #define RIPPLE_APP_AMMOFFER_H_INCLUDED -#include -#include - +#include +#include #include #include diff --git a/src/xrpld/app/paths/Credit.cpp b/src/xrpld/app/paths/Credit.cpp index ca721e4edf..f975385b28 100644 --- a/src/xrpld/app/paths/Credit.cpp +++ b/src/xrpld/app/paths/Credit.cpp @@ -17,8 +17,7 @@ */ //============================================================================== -#include - +#include #include #include #include diff --git a/src/xrpld/app/paths/Credit.h b/src/xrpld/app/paths/Credit.h index 0ea2d687dd..12ea27a734 100644 --- a/src/xrpld/app/paths/Credit.h +++ b/src/xrpld/app/paths/Credit.h @@ -20,8 +20,7 @@ #ifndef RIPPLE_APP_PATHS_CREDIT_H_INCLUDED #define RIPPLE_APP_PATHS_CREDIT_H_INCLUDED -#include - +#include #include #include diff --git a/src/xrpld/app/paths/Pathfinder.cpp b/src/xrpld/app/paths/Pathfinder.cpp index 74a33ec917..41a3697888 100644 --- a/src/xrpld/app/paths/Pathfinder.cpp +++ b/src/xrpld/app/paths/Pathfinder.cpp @@ -24,11 +24,11 @@ #include #include #include -#include #include #include #include +#include #include diff --git a/src/xrpld/app/paths/RippleCalc.cpp b/src/xrpld/app/paths/RippleCalc.cpp index 9c438bdfa9..237f0b8f9c 100644 --- a/src/xrpld/app/paths/RippleCalc.cpp +++ b/src/xrpld/app/paths/RippleCalc.cpp @@ -20,9 +20,9 @@ #include #include #include -#include #include +#include #include namespace ripple { diff --git a/src/xrpld/app/paths/RippleCalc.h b/src/xrpld/app/paths/RippleCalc.h index 09de7334e8..527c26f2ca 100644 --- a/src/xrpld/app/paths/RippleCalc.h +++ b/src/xrpld/app/paths/RippleCalc.h @@ -20,9 +20,8 @@ #ifndef RIPPLE_APP_PATHS_RIPPLECALC_H_INCLUDED #define RIPPLE_APP_PATHS_RIPPLECALC_H_INCLUDED -#include - #include +#include #include #include diff --git a/src/xrpld/app/paths/TrustLine.h b/src/xrpld/app/paths/TrustLine.h index efbe281f5e..5cff89b177 100644 --- a/src/xrpld/app/paths/TrustLine.h +++ b/src/xrpld/app/paths/TrustLine.h @@ -20,9 +20,8 @@ #ifndef RIPPLE_APP_PATHS_RIPPLESTATE_H_INCLUDED #define RIPPLE_APP_PATHS_RIPPLESTATE_H_INCLUDED -#include - #include +#include #include #include #include diff --git a/src/xrpld/app/paths/detail/BookStep.cpp b/src/xrpld/app/paths/detail/BookStep.cpp index 554d2525f5..97cf87c046 100644 --- a/src/xrpld/app/paths/detail/BookStep.cpp +++ b/src/xrpld/app/paths/detail/BookStep.cpp @@ -23,11 +23,11 @@ #include #include #include -#include #include #include #include +#include #include #include #include diff --git a/src/xrpld/app/paths/detail/DirectStep.cpp b/src/xrpld/app/paths/detail/DirectStep.cpp index 5e62a289a3..03d207e008 100644 --- a/src/xrpld/app/paths/detail/DirectStep.cpp +++ b/src/xrpld/app/paths/detail/DirectStep.cpp @@ -20,9 +20,9 @@ #include #include #include -#include #include +#include #include #include #include diff --git a/src/xrpld/app/paths/detail/FlowDebugInfo.h b/src/xrpld/app/paths/detail/FlowDebugInfo.h index e28b34f5d1..eec1d7c5a6 100644 --- a/src/xrpld/app/paths/detail/FlowDebugInfo.h +++ b/src/xrpld/app/paths/detail/FlowDebugInfo.h @@ -21,8 +21,8 @@ #define RIPPLE_PATH_IMPL_FLOWDEBUGINFO_H_INCLUDED #include -#include +#include #include #include diff --git a/src/xrpld/app/paths/detail/PaySteps.cpp b/src/xrpld/app/paths/detail/PaySteps.cpp index aa9e21e182..6eb38eee83 100644 --- a/src/xrpld/app/paths/detail/PaySteps.cpp +++ b/src/xrpld/app/paths/detail/PaySteps.cpp @@ -18,10 +18,10 @@ //============================================================================== #include -#include #include #include +#include #include #include diff --git a/src/xrpld/app/paths/detail/StepChecks.h b/src/xrpld/app/paths/detail/StepChecks.h index 4acafd1b9a..5ca2a463cb 100644 --- a/src/xrpld/app/paths/detail/StepChecks.h +++ b/src/xrpld/app/paths/detail/StepChecks.h @@ -20,11 +20,10 @@ #ifndef RIPPLE_APP_PATHS_IMPL_STEP_CHECKS_H_INCLUDED #define RIPPLE_APP_PATHS_IMPL_STEP_CHECKS_H_INCLUDED -#include -#include - #include #include +#include +#include #include #include diff --git a/src/xrpld/app/paths/detail/XRPEndpointStep.cpp b/src/xrpld/app/paths/detail/XRPEndpointStep.cpp index 7fdfb3749d..9cbcb0c84d 100644 --- a/src/xrpld/app/paths/detail/XRPEndpointStep.cpp +++ b/src/xrpld/app/paths/detail/XRPEndpointStep.cpp @@ -21,9 +21,9 @@ #include #include #include -#include #include +#include #include #include #include diff --git a/src/xrpld/app/tx/apply.h b/src/xrpld/app/tx/apply.h index 101f9a946d..7f43d1a744 100644 --- a/src/xrpld/app/tx/apply.h +++ b/src/xrpld/app/tx/apply.h @@ -22,9 +22,9 @@ #include #include -#include #include +#include #include #include diff --git a/src/xrpld/app/tx/applySteps.h b/src/xrpld/app/tx/applySteps.h index ec7180e263..a543ac37de 100644 --- a/src/xrpld/app/tx/applySteps.h +++ b/src/xrpld/app/tx/applySteps.h @@ -20,9 +20,8 @@ #ifndef RIPPLE_TX_APPLYSTEPS_H_INCLUDED #define RIPPLE_TX_APPLYSTEPS_H_INCLUDED -#include - #include +#include namespace ripple { diff --git a/src/xrpld/app/tx/detail/AMMBid.cpp b/src/xrpld/app/tx/detail/AMMBid.cpp index 86a80431b4..806c075c4f 100644 --- a/src/xrpld/app/tx/detail/AMMBid.cpp +++ b/src/xrpld/app/tx/detail/AMMBid.cpp @@ -20,9 +20,9 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/AMMClawback.cpp b/src/xrpld/app/tx/detail/AMMClawback.cpp index 07c5151727..634b948a64 100644 --- a/src/xrpld/app/tx/detail/AMMClawback.cpp +++ b/src/xrpld/app/tx/detail/AMMClawback.cpp @@ -21,9 +21,9 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/AMMCreate.cpp b/src/xrpld/app/tx/detail/AMMCreate.cpp index f0ccc6f298..03c972f1cd 100644 --- a/src/xrpld/app/tx/detail/AMMCreate.cpp +++ b/src/xrpld/app/tx/detail/AMMCreate.cpp @@ -21,9 +21,9 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/AMMDelete.cpp b/src/xrpld/app/tx/detail/AMMDelete.cpp index 28d56eab98..004e0b2229 100644 --- a/src/xrpld/app/tx/detail/AMMDelete.cpp +++ b/src/xrpld/app/tx/detail/AMMDelete.cpp @@ -19,8 +19,8 @@ #include #include -#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/AMMDeposit.cpp b/src/xrpld/app/tx/detail/AMMDeposit.cpp index 0dafa0da6c..614d788c71 100644 --- a/src/xrpld/app/tx/detail/AMMDeposit.cpp +++ b/src/xrpld/app/tx/detail/AMMDeposit.cpp @@ -20,9 +20,9 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/AMMVote.cpp b/src/xrpld/app/tx/detail/AMMVote.cpp index 84d0905a22..6fbff86056 100644 --- a/src/xrpld/app/tx/detail/AMMVote.cpp +++ b/src/xrpld/app/tx/detail/AMMVote.cpp @@ -19,8 +19,8 @@ #include #include -#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp index 2ad1a19df5..9bc36efc81 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.cpp +++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp @@ -20,9 +20,9 @@ #include #include #include -#include #include +#include #include #include diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.h b/src/xrpld/app/tx/detail/AMMWithdraw.h index 1de91fd787..e9a597bdb7 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.h +++ b/src/xrpld/app/tx/detail/AMMWithdraw.h @@ -21,7 +21,8 @@ #define RIPPLE_TX_AMMWITHDRAW_H_INCLUDED #include -#include + +#include namespace ripple { diff --git a/src/xrpld/app/tx/detail/ApplyContext.h b/src/xrpld/app/tx/detail/ApplyContext.h index 720d0aeea3..0344771a43 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.h +++ b/src/xrpld/app/tx/detail/ApplyContext.h @@ -22,9 +22,9 @@ #include #include -#include #include +#include #include #include diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 40991ea99a..86d6e8a8f4 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -19,10 +19,10 @@ #include #include -#include -#include #include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/BookTip.h b/src/xrpld/app/tx/detail/BookTip.h index f215cdb620..0a9c49a4e9 100644 --- a/src/xrpld/app/tx/detail/BookTip.h +++ b/src/xrpld/app/tx/detail/BookTip.h @@ -20,8 +20,7 @@ #ifndef RIPPLE_APP_BOOK_BOOKTIP_H_INCLUDED #define RIPPLE_APP_BOOK_BOOKTIP_H_INCLUDED -#include - +#include #include #include diff --git a/src/xrpld/app/tx/detail/CancelCheck.cpp b/src/xrpld/app/tx/detail/CancelCheck.cpp index cfa3bd10e2..39d0d23096 100644 --- a/src/xrpld/app/tx/detail/CancelCheck.cpp +++ b/src/xrpld/app/tx/detail/CancelCheck.cpp @@ -19,9 +19,9 @@ #include #include -#include #include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/CancelOffer.cpp b/src/xrpld/app/tx/detail/CancelOffer.cpp index 004ae1e8b9..e0a5c7baa7 100644 --- a/src/xrpld/app/tx/detail/CancelOffer.cpp +++ b/src/xrpld/app/tx/detail/CancelOffer.cpp @@ -18,9 +18,9 @@ //============================================================================== #include -#include #include +#include #include namespace ripple { diff --git a/src/xrpld/app/tx/detail/Change.cpp b/src/xrpld/app/tx/detail/Change.cpp index 1392d84c08..de30ed5f62 100644 --- a/src/xrpld/app/tx/detail/Change.cpp +++ b/src/xrpld/app/tx/detail/Change.cpp @@ -22,9 +22,9 @@ #include #include #include -#include #include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/Clawback.cpp b/src/xrpld/app/tx/detail/Clawback.cpp index 41ab1256fb..08cf4baef0 100644 --- a/src/xrpld/app/tx/detail/Clawback.cpp +++ b/src/xrpld/app/tx/detail/Clawback.cpp @@ -18,8 +18,8 @@ //============================================================================== #include -#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/CreateCheck.cpp b/src/xrpld/app/tx/detail/CreateCheck.cpp index 9baceef944..4dbfd1f81d 100644 --- a/src/xrpld/app/tx/detail/CreateCheck.cpp +++ b/src/xrpld/app/tx/detail/CreateCheck.cpp @@ -18,9 +18,9 @@ //============================================================================== #include -#include #include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/CreateOffer.cpp b/src/xrpld/app/tx/detail/CreateOffer.cpp index 3cfae92cbd..6185e52183 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.cpp +++ b/src/xrpld/app/tx/detail/CreateOffer.cpp @@ -21,10 +21,10 @@ #include #include #include -#include #include #include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/Credentials.cpp b/src/xrpld/app/tx/detail/Credentials.cpp index 73c397cf37..b30ae200b7 100644 --- a/src/xrpld/app/tx/detail/Credentials.cpp +++ b/src/xrpld/app/tx/detail/Credentials.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include #include -#include -#include #include +#include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/DID.cpp b/src/xrpld/app/tx/detail/DID.cpp index 31ce7c8770..8c4a220844 100644 --- a/src/xrpld/app/tx/detail/DID.cpp +++ b/src/xrpld/app/tx/detail/DID.cpp @@ -18,10 +18,10 @@ //============================================================================== #include -#include -#include #include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/DelegateSet.cpp b/src/xrpld/app/tx/detail/DelegateSet.cpp index ddeb01b399..53052fd75b 100644 --- a/src/xrpld/app/tx/detail/DelegateSet.cpp +++ b/src/xrpld/app/tx/detail/DelegateSet.cpp @@ -18,9 +18,9 @@ //============================================================================== #include -#include #include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/DeleteAccount.cpp b/src/xrpld/app/tx/detail/DeleteAccount.cpp index 4311aa79a8..deb1743991 100644 --- a/src/xrpld/app/tx/detail/DeleteAccount.cpp +++ b/src/xrpld/app/tx/detail/DeleteAccount.cpp @@ -17,7 +17,6 @@ */ //============================================================================== -#include #include #include #include @@ -25,11 +24,12 @@ #include #include #include -#include #include #include #include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/DeleteOracle.cpp b/src/xrpld/app/tx/detail/DeleteOracle.cpp index 78e3d55230..ac195d100c 100644 --- a/src/xrpld/app/tx/detail/DeleteOracle.cpp +++ b/src/xrpld/app/tx/detail/DeleteOracle.cpp @@ -18,8 +18,8 @@ //============================================================================== #include -#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/DepositPreauth.cpp b/src/xrpld/app/tx/detail/DepositPreauth.cpp index f10f09b38f..0e8c5c05d2 100644 --- a/src/xrpld/app/tx/detail/DepositPreauth.cpp +++ b/src/xrpld/app/tx/detail/DepositPreauth.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include #include -#include #include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index 3c15278efc..ace7437098 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -17,17 +17,17 @@ */ //============================================================================== -#include #include #include #include #include #include -#include -#include #include #include +#include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index da0dfc117f..f20a49366b 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -19,14 +19,14 @@ #include #include -#include #include #include #include -#include -#include #include +#include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/LedgerStateFix.cpp b/src/xrpld/app/tx/detail/LedgerStateFix.cpp index 008d9b6ae7..b861f1d0ef 100644 --- a/src/xrpld/app/tx/detail/LedgerStateFix.cpp +++ b/src/xrpld/app/tx/detail/LedgerStateFix.cpp @@ -19,8 +19,8 @@ #include #include -#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp b/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp index 77fe19a287..1c6d153ec5 100644 --- a/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp +++ b/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp @@ -18,8 +18,8 @@ //============================================================================== #include -#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp index c195e45c1d..478ef17bb0 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp @@ -18,8 +18,8 @@ //============================================================================== #include -#include +#include #include #include diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceDestroy.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceDestroy.cpp index e2b87dbd79..2c330ba8f7 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceDestroy.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceDestroy.cpp @@ -18,8 +18,8 @@ //============================================================================== #include -#include +#include #include #include diff --git a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp index ab74e5ac39..0cf6a86a37 100644 --- a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp @@ -19,8 +19,8 @@ #include #include -#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/NFTokenCancelOffer.cpp b/src/xrpld/app/tx/detail/NFTokenCancelOffer.cpp index f6072bc953..3d0bf04a1b 100644 --- a/src/xrpld/app/tx/detail/NFTokenCancelOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenCancelOffer.cpp @@ -19,8 +19,8 @@ #include #include -#include +#include #include #include diff --git a/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp b/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp index 8e1a026415..f9cc8c1fc8 100644 --- a/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp @@ -19,8 +19,8 @@ #include #include -#include +#include #include #include diff --git a/src/xrpld/app/tx/detail/NFTokenMint.cpp b/src/xrpld/app/tx/detail/NFTokenMint.cpp index 42b551b3a4..4c07a6e499 100644 --- a/src/xrpld/app/tx/detail/NFTokenMint.cpp +++ b/src/xrpld/app/tx/detail/NFTokenMint.cpp @@ -18,9 +18,9 @@ //============================================================================== #include -#include #include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/NFTokenUtils.cpp b/src/xrpld/app/tx/detail/NFTokenUtils.cpp index 4866a3b385..ad3e6f4d35 100644 --- a/src/xrpld/app/tx/detail/NFTokenUtils.cpp +++ b/src/xrpld/app/tx/detail/NFTokenUtils.cpp @@ -18,10 +18,10 @@ //============================================================================== #include -#include -#include #include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/NFTokenUtils.h b/src/xrpld/app/tx/detail/NFTokenUtils.h index 7ee0541984..6d33d4ec1a 100644 --- a/src/xrpld/app/tx/detail/NFTokenUtils.h +++ b/src/xrpld/app/tx/detail/NFTokenUtils.h @@ -21,9 +21,9 @@ #define RIPPLE_TX_IMPL_DETAILS_NFTOKENUTILS_H_INCLUDED #include -#include #include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/Offer.h b/src/xrpld/app/tx/detail/Offer.h index d6ff4c7699..c214bea23f 100644 --- a/src/xrpld/app/tx/detail/Offer.h +++ b/src/xrpld/app/tx/detail/Offer.h @@ -20,10 +20,9 @@ #ifndef RIPPLE_APP_BOOK_OFFER_H_INCLUDED #define RIPPLE_APP_BOOK_OFFER_H_INCLUDED -#include - #include #include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/OfferStream.cpp b/src/xrpld/app/tx/detail/OfferStream.cpp index 55993f5c5f..8e1215f5c8 100644 --- a/src/xrpld/app/tx/detail/OfferStream.cpp +++ b/src/xrpld/app/tx/detail/OfferStream.cpp @@ -19,9 +19,9 @@ #include #include -#include #include +#include #include #include diff --git a/src/xrpld/app/tx/detail/OfferStream.h b/src/xrpld/app/tx/detail/OfferStream.h index cf6c1c3d2d..6470f876a6 100644 --- a/src/xrpld/app/tx/detail/OfferStream.h +++ b/src/xrpld/app/tx/detail/OfferStream.h @@ -22,11 +22,11 @@ #include #include -#include #include #include #include +#include #include diff --git a/src/xrpld/app/tx/detail/PayChan.cpp b/src/xrpld/app/tx/detail/PayChan.cpp index 12a9d0cb75..bdfe0d5c95 100644 --- a/src/xrpld/app/tx/detail/PayChan.cpp +++ b/src/xrpld/app/tx/detail/PayChan.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include #include -#include -#include #include #include +#include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/Payment.cpp b/src/xrpld/app/tx/detail/Payment.cpp index 784330b203..e622d54498 100644 --- a/src/xrpld/app/tx/detail/Payment.cpp +++ b/src/xrpld/app/tx/detail/Payment.cpp @@ -17,14 +17,14 @@ */ //============================================================================== -#include #include #include #include #include -#include #include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/PermissionedDomainDelete.cpp b/src/xrpld/app/tx/detail/PermissionedDomainDelete.cpp index 64c498b68b..76224ba6b3 100644 --- a/src/xrpld/app/tx/detail/PermissionedDomainDelete.cpp +++ b/src/xrpld/app/tx/detail/PermissionedDomainDelete.cpp @@ -18,8 +18,8 @@ //============================================================================== #include -#include +#include #include namespace ripple { diff --git a/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp b/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp index 6e2df2a082..cc25809aa1 100644 --- a/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp +++ b/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include #include -#include +#include +#include #include #include diff --git a/src/xrpld/app/tx/detail/SetAccount.cpp b/src/xrpld/app/tx/detail/SetAccount.cpp index ec618981c1..dc84c7cc7e 100644 --- a/src/xrpld/app/tx/detail/SetAccount.cpp +++ b/src/xrpld/app/tx/detail/SetAccount.cpp @@ -20,9 +20,9 @@ #include #include #include -#include #include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/SetOracle.cpp b/src/xrpld/app/tx/detail/SetOracle.cpp index ba1d4a2e47..78ff8e2953 100644 --- a/src/xrpld/app/tx/detail/SetOracle.cpp +++ b/src/xrpld/app/tx/detail/SetOracle.cpp @@ -18,9 +18,9 @@ //============================================================================== #include -#include -#include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/SetSignerList.cpp b/src/xrpld/app/tx/detail/SetSignerList.cpp index b52130e2fa..60f92cf87b 100644 --- a/src/xrpld/app/tx/detail/SetSignerList.cpp +++ b/src/xrpld/app/tx/detail/SetSignerList.cpp @@ -19,9 +19,9 @@ #include #include -#include #include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index d3b39aaf11..87f1721b29 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -19,9 +19,9 @@ #include #include -#include #include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/Taker.h b/src/xrpld/app/tx/detail/Taker.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 0db0484842..8f881d7252 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -18,7 +18,6 @@ //============================================================================== #include -#include #include #include #include @@ -26,11 +25,12 @@ #include #include #include -#include #include #include #include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/VaultClawback.cpp b/src/xrpld/app/tx/detail/VaultClawback.cpp index 87740da179..061aacdbb8 100644 --- a/src/xrpld/app/tx/detail/VaultClawback.cpp +++ b/src/xrpld/app/tx/detail/VaultClawback.cpp @@ -18,9 +18,9 @@ //============================================================================== #include -#include #include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/VaultCreate.cpp b/src/xrpld/app/tx/detail/VaultCreate.cpp index 0b5cdd4fc0..855275bf4e 100644 --- a/src/xrpld/app/tx/detail/VaultCreate.cpp +++ b/src/xrpld/app/tx/detail/VaultCreate.cpp @@ -20,8 +20,8 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/VaultDelete.cpp b/src/xrpld/app/tx/detail/VaultDelete.cpp index d4b74ae1d5..5e4e16a99b 100644 --- a/src/xrpld/app/tx/detail/VaultDelete.cpp +++ b/src/xrpld/app/tx/detail/VaultDelete.cpp @@ -18,8 +18,8 @@ //============================================================================== #include -#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/VaultDeposit.cpp b/src/xrpld/app/tx/detail/VaultDeposit.cpp index 5cdcb43e20..3d346d63a2 100644 --- a/src/xrpld/app/tx/detail/VaultDeposit.cpp +++ b/src/xrpld/app/tx/detail/VaultDeposit.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include #include #include -#include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/VaultSet.cpp b/src/xrpld/app/tx/detail/VaultSet.cpp index 4750f89be2..5a519f81cf 100644 --- a/src/xrpld/app/tx/detail/VaultSet.cpp +++ b/src/xrpld/app/tx/detail/VaultSet.cpp @@ -18,8 +18,8 @@ //============================================================================== #include -#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/VaultWithdraw.cpp b/src/xrpld/app/tx/detail/VaultWithdraw.cpp index 0ceaabbfde..63cc22fe48 100644 --- a/src/xrpld/app/tx/detail/VaultWithdraw.cpp +++ b/src/xrpld/app/tx/detail/VaultWithdraw.cpp @@ -17,10 +17,10 @@ */ //============================================================================== -#include #include -#include +#include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/XChainBridge.cpp b/src/xrpld/app/tx/detail/XChainBridge.cpp index 6ca049ee66..92e3c7f625 100644 --- a/src/xrpld/app/tx/detail/XChainBridge.cpp +++ b/src/xrpld/app/tx/detail/XChainBridge.cpp @@ -21,15 +21,15 @@ #include #include #include -#include -#include -#include #include #include #include #include #include +#include +#include +#include #include #include #include diff --git a/src/xrpld/rpc/detail/RPCHelpers.cpp b/src/xrpld/rpc/detail/RPCHelpers.cpp index 52a69eb79e..4b28d44253 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCHelpers.cpp @@ -24,11 +24,11 @@ #include #include #include -#include #include #include #include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/AMMInfo.cpp b/src/xrpld/rpc/handlers/AMMInfo.cpp index b312264f90..7c1dd3ba58 100644 --- a/src/xrpld/rpc/handlers/AMMInfo.cpp +++ b/src/xrpld/rpc/handlers/AMMInfo.cpp @@ -19,11 +19,11 @@ #include #include -#include #include #include #include +#include #include #include diff --git a/src/xrpld/rpc/handlers/AccountChannels.cpp b/src/xrpld/rpc/handlers/AccountChannels.cpp index 5d810d61a0..1b0046ab64 100644 --- a/src/xrpld/rpc/handlers/AccountChannels.cpp +++ b/src/xrpld/rpc/handlers/AccountChannels.cpp @@ -17,12 +17,12 @@ */ //============================================================================== -#include -#include #include #include #include +#include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/AccountCurrenciesHandler.cpp b/src/xrpld/rpc/handlers/AccountCurrenciesHandler.cpp index 2d08561ec9..59a13f5893 100644 --- a/src/xrpld/rpc/handlers/AccountCurrenciesHandler.cpp +++ b/src/xrpld/rpc/handlers/AccountCurrenciesHandler.cpp @@ -18,10 +18,10 @@ //============================================================================== #include -#include #include #include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/AccountInfo.cpp b/src/xrpld/rpc/handlers/AccountInfo.cpp index 3432021690..0a36993d65 100644 --- a/src/xrpld/rpc/handlers/AccountInfo.cpp +++ b/src/xrpld/rpc/handlers/AccountInfo.cpp @@ -19,12 +19,12 @@ #include #include -#include #include #include #include #include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/AccountLines.cpp b/src/xrpld/rpc/handlers/AccountLines.cpp index e921eee386..893ca9a190 100644 --- a/src/xrpld/rpc/handlers/AccountLines.cpp +++ b/src/xrpld/rpc/handlers/AccountLines.cpp @@ -18,11 +18,11 @@ //============================================================================== #include -#include #include #include #include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/AccountObjects.cpp b/src/xrpld/rpc/handlers/AccountObjects.cpp index 2b2496a1dd..acd99c205e 100644 --- a/src/xrpld/rpc/handlers/AccountObjects.cpp +++ b/src/xrpld/rpc/handlers/AccountObjects.cpp @@ -18,11 +18,11 @@ //============================================================================== #include -#include #include #include #include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/AccountOffers.cpp b/src/xrpld/rpc/handlers/AccountOffers.cpp index bc575d2d86..e65b39b35b 100644 --- a/src/xrpld/rpc/handlers/AccountOffers.cpp +++ b/src/xrpld/rpc/handlers/AccountOffers.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include #include #include #include #include +#include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/AccountTx.cpp b/src/xrpld/rpc/handlers/AccountTx.cpp index d5df40303b..6b1dccdba9 100644 --- a/src/xrpld/rpc/handlers/AccountTx.cpp +++ b/src/xrpld/rpc/handlers/AccountTx.cpp @@ -22,13 +22,13 @@ #include #include #include -#include #include #include #include #include #include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/BookOffers.cpp b/src/xrpld/rpc/handlers/BookOffers.cpp index df4712209c..6506474163 100644 --- a/src/xrpld/rpc/handlers/BookOffers.cpp +++ b/src/xrpld/rpc/handlers/BookOffers.cpp @@ -19,12 +19,12 @@ #include #include -#include #include #include #include #include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/DepositAuthorized.cpp b/src/xrpld/rpc/handlers/DepositAuthorized.cpp index 1bb480544d..c184a7f845 100644 --- a/src/xrpld/rpc/handlers/DepositAuthorized.cpp +++ b/src/xrpld/rpc/handlers/DepositAuthorized.cpp @@ -17,11 +17,11 @@ */ //============================================================================== -#include -#include #include #include +#include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/GatewayBalances.cpp b/src/xrpld/rpc/handlers/GatewayBalances.cpp index ca9e370c81..a4542f682f 100644 --- a/src/xrpld/rpc/handlers/GatewayBalances.cpp +++ b/src/xrpld/rpc/handlers/GatewayBalances.cpp @@ -19,10 +19,10 @@ #include #include -#include #include #include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/GetAggregatePrice.cpp b/src/xrpld/rpc/handlers/GetAggregatePrice.cpp index 33a88ba78f..2227143d70 100644 --- a/src/xrpld/rpc/handlers/GetAggregatePrice.cpp +++ b/src/xrpld/rpc/handlers/GetAggregatePrice.cpp @@ -19,11 +19,11 @@ #include #include -#include #include #include #include +#include #include #include diff --git a/src/xrpld/rpc/handlers/LedgerData.cpp b/src/xrpld/rpc/handlers/LedgerData.cpp index 7bd50cc1e5..e6c579a4b5 100644 --- a/src/xrpld/rpc/handlers/LedgerData.cpp +++ b/src/xrpld/rpc/handlers/LedgerData.cpp @@ -18,13 +18,13 @@ //============================================================================== #include -#include #include #include #include #include #include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/LedgerEntry.cpp b/src/xrpld/rpc/handlers/LedgerEntry.cpp index 61a7e2fb2c..cead16c04d 100644 --- a/src/xrpld/rpc/handlers/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/LedgerEntry.cpp @@ -17,8 +17,6 @@ */ //============================================================================== -#include -#include #include #include #include @@ -28,6 +26,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/LedgerHandler.h b/src/xrpld/rpc/handlers/LedgerHandler.h index a573589cbc..4cbf984505 100644 --- a/src/xrpld/rpc/handlers/LedgerHandler.h +++ b/src/xrpld/rpc/handlers/LedgerHandler.h @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -31,6 +30,7 @@ #include #include +#include #include namespace Json { diff --git a/src/xrpld/rpc/handlers/LedgerHeader.cpp b/src/xrpld/rpc/handlers/LedgerHeader.cpp index 1815c8db56..a407dc4318 100644 --- a/src/xrpld/rpc/handlers/LedgerHeader.cpp +++ b/src/xrpld/rpc/handlers/LedgerHeader.cpp @@ -18,10 +18,10 @@ //============================================================================== #include -#include #include #include +#include #include namespace ripple { diff --git a/src/xrpld/rpc/handlers/NFTOffers.cpp b/src/xrpld/rpc/handlers/NFTOffers.cpp index 52a5c69ab0..598abd2c3f 100644 --- a/src/xrpld/rpc/handlers/NFTOffers.cpp +++ b/src/xrpld/rpc/handlers/NFTOffers.cpp @@ -17,13 +17,13 @@ */ //============================================================================== -#include -#include #include #include #include #include +#include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/NoRippleCheck.cpp b/src/xrpld/rpc/handlers/NoRippleCheck.cpp index a6007e9eab..abe51aa206 100644 --- a/src/xrpld/rpc/handlers/NoRippleCheck.cpp +++ b/src/xrpld/rpc/handlers/NoRippleCheck.cpp @@ -20,11 +20,11 @@ #include #include #include -#include #include #include #include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/PayChanClaim.cpp b/src/xrpld/rpc/handlers/PayChanClaim.cpp index b62f5e54e5..6945d2a051 100644 --- a/src/xrpld/rpc/handlers/PayChanClaim.cpp +++ b/src/xrpld/rpc/handlers/PayChanClaim.cpp @@ -18,11 +18,11 @@ //============================================================================== #include -#include #include #include #include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/Subscribe.cpp b/src/xrpld/rpc/handlers/Subscribe.cpp index 1696754e9c..be08769875 100644 --- a/src/xrpld/rpc/handlers/Subscribe.cpp +++ b/src/xrpld/rpc/handlers/Subscribe.cpp @@ -20,13 +20,13 @@ #include #include #include -#include #include #include #include #include #include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/TransactionEntry.cpp b/src/xrpld/rpc/handlers/TransactionEntry.cpp index c94e95063b..02ff329354 100644 --- a/src/xrpld/rpc/handlers/TransactionEntry.cpp +++ b/src/xrpld/rpc/handlers/TransactionEntry.cpp @@ -19,10 +19,10 @@ #include #include -#include #include #include +#include #include namespace ripple { From 3cbdf818a73efc039b1143cd099e735e12ae140d Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Thu, 18 Sep 2025 13:55:49 -0400 Subject: [PATCH 24/24] Miscellaneous refactors and updates (#5590) - Added a new Invariant: `ValidPseudoAccounts` which checks that all pseudo-accounts behave consistently through creation and updates, and that no "real" accounts look like pseudo-accounts (which means they don't have a 0 sequence). - `to_short_string(base_uint)`. Like `to_string`, but only returns the first 8 characters. (Similar to how a git commit ID can be abbreviated.) Used as a wrapped sink to prefix most transaction-related messages. More can be added later. - `XRPL_ASSERT_PARTS`. Convenience wrapper for `XRPL_ASSERT`, which takes the `function` and `description` as separate parameters. - `SField::sMD_PseudoAccount`. Metadata option for `SField` definitions to indicate that the field, if set in an `AccountRoot` indicates that account is a pseudo-account. Removes the need for hard-coded field lists all over the place. Added the flag to `AMMID` and `VaultID`. - Added functionality to `SField` ctor to detect both code and name collisions using asserts. And require all SFields to have a name - Convenience type aliases `STLedgerEntry::const_pointer` and `STLedgerEntry::const_ref`. (`SLE` is an alias to `STLedgerEntry`.) - Generalized `feeunit.h` (`TaggedFee`) into `unit.h` (`ValueUnit`) and added new "BIPS"-related tags for future use. Also refactored the type restrictions to use Concepts. - Restructured `transactions.macro` to do two big things 1. Include the `#include` directives for transactor header files directly in the macro file. Removes the need to update `applySteps.cpp` and the resulting conflicts. 2. Added a `privileges` parameter to the `TRANSACTION` macro, which specifies some of the operations a transaction is allowed to do. These `privileges` are enforced by invariant checks. Again, removed the need to update scattered lists of transaction types in various checks. - Unit tests: 1. Moved more helper functions into `TestHelpers.h` and `.cpp`. 2. Cleaned up the namespaces to prevent / mitigate random collisions and ambiguous symbols, particularly in unity builds. 3. Generalized `Env::balance` to add support for `MPTIssue` and `Asset`. 4. Added a set of helper classes to simplify `Env` transaction parameter classes: `JTxField`, `JTxFieldWrapper`, and a bunch of classes derived or aliased from it. For an example of how awesome it is, check the changes `src/test/jtx/escrow.h` for how much simpler the definitions are for `finish_time`, `cancel_time`, `condition`, and `fulfillment`. 5. Generalized several of the amount-related helper classes to understand `Asset`s. 6. `env.balance` for an MPT issuer will return a negative number (or 0) for consistency with IOUs. --- include/xrpl/basics/base_uint.h | 10 + include/xrpl/basics/safe_cast.h | 7 +- include/xrpl/beast/utility/instrumentation.h | 5 + include/xrpl/ledger/View.h | 16 + include/xrpl/protocol/FeeUnits.h | 565 ------------------ include/xrpl/protocol/LedgerFormats.h | 2 +- include/xrpl/protocol/Protocol.h | 1 - include/xrpl/protocol/SField.h | 14 +- include/xrpl/protocol/STLedgerEntry.h | 9 +- include/xrpl/protocol/STObject.h | 2 +- include/xrpl/protocol/STValidation.h | 2 +- include/xrpl/protocol/TxFlags.h | 5 +- include/xrpl/protocol/Units.h | 555 +++++++++++++++++ include/xrpl/protocol/XRPAmount.h | 4 +- include/xrpl/protocol/detail/sfields.macro | 6 +- .../xrpl/protocol/detail/transactions.macro | 237 +++++++- include/xrpl/protocol/jss.h | 6 +- src/libxrpl/ledger/View.cpp | 109 +++- src/libxrpl/protocol/SField.cpp | 35 +- src/libxrpl/protocol/TxFormats.cpp | 3 +- src/test/app/AMMCalc_test.cpp | 2 +- src/test/app/AMMExtended_test.cpp | 194 +++--- src/test/app/AMM_test.cpp | 199 +++--- src/test/app/Credentials_test.cpp | 9 - src/test/app/DID_test.cpp | 8 - src/test/app/EscrowToken_test.cpp | 16 +- src/test/app/Invariants_test.cpp | 132 +++- src/test/app/LPTokenTransfer_test.cpp | 16 +- src/test/app/ValidatorSite_test.cpp | 2 +- src/test/app/Vault_test.cpp | 40 +- src/test/basics/FileUtilities_test.cpp | 2 +- .../{FeeUnits_test.cpp => Units_test.cpp} | 32 +- src/test/basics/base_uint_test.cpp | 5 + src/test/core/Config_test.cpp | 6 +- src/test/csf/Digraph.h | 6 +- src/test/jtx/Env.h | 3 + src/test/jtx/TestHelpers.h | 277 ++++++++- src/test/jtx/amount.h | 84 ++- src/test/jtx/balance.h | 8 +- src/test/jtx/escrow.h | 81 +-- src/test/jtx/fee.h | 5 + src/test/jtx/flags.h | 32 +- src/test/jtx/impl/Env.cpp | 12 +- src/test/jtx/impl/TestHelpers.cpp | 30 +- src/test/jtx/impl/amount.cpp | 8 +- src/test/jtx/impl/balance.cpp | 55 +- src/test/jtx/impl/fee.cpp | 9 +- src/test/jtx/impl/owners.cpp | 8 +- src/test/jtx/owners.h | 7 +- src/test/jtx/require.h | 7 +- src/test/jtx/tags.h | 10 + src/test/nodestore/import_test.cpp | 3 +- src/test/unit_test/FileDirGuard.h | 2 - src/test/unit_test/multi_runner.cpp | 6 +- src/test/unit_test/multi_runner.h | 3 +- src/xrpld/app/main/Application.cpp | 4 +- src/xrpld/app/misc/detail/LoadFeeTrack.cpp | 3 +- src/xrpld/app/tx/detail/DeleteAccount.cpp | 2 +- src/xrpld/app/tx/detail/Escrow.cpp | 4 +- src/xrpld/app/tx/detail/InvariantCheck.cpp | 328 +++++++--- src/xrpld/app/tx/detail/InvariantCheck.h | 31 +- src/xrpld/app/tx/detail/Transactor.cpp | 5 +- src/xrpld/app/tx/detail/Transactor.h | 2 + src/xrpld/app/tx/detail/applySteps.cpp | 65 +- src/xrpld/rpc/detail/RPCHelpers.cpp | 2 +- 65 files changed, 2148 insertions(+), 1210 deletions(-) delete mode 100644 include/xrpl/protocol/FeeUnits.h create mode 100644 include/xrpl/protocol/Units.h rename src/test/basics/{FeeUnits_test.cpp => Units_test.cpp} (92%) diff --git a/include/xrpl/basics/base_uint.h b/include/xrpl/basics/base_uint.h index d36bf74c54..b1a4622cc4 100644 --- a/include/xrpl/basics/base_uint.h +++ b/include/xrpl/basics/base_uint.h @@ -632,6 +632,16 @@ to_string(base_uint const& a) return strHex(a.cbegin(), a.cend()); } +template +inline std::string +to_short_string(base_uint const& a) +{ + static_assert( + base_uint::bytes > 4, + "For 4 bytes or less, use a native type"); + return strHex(a.cbegin(), a.cbegin() + 4) + "..."; +} + template inline std::ostream& operator<<(std::ostream& out, base_uint const& u) diff --git a/include/xrpl/basics/safe_cast.h b/include/xrpl/basics/safe_cast.h index f193f1793f..a750a3b6fc 100644 --- a/include/xrpl/basics/safe_cast.h +++ b/include/xrpl/basics/safe_cast.h @@ -28,9 +28,8 @@ namespace ripple { // the destination can hold all values of the source. This is particularly // handy when the source or destination is an enumeration type. -template -static constexpr bool is_safetocasttovalue_v = - (std::is_integral_v && std::is_integral_v) && +template +concept SafeToCast = (std::is_integral_v && std::is_integral_v) && (std::is_signed::value || std::is_unsigned::value) && (std::is_signed::value != std::is_signed::value ? sizeof(Dest) > sizeof(Src) @@ -78,7 +77,7 @@ inline constexpr std:: unsafe_cast(Src s) noexcept { static_assert( - !is_safetocasttovalue_v, + !SafeToCast, "Only unsafe if casting signed to unsigned or " "destination is too small"); return static_cast(s); diff --git a/include/xrpl/beast/utility/instrumentation.h b/include/xrpl/beast/utility/instrumentation.h index 72c48959a0..3594855eef 100644 --- a/include/xrpl/beast/utility/instrumentation.h +++ b/include/xrpl/beast/utility/instrumentation.h @@ -39,11 +39,16 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #endif #define XRPL_ASSERT ALWAYS_OR_UNREACHABLE +#define XRPL_ASSERT_PARTS(cond, function, description, ...) \ + XRPL_ASSERT(cond, function " : " description) // How to use the instrumentation macros: // // * XRPL_ASSERT if cond must be true but the line might not be reached during // fuzzing. Same like `assert` in normal use. +// * XRPL_ASSERT_PARTS is for convenience, and works like XRPL_ASSERT, but +// splits the message param into "function" and "description", then joins +// them with " : " before passing to XRPL_ASSERT. // * ALWAYS if cond must be true _and_ the line must be reached during fuzzing. // Same like `assert` in normal use. // * REACHABLE if the line must be reached during fuzzing diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index 3d67e25a22..9698b4fda3 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -561,12 +561,28 @@ createPseudoAccount( [[nodiscard]] bool isPseudoAccount(std::shared_ptr sleAcct); +// Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account if +// set +// Pseudo-account designator fields MUST be maintained by including the +// SField::sMD_PseudoAccount flag in the SField definition. (Don't forget to +// "| SField::sMD_Default"!) The fields do NOT need to be amendment-gated, +// since a non-active amendment will not set any field, by definition. +// Specific properties of a pseudo-account are NOT checked here, that's what +// InvariantCheck is for. +[[nodiscard]] std::vector const& +getPseudoAccountFields(); + [[nodiscard]] inline bool isPseudoAccount(ReadView const& view, AccountID accountId) { return isPseudoAccount(view.read(keylet::account(accountId))); } +[[nodiscard]] TER +canAddHolding(ReadView const& view, Asset const& asset); + +/// Any transactors that call addEmptyHolding() in doApply must call +/// canAddHolding() in preflight with the same View and Asset [[nodiscard]] TER addEmptyHolding( ApplyView& view, diff --git a/include/xrpl/protocol/FeeUnits.h b/include/xrpl/protocol/FeeUnits.h deleted file mode 100644 index 31a1886b7f..0000000000 --- a/include/xrpl/protocol/FeeUnits.h +++ /dev/null @@ -1,565 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2019 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#ifndef BASICS_FEES_H_INCLUDED -#define BASICS_FEES_H_INCLUDED - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -namespace ripple { - -namespace feeunit { - -/** "drops" are the smallest divisible amount of XRP. This is what most - of the code uses. */ -struct dropTag; -/** "fee units" calculations are a not-really-unitless value that is used - to express the cost of a given transaction vs. a reference transaction. - They are primarily used by the Transactor classes. */ -struct feeunitTag; -/** "fee levels" are used by the transaction queue to compare the relative - cost of transactions that require different levels of effort to process. - See also: src/ripple/app/misc/FeeEscalation.md#fee-level */ -struct feelevelTag; -/** unitless values are plain scalars wrapped in a TaggedFee. They are - used for calculations in this header. */ -struct unitlessTag; - -template -using enable_if_unit_t = typename std::enable_if_t< - std::is_class_v && std::is_object_v && - std::is_object_v>; - -/** `is_usable_unit_v` is checked to ensure that only values with - known valid type tags can be used (sometimes transparently) in - non-fee contexts. At the time of implementation, this includes - all known tags, but more may be added in the future, and they - should not be added automatically unless determined to be - appropriate. -*/ -template > -constexpr bool is_usable_unit_v = - std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v; - -template -class TaggedFee : private boost::totally_ordered>, - private boost::additive>, - private boost::equality_comparable, T>, - private boost::dividable, T>, - private boost::modable, T>, - private boost::unit_steppable> -{ -public: - using unit_type = UnitTag; - using value_type = T; - -private: - value_type fee_; - -protected: - template - static constexpr bool is_compatible_v = - std::is_arithmetic_v && std::is_arithmetic_v && - std::is_convertible_v; - - template > - static constexpr bool is_compatiblefee_v = - is_compatible_v && - std::is_same_v; - - template - using enable_if_compatible_t = - typename std::enable_if_t>; - - template - using enable_if_compatiblefee_t = - typename std::enable_if_t>; - -public: - TaggedFee() = default; - constexpr TaggedFee(TaggedFee const& other) = default; - constexpr TaggedFee& - operator=(TaggedFee const& other) = default; - - constexpr explicit TaggedFee(beast::Zero) : fee_(0) - { - } - - constexpr TaggedFee& - operator=(beast::Zero) - { - fee_ = 0; - return *this; - } - - constexpr explicit TaggedFee(value_type fee) : fee_(fee) - { - } - - TaggedFee& - operator=(value_type fee) - { - fee_ = fee; - return *this; - } - - /** Instances with the same unit, and a type that is - "safe" to convert to this one can be converted - implicitly */ - template < - class Other, - class = std::enable_if_t< - is_compatible_v && - is_safetocasttovalue_v>> - constexpr TaggedFee(TaggedFee const& fee) - : TaggedFee(safe_cast(fee.fee())) - { - } - - constexpr TaggedFee - operator*(value_type const& rhs) const - { - return TaggedFee{fee_ * rhs}; - } - - friend constexpr TaggedFee - operator*(value_type lhs, TaggedFee const& rhs) - { - // multiplication is commutative - return rhs * lhs; - } - - constexpr value_type - operator/(TaggedFee const& rhs) const - { - return fee_ / rhs.fee_; - } - - TaggedFee& - operator+=(TaggedFee const& other) - { - fee_ += other.fee(); - return *this; - } - - TaggedFee& - operator-=(TaggedFee const& other) - { - fee_ -= other.fee(); - return *this; - } - - TaggedFee& - operator++() - { - ++fee_; - return *this; - } - - TaggedFee& - operator--() - { - --fee_; - return *this; - } - - TaggedFee& - operator*=(value_type const& rhs) - { - fee_ *= rhs; - return *this; - } - - TaggedFee& - operator/=(value_type const& rhs) - { - fee_ /= rhs; - return *this; - } - - template - std::enable_if_t, TaggedFee&> - operator%=(value_type const& rhs) - { - fee_ %= rhs; - return *this; - } - - TaggedFee - operator-() const - { - static_assert( - std::is_signed_v, "- operator illegal on unsigned fee types"); - return TaggedFee{-fee_}; - } - - bool - operator==(TaggedFee const& other) const - { - return fee_ == other.fee_; - } - - template > - bool - operator==(TaggedFee const& other) const - { - return fee_ == other.fee(); - } - - bool - operator==(value_type other) const - { - return fee_ == other; - } - - template > - bool - operator!=(TaggedFee const& other) const - { - return !operator==(other); - } - - bool - operator<(TaggedFee const& other) const - { - return fee_ < other.fee_; - } - - /** Returns true if the amount is not zero */ - explicit constexpr - operator bool() const noexcept - { - return fee_ != 0; - } - - /** Return the sign of the amount */ - constexpr int - signum() const noexcept - { - return (fee_ < 0) ? -1 : (fee_ ? 1 : 0); - } - - /** Returns the number of drops */ - constexpr value_type - fee() const - { - return fee_; - } - - template - constexpr double - decimalFromReference(TaggedFee reference) const - { - return static_cast(fee_) / reference.fee(); - } - - // `is_usable_unit_v` is checked to ensure that only values with - // known valid type tags can be converted to JSON. At the time - // of implementation, that includes all known tags, but more may - // be added in the future. - std::enable_if_t, Json::Value> - jsonClipped() const - { - if constexpr (std::is_integral_v) - { - using jsontype = std::conditional_t< - std::is_signed_v, - Json::Int, - Json::UInt>; - - constexpr auto min = std::numeric_limits::min(); - constexpr auto max = std::numeric_limits::max(); - - if (fee_ < min) - return min; - if (fee_ > max) - return max; - return static_cast(fee_); - } - else - { - return fee_; - } - } - - /** Returns the underlying value. Code SHOULD NOT call this - function unless the type has been abstracted away, - e.g. in a templated function. - */ - constexpr value_type - value() const - { - return fee_; - } - - friend std::istream& - operator>>(std::istream& s, TaggedFee& val) - { - s >> val.fee_; - return s; - } -}; - -// Output Fees as just their numeric value. -template -std::basic_ostream& -operator<<(std::basic_ostream& os, TaggedFee const& q) -{ - return os << q.value(); -} - -template -std::string -to_string(TaggedFee const& amount) -{ - return std::to_string(amount.fee()); -} - -template > -constexpr bool can_muldiv_source_v = - std::is_convertible_v; - -template > -constexpr bool can_muldiv_dest_v = - can_muldiv_source_v && // Dest is also a source - std::is_convertible_v && - sizeof(typename Dest::value_type) >= sizeof(std::uint64_t); - -template < - class Source1, - class Source2, - class = enable_if_unit_t, - class = enable_if_unit_t> -constexpr bool can_muldiv_sources_v = - can_muldiv_source_v && can_muldiv_source_v && - std::is_same_v; - -template < - class Source1, - class Source2, - class Dest, - class = enable_if_unit_t, - class = enable_if_unit_t, - class = enable_if_unit_t> -constexpr bool can_muldiv_v = - can_muldiv_sources_v && can_muldiv_dest_v; -// Source and Dest can be the same by default - -template < - class Source1, - class Source2, - class Dest, - class = enable_if_unit_t, - class = enable_if_unit_t, - class = enable_if_unit_t> -constexpr bool can_muldiv_commute_v = can_muldiv_v && - !std::is_same_v; - -template -using enable_muldiv_source_t = - typename std::enable_if_t>; - -template -using enable_muldiv_dest_t = typename std::enable_if_t>; - -template -using enable_muldiv_sources_t = - typename std::enable_if_t>; - -template -using enable_muldiv_t = - typename std::enable_if_t>; - -template -using enable_muldiv_commute_t = - typename std::enable_if_t>; - -template -TaggedFee -scalar(T value) -{ - return TaggedFee{value}; -} - -template < - class Source1, - class Source2, - class Dest, - class = enable_muldiv_t> -std::optional -mulDivU(Source1 value, Dest mul, Source2 div) -{ - // Fees can never be negative in any context. - if (value.value() < 0 || mul.value() < 0 || div.value() < 0) - { - // split the asserts so if one hits, the user can tell which - // without a debugger. - XRPL_ASSERT( - value.value() >= 0, - "ripple::feeunit::mulDivU : minimum value input"); - XRPL_ASSERT( - mul.value() >= 0, "ripple::feeunit::mulDivU : minimum mul input"); - XRPL_ASSERT( - div.value() >= 0, "ripple::feeunit::mulDivU : minimum div input"); - return std::nullopt; - } - - using desttype = typename Dest::value_type; - constexpr auto max = std::numeric_limits::max(); - - // Shortcuts, since these happen a lot in the real world - if (value == div) - return mul; - if (mul.value() == div.value()) - { - if (value.value() > max) - return std::nullopt; - return Dest{static_cast(value.value())}; - } - - using namespace boost::multiprecision; - - uint128_t product; - product = multiply( - product, - static_cast(value.value()), - static_cast(mul.value())); - - auto quotient = product / div.value(); - - if (quotient > max) - return std::nullopt; - - return Dest{static_cast(quotient)}; -} - -} // namespace feeunit - -template -using FeeLevel = feeunit::TaggedFee; -using FeeLevel64 = FeeLevel; -using FeeLevelDouble = FeeLevel; - -template < - class Source1, - class Source2, - class Dest, - class = feeunit::enable_muldiv_t> -std::optional -mulDiv(Source1 value, Dest mul, Source2 div) -{ - return feeunit::mulDivU(value, mul, div); -} - -template < - class Source1, - class Source2, - class Dest, - class = feeunit::enable_muldiv_commute_t> -std::optional -mulDiv(Dest value, Source1 mul, Source2 div) -{ - // Multiplication is commutative - return feeunit::mulDivU(mul, value, div); -} - -template > -std::optional -mulDiv(std::uint64_t value, Dest mul, std::uint64_t div) -{ - // Give the scalars a non-tag so the - // unit-handling version gets called. - return feeunit::mulDivU(feeunit::scalar(value), mul, feeunit::scalar(div)); -} - -template > -std::optional -mulDiv(Dest value, std::uint64_t mul, std::uint64_t div) -{ - // Multiplication is commutative - return mulDiv(mul, value, div); -} - -template < - class Source1, - class Source2, - class = feeunit::enable_muldiv_sources_t> -std::optional -mulDiv(Source1 value, std::uint64_t mul, Source2 div) -{ - // Give the scalars a dimensionless unit so the - // unit-handling version gets called. - auto unitresult = feeunit::mulDivU(value, feeunit::scalar(mul), div); - - if (!unitresult) - return std::nullopt; - - return unitresult->value(); -} - -template < - class Source1, - class Source2, - class = feeunit::enable_muldiv_sources_t> -std::optional -mulDiv(std::uint64_t value, Source1 mul, Source2 div) -{ - // Multiplication is commutative - return mulDiv(mul, value, div); -} - -template -constexpr std::enable_if_t< - std::is_same_v && - std::is_integral_v && - std::is_integral_v, - Dest> -safe_cast(Src s) noexcept -{ - // Dest may not have an explicit value constructor - return Dest{safe_cast(s.value())}; -} - -template -constexpr std::enable_if_t< - std::is_same_v && - std::is_integral_v && - std::is_integral_v, - Dest> -unsafe_cast(Src s) noexcept -{ - // Dest may not have an explicit value constructor - return Dest{unsafe_cast(s.value())}; -} - -} // namespace ripple - -#endif // BASICS_FEES_H_INCLUDED diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index 7cf92d0822..40c9fce1bb 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -56,7 +56,7 @@ enum LedgerEntryType : std::uint16_t #pragma push_macro("LEDGER_ENTRY") #undef LEDGER_ENTRY -#define LEDGER_ENTRY(tag, value, name, rpcName, fields) tag = value, +#define LEDGER_ENTRY(tag, value, ...) tag = value, #include diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index a0fcfee34c..84b8c3889b 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -22,7 +22,6 @@ #include #include -#include #include diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index 777cfa02ba..2f85cf3b7c 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -148,8 +149,10 @@ public: sMD_ChangeNew = 0x02, // new value when it changes sMD_DeleteFinal = 0x04, // final value when it is deleted sMD_Create = 0x08, // value when it's created - sMD_Always = 0x10, // value when node containing it is affected at all - sMD_BaseTen = 0x20, + sMD_Always = 0x10, // value when node containing it is affected at all + sMD_BaseTen = 0x20, // value is treated as base 10, overriding behavior + sMD_PseudoAccount = 0x40, // if this field is set in an ACCOUNT_ROOT + // _only_, then it is a pseudo-account sMD_Default = sMD_ChangeOrig | sMD_ChangeNew | sMD_DeleteFinal | sMD_Create }; @@ -184,7 +187,7 @@ public: char const* fn, int meta = sMD_Default, IsSigning signing = IsSigning::yes); - explicit SField(private_access_tag_t, int fc); + explicit SField(private_access_tag_t, int fc, char const* fn); static SField const& getField(int fieldCode); @@ -297,7 +300,7 @@ public: static int compare(SField const& f1, SField const& f2); - static std::map const& + static std::unordered_map const& getKnownCodeToField() { return knownCodeToField; @@ -305,7 +308,8 @@ public: private: static int num; - static std::map knownCodeToField; + static std::unordered_map knownCodeToField; + static std::unordered_map knownNameToField; }; /** A field with a type known at compile time. */ diff --git a/include/xrpl/protocol/STLedgerEntry.h b/include/xrpl/protocol/STLedgerEntry.h index 3609a04d4b..571c7af5fe 100644 --- a/include/xrpl/protocol/STLedgerEntry.h +++ b/include/xrpl/protocol/STLedgerEntry.h @@ -26,7 +26,9 @@ namespace ripple { class Rules; +namespace test { class Invariants_test; +} class STLedgerEntry final : public STObject, public CountedObject { @@ -36,6 +38,8 @@ class STLedgerEntry final : public STObject, public CountedObject public: using pointer = std::shared_ptr; using ref = std::shared_ptr const&; + using const_pointer = std::shared_ptr; + using const_ref = std::shared_ptr const&; /** Create an empty object with the given key and type. */ explicit STLedgerEntry(Keylet const& k); @@ -54,7 +58,7 @@ public: getText() const override; Json::Value - getJson(JsonOptions options) const override; + getJson(JsonOptions options = JsonOptions::none) const override; /** Returns the 'key' (or 'index') of this item. The key identifies this entry's position in @@ -84,7 +88,8 @@ private: void setSLEType(); - friend Invariants_test; // this test wants access to the private type_ + friend test::Invariants_test; // this test wants access to the private + // type_ STBase* copy(std::size_t n, void* buf) const override; diff --git a/include/xrpl/protocol/STObject.h b/include/xrpl/protocol/STObject.h index 6cd083ef85..b3cb561390 100644 --- a/include/xrpl/protocol/STObject.h +++ b/include/xrpl/protocol/STObject.h @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -34,6 +33,7 @@ #include #include #include +#include #include #include diff --git a/include/xrpl/protocol/STValidation.h b/include/xrpl/protocol/STValidation.h index 2aa74203a2..991922514d 100644 --- a/include/xrpl/protocol/STValidation.h +++ b/include/xrpl/protocol/STValidation.h @@ -22,10 +22,10 @@ #include #include -#include #include #include #include +#include #include #include diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 70c6833d3a..30d991e680 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -127,6 +127,8 @@ constexpr std::uint32_t tfTrustSetPermissionMask = ~(tfUniversal | tfSetfAuth | // EnableAmendment flags: constexpr std::uint32_t tfGotMajority = 0x00010000; constexpr std::uint32_t tfLostMajority = 0x00020000; +constexpr std::uint32_t tfChangeMask = + ~( tfUniversal | tfGotMajority | tfLostMajority); // PaymentChannelClaim flags: constexpr std::uint32_t tfRenew = 0x00010000; @@ -141,7 +143,8 @@ constexpr std::uint32_t const tfTransferable = 0x00000008; constexpr std::uint32_t const tfMutable = 0x00000010; // MPTokenIssuanceCreate flags: -// NOTE - there is intentionally no flag here for lsfMPTLocked, which this transaction cannot mutate. +// Note: tf/lsfMPTLocked is intentionally omitted, since this transaction +// is not allowed to modify it. constexpr std::uint32_t const tfMPTCanLock = lsfMPTCanLock; constexpr std::uint32_t const tfMPTRequireAuth = lsfMPTRequireAuth; constexpr std::uint32_t const tfMPTCanEscrow = lsfMPTCanEscrow; diff --git a/include/xrpl/protocol/Units.h b/include/xrpl/protocol/Units.h new file mode 100644 index 0000000000..6b2ae67059 --- /dev/null +++ b/include/xrpl/protocol/Units.h @@ -0,0 +1,555 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2019 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef PROTOCOL_UNITS_H_INCLUDED +#define PROTOCOL_UNITS_H_INCLUDED + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace ripple { + +namespace unit { + +/** "drops" are the smallest divisible amount of XRP. This is what most + of the code uses. */ +struct dropTag; +/** "fee levels" are used by the transaction queue to compare the relative + cost of transactions that require different levels of effort to process. + See also: src/ripple/app/misc/FeeEscalation.md#fee-level */ +struct feelevelTag; +/** unitless values are plain scalars wrapped in a ValueUnit. They are + used for calculations in this header. */ +struct unitlessTag; + +/** Units to represent basis points (bips) and 1/10 basis points */ +class BipsTag; +class TenthBipsTag; + +// These names don't have to be too descriptive, because we're in the "unit" +// namespace. + +template +concept Valid = std::is_class_v && std::is_object_v && + std::is_object_v; + +/** `Usable` is checked to ensure that only values with + known valid type tags can be used (sometimes transparently) in + non-unit contexts. At the time of implementation, this includes + all known tags, but more may be added in the future, and they + should not be added automatically unless determined to be + appropriate. +*/ +template +concept Usable = Valid && + (std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v); + +template +concept Compatible = Valid && std::is_arithmetic_v && + std::is_arithmetic_v && + std::is_convertible_v; + +template +concept Integral = std::is_integral_v; + +template +concept IntegralValue = Integral; + +template +concept CastableValue = IntegralValue && IntegralValue && + std::is_same_v; + +template +class ValueUnit : private boost::totally_ordered>, + private boost::additive>, + private boost::equality_comparable, T>, + private boost::dividable, T>, + private boost::modable, T>, + private boost::unit_steppable> +{ +public: + using unit_type = UnitTag; + using value_type = T; + +private: + value_type value_; + +public: + ValueUnit() = default; + constexpr ValueUnit(ValueUnit const& other) = default; + constexpr ValueUnit& + operator=(ValueUnit const& other) = default; + + constexpr explicit ValueUnit(beast::Zero) : value_(0) + { + } + + constexpr ValueUnit& + operator=(beast::Zero) + { + value_ = 0; + return *this; + } + + constexpr explicit ValueUnit(value_type value) : value_(value) + { + } + + constexpr ValueUnit& + operator=(value_type value) + { + value_ = value; + return *this; + } + + /** Instances with the same unit, and a type that is + "safe" to convert to this one can be converted + implicitly */ + template Other> + constexpr ValueUnit(ValueUnit const& value) + requires SafeToCast + : ValueUnit(safe_cast(value.value())) + { + } + + constexpr ValueUnit + operator+(value_type const& rhs) const + { + return ValueUnit{value_ + rhs}; + } + + friend constexpr ValueUnit + operator+(value_type lhs, ValueUnit const& rhs) + { + // addition is commutative + return rhs + lhs; + } + + constexpr ValueUnit + operator-(value_type const& rhs) const + { + return ValueUnit{value_ - rhs}; + } + + friend constexpr ValueUnit + operator-(value_type lhs, ValueUnit const& rhs) + { + // subtraction is NOT commutative, but (lhs + (-rhs)) is addition, which + // is + return -rhs + lhs; + } + + constexpr ValueUnit + operator*(value_type const& rhs) const + { + return ValueUnit{value_ * rhs}; + } + + friend constexpr ValueUnit + operator*(value_type lhs, ValueUnit const& rhs) + { + // multiplication is commutative + return rhs * lhs; + } + + constexpr value_type + operator/(ValueUnit const& rhs) const + { + return value_ / rhs.value_; + } + + ValueUnit& + operator+=(ValueUnit const& other) + { + value_ += other.value(); + return *this; + } + + ValueUnit& + operator-=(ValueUnit const& other) + { + value_ -= other.value(); + return *this; + } + + ValueUnit& + operator++() + { + ++value_; + return *this; + } + + ValueUnit& + operator--() + { + --value_; + return *this; + } + + ValueUnit& + operator*=(value_type const& rhs) + { + value_ *= rhs; + return *this; + } + + ValueUnit& + operator/=(value_type const& rhs) + { + value_ /= rhs; + return *this; + } + + template + ValueUnit& + operator%=(value_type const& rhs) + { + value_ %= rhs; + return *this; + } + + ValueUnit + operator-() const + { + static_assert( + std::is_signed_v, "- operator illegal on unsigned value types"); + return ValueUnit{-value_}; + } + + constexpr bool + operator==(ValueUnit const& other) const + { + return value_ == other.value_; + } + + template Other> + constexpr bool + operator==(ValueUnit const& other) const + { + return value_ == other.value(); + } + + constexpr bool + operator==(value_type other) const + { + return value_ == other; + } + + template Other> + constexpr bool + operator!=(ValueUnit const& other) const + { + return !operator==(other); + } + + constexpr bool + operator<(ValueUnit const& other) const + { + return value_ < other.value_; + } + + /** Returns true if the amount is not zero */ + explicit constexpr + operator bool() const noexcept + { + return value_ != 0; + } + + /** Return the sign of the amount */ + constexpr int + signum() const noexcept + { + return (value_ < 0) ? -1 : (value_ ? 1 : 0); + } + + /** Returns the number of drops */ + // TODO: Move this to a new class, maybe with the old "TaggedFee" name + constexpr value_type + fee() const + { + return value_; + } + + template + constexpr double + decimalFromReference(ValueUnit reference) const + { + return static_cast(value_) / reference.value(); + } + + // `Usable` is checked to ensure that only values with + // known valid type tags can be converted to JSON. At the time + // of implementation, that includes all known tags, but more may + // be added in the future. + Json::Value + jsonClipped() const + requires Usable + { + if constexpr (std::is_integral_v) + { + using jsontype = std::conditional_t< + std::is_signed_v, + Json::Int, + Json::UInt>; + + constexpr auto min = std::numeric_limits::min(); + constexpr auto max = std::numeric_limits::max(); + + if (value_ < min) + return min; + if (value_ > max) + return max; + return static_cast(value_); + } + else + { + return value_; + } + } + + /** Returns the underlying value. Code SHOULD NOT call this + function unless the type has been abstracted away, + e.g. in a templated function. + */ + constexpr value_type + value() const + { + return value_; + } + + friend std::istream& + operator>>(std::istream& s, ValueUnit& val) + { + s >> val.value_; + return s; + } +}; + +// Output Values as just their numeric value. +template +std::basic_ostream& +operator<<(std::basic_ostream& os, ValueUnit const& q) +{ + return os << q.value(); +} + +template +std::string +to_string(ValueUnit const& amount) +{ + return std::to_string(amount.value()); +} + +template +concept muldivSource = Valid && + std::is_convertible_v; + +template +concept muldivDest = muldivSource && // Dest is also a source + std::is_convertible_v && + sizeof(typename Dest::value_type) >= sizeof(std::uint64_t); + +template +concept muldivSources = muldivSource && muldivSource && + std::is_same_v; + +template +concept muldivable = muldivSources && muldivDest; +// Source and Dest can be the same by default + +template +concept muldivCommutable = muldivable && + !std::is_same_v; + +template +ValueUnit +scalar(T value) +{ + return ValueUnit{value}; +} + +template Dest> +std::optional +mulDivU(Source1 value, Dest mul, Source2 div) +{ + // values can never be negative in any context. + if (value.value() < 0 || mul.value() < 0 || div.value() < 0) + { + // split the asserts so if one hits, the user can tell which + // without a debugger. + XRPL_ASSERT( + value.value() >= 0, "ripple::unit::mulDivU : minimum value input"); + XRPL_ASSERT( + mul.value() >= 0, "ripple::unit::mulDivU : minimum mul input"); + XRPL_ASSERT( + div.value() > 0, "ripple::unit::mulDivU : minimum div input"); + return std::nullopt; + } + + using desttype = typename Dest::value_type; + constexpr auto max = std::numeric_limits::max(); + + // Shortcuts, since these happen a lot in the real world + if (value == div) + return mul; + if (mul.value() == div.value()) + { + if (value.value() > max) + return std::nullopt; + return Dest{static_cast(value.value())}; + } + + using namespace boost::multiprecision; + + uint128_t product; + product = multiply( + product, + static_cast(value.value()), + static_cast(mul.value())); + + auto quotient = product / div.value(); + + if (quotient > max) + return std::nullopt; + + return Dest{static_cast(quotient)}; +} + +} // namespace unit + +// Fee Levels +template +using FeeLevel = unit::ValueUnit; +using FeeLevel64 = FeeLevel; +using FeeLevelDouble = FeeLevel; + +// Basis points (Bips) +template +using Bips = unit::ValueUnit; +using Bips16 = Bips; +using Bips32 = Bips; +template +using TenthBips = unit::ValueUnit; +using TenthBips16 = TenthBips; +using TenthBips32 = TenthBips; + +template Dest> +std::optional +mulDiv(Source1 value, Dest mul, Source2 div) +{ + return unit::mulDivU(value, mul, div); +} + +template < + class Source1, + class Source2, + unit::muldivCommutable Dest> +std::optional +mulDiv(Dest value, Source1 mul, Source2 div) +{ + // Multiplication is commutative + return unit::mulDivU(mul, value, div); +} + +template +std::optional +mulDiv(std::uint64_t value, Dest mul, std::uint64_t div) +{ + // Give the scalars a non-tag so the + // unit-handling version gets called. + return unit::mulDivU(unit::scalar(value), mul, unit::scalar(div)); +} + +template +std::optional +mulDiv(Dest value, std::uint64_t mul, std::uint64_t div) +{ + // Multiplication is commutative + return mulDiv(mul, value, div); +} + +template Source2> +std::optional +mulDiv(Source1 value, std::uint64_t mul, Source2 div) +{ + // Give the scalars a dimensionless unit so the + // unit-handling version gets called. + auto unitresult = unit::mulDivU(value, unit::scalar(mul), div); + + if (!unitresult) + return std::nullopt; + + return unitresult->value(); +} + +template Source2> +std::optional +mulDiv(std::uint64_t value, Source1 mul, Source2 div) +{ + // Multiplication is commutative + return mulDiv(mul, value, div); +} + +template Src> +constexpr Dest +safe_cast(Src s) noexcept +{ + // Dest may not have an explicit value constructor + return Dest{safe_cast(s.value())}; +} + +template +constexpr Dest +safe_cast(Src s) noexcept +{ + // Dest may not have an explicit value constructor + return Dest{safe_cast(s)}; +} + +template Src> +constexpr Dest +unsafe_cast(Src s) noexcept +{ + // Dest may not have an explicit value constructor + return Dest{unsafe_cast(s.value())}; +} + +template +constexpr Dest +unsafe_cast(Src s) noexcept +{ + // Dest may not have an explicit value constructor + return Dest{unsafe_cast(s)}; +} + +} // namespace ripple + +#endif // PROTOCOL_UNITS_H_INCLUDED diff --git a/include/xrpl/protocol/XRPAmount.h b/include/xrpl/protocol/XRPAmount.h index 332735dc6f..a7a013d625 100644 --- a/include/xrpl/protocol/XRPAmount.h +++ b/include/xrpl/protocol/XRPAmount.h @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include @@ -42,7 +42,7 @@ class XRPAmount : private boost::totally_ordered, private boost::additive { public: - using unit_type = feeunit::dropTag; + using unit_type = unit::dropTag; using value_type = std::int64_t; private: diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 96192324fd..10fe015dac 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -174,7 +174,8 @@ TYPED_SFIELD(sfNFTokenID, UINT256, 10) TYPED_SFIELD(sfEmitParentTxnID, UINT256, 11) TYPED_SFIELD(sfEmitNonce, UINT256, 12) TYPED_SFIELD(sfEmitHookHash, UINT256, 13) -TYPED_SFIELD(sfAMMID, UINT256, 14) +TYPED_SFIELD(sfAMMID, UINT256, 14, + SField::sMD_PseudoAccount | SField::sMD_Default) // 256-bit (uncommon) TYPED_SFIELD(sfBookDirectory, UINT256, 16) @@ -196,7 +197,8 @@ TYPED_SFIELD(sfHookHash, UINT256, 31) TYPED_SFIELD(sfHookNamespace, UINT256, 32) TYPED_SFIELD(sfHookSetTxnID, UINT256, 33) TYPED_SFIELD(sfDomainID, UINT256, 34) -TYPED_SFIELD(sfVaultID, UINT256, 35) +TYPED_SFIELD(sfVaultID, UINT256, 35, + SField::sMD_PseudoAccount | SField::sMD_Default) TYPED_SFIELD(sfParentBatchID, UINT256, 36) // number (common) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 3aaa5a40a3..3ea4a3bbec 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -22,16 +22,31 @@ #endif /** - * TRANSACTION(tag, value, name, delegatable, amendments, fields) + * TRANSACTION(tag, value, name, delegatable, amendments, privileges, fields) + * + * To ease maintenance, you may replace any unneeded values with "..." + * e.g. #define TRANSACTION(tag, value, name, ...) * * You must define a transactor class in the `ripple` namespace named `name`, - * and include its header in `src/xrpld/app/tx/detail/applySteps.cpp`. + * and include its header alongside the TRANSACTOR definition using this + * format: + * #if TRANSACTION_INCLUDE + * # include + * #endif + * + * The `privileges` parameter of the TRANSACTION macro is a bitfield + * defining which operations the transaction can perform. + * The values are defined and used in InvariantCheck.cpp */ /** This transaction type executes a payment. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttPAYMENT, 0, Payment, Delegation::delegatable, uint256{}, + createAcct, ({ {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, @@ -45,9 +60,13 @@ TRANSACTION(ttPAYMENT, 0, Payment, })) /** This transaction type creates an escrow object. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, Delegation::delegatable, uint256{}, + noPriv, ({ {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, @@ -61,6 +80,7 @@ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, Delegation::delegatable, uint256{}, + noPriv, ({ {sfOwner, soeREQUIRED}, {sfOfferSequence, soeREQUIRED}, @@ -71,9 +91,13 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, /** This transaction type adjusts various account settings. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttACCOUNT_SET, 3, AccountSet, Delegation::notDelegatable, uint256{}, + noPriv, ({ {sfEmailHash, soeOPTIONAL}, {sfWalletLocator, soeOPTIONAL}, @@ -88,18 +112,26 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet, })) /** This transaction type cancels an existing escrow. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, Delegation::delegatable, uint256{}, + noPriv, ({ {sfOwner, soeREQUIRED}, {sfOfferSequence, soeREQUIRED}, })) /** This transaction type sets or clears an account's "regular key". */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, Delegation::notDelegatable, uint256{}, + noPriv, ({ {sfRegularKey, soeOPTIONAL}, })) @@ -107,9 +139,13 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, // 6 deprecated /** This transaction type creates an offer to trade one asset for another. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, Delegation::delegatable, uint256{}, + noPriv, ({ {sfTakerPays, soeREQUIRED}, {sfTakerGets, soeREQUIRED}, @@ -119,9 +155,13 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, })) /** This transaction type cancels existing offers to trade one asset for another. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, Delegation::delegatable, uint256{}, + noPriv, ({ {sfOfferSequence, soeREQUIRED}, })) @@ -129,9 +169,13 @@ TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, // 9 deprecated /** This transaction type creates a new set of tickets. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, Delegation::delegatable, featureTicketBatch, + noPriv, ({ {sfTicketCount, soeREQUIRED}, })) @@ -141,18 +185,26 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, /** This transaction type modifies the signer list associated with an account. */ // The SignerEntries are optional because a SignerList is deleted by // setting the SignerQuorum to zero and omitting SignerEntries. +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, Delegation::notDelegatable, uint256{}, + noPriv, ({ {sfSignerQuorum, soeREQUIRED}, {sfSignerEntries, soeOPTIONAL}, })) /** This transaction type creates a new unidirectional XRP payment channel. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, Delegation::delegatable, uint256{}, + noPriv, ({ {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED}, @@ -166,6 +218,7 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, Delegation::delegatable, uint256{}, + noPriv, ({ {sfChannel, soeREQUIRED}, {sfAmount, soeREQUIRED}, @@ -176,6 +229,7 @@ TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, Delegation::delegatable, uint256{}, + noPriv, ({ {sfChannel, soeREQUIRED}, {sfAmount, soeOPTIONAL}, @@ -186,9 +240,13 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, })) /** This transaction type creates a new check. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, Delegation::delegatable, featureChecks, + noPriv, ({ {sfDestination, soeREQUIRED}, {sfSendMax, soeREQUIRED}, @@ -198,9 +256,13 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, })) /** This transaction type cashes an existing check. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttCHECK_CASH, 17, CheckCash, Delegation::delegatable, featureChecks, + noPriv, ({ {sfCheckID, soeREQUIRED}, {sfAmount, soeOPTIONAL}, @@ -208,17 +270,25 @@ TRANSACTION(ttCHECK_CASH, 17, CheckCash, })) /** This transaction type cancels an existing check. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, Delegation::delegatable, featureChecks, + noPriv, ({ {sfCheckID, soeREQUIRED}, })) /** This transaction type grants or revokes authorization to transfer funds. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, Delegation::delegatable, featureDepositPreauth, + noPriv, ({ {sfAuthorize, soeOPTIONAL}, {sfUnauthorize, soeOPTIONAL}, @@ -227,9 +297,13 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, })) /** This transaction type modifies a trustline between two accounts. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttTRUST_SET, 20, TrustSet, Delegation::delegatable, uint256{}, + noPriv, ({ {sfLimitAmount, soeOPTIONAL}, {sfQualityIn, soeOPTIONAL}, @@ -237,9 +311,13 @@ TRANSACTION(ttTRUST_SET, 20, TrustSet, })) /** This transaction type deletes an existing account. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, Delegation::notDelegatable, uint256{}, + mustDeleteAcct, ({ {sfDestination, soeREQUIRED}, {sfDestinationTag, soeOPTIONAL}, @@ -249,9 +327,13 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, // 22 reserved /** This transaction mints a new NFT. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, Delegation::delegatable, featureNonFungibleTokensV1, + changeNFTCounts, ({ {sfNFTokenTaxon, soeREQUIRED}, {sfTransferFee, soeOPTIONAL}, @@ -263,18 +345,26 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, })) /** This transaction burns (i.e. destroys) an existing NFT. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, Delegation::delegatable, featureNonFungibleTokensV1, + changeNFTCounts, ({ {sfNFTokenID, soeREQUIRED}, {sfOwner, soeOPTIONAL}, })) /** This transaction creates a new offer to buy or sell an NFT. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, Delegation::delegatable, featureNonFungibleTokensV1, + noPriv, ({ {sfNFTokenID, soeREQUIRED}, {sfAmount, soeREQUIRED}, @@ -284,17 +374,25 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, })) /** This transaction cancels an existing offer to buy or sell an existing NFT. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, Delegation::delegatable, featureNonFungibleTokensV1, + noPriv, ({ {sfNFTokenOffers, soeREQUIRED}, })) /** This transaction accepts an existing offer to buy or sell an existing NFT. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, Delegation::delegatable, featureNonFungibleTokensV1, + noPriv, ({ {sfNFTokenBuyOffer, soeOPTIONAL}, {sfNFTokenSellOffer, soeOPTIONAL}, @@ -302,18 +400,26 @@ TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, })) /** This transaction claws back issued tokens. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttCLAWBACK, 30, Clawback, Delegation::delegatable, featureClawback, + noPriv, ({ {sfAmount, soeREQUIRED, soeMPTSupported}, {sfHolder, soeOPTIONAL}, })) /** This transaction claws back tokens from an AMM pool. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, Delegation::delegatable, featureAMMClawback, + mayDeleteAcct | overrideFreeze, ({ {sfHolder, soeREQUIRED}, {sfAsset, soeREQUIRED}, @@ -322,9 +428,13 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, })) /** This transaction type creates an AMM instance */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttAMM_CREATE, 35, AMMCreate, Delegation::delegatable, featureAMM, + createPseudoAcct, ({ {sfAmount, soeREQUIRED}, {sfAmount2, soeREQUIRED}, @@ -332,9 +442,13 @@ TRANSACTION(ttAMM_CREATE, 35, AMMCreate, })) /** This transaction type deposits into an AMM instance */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, Delegation::delegatable, featureAMM, + noPriv, ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, @@ -346,9 +460,13 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, })) /** This transaction type withdraws from an AMM instance */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, Delegation::delegatable, featureAMM, + mayDeleteAcct, ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, @@ -359,9 +477,13 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, })) /** This transaction type votes for the trading fee */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttAMM_VOTE, 38, AMMVote, Delegation::delegatable, featureAMM, + noPriv, ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, @@ -369,9 +491,13 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote, })) /** This transaction type bids for the auction slot */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttAMM_BID, 39, AMMBid, Delegation::delegatable, featureAMM, + noPriv, ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, @@ -381,18 +507,26 @@ TRANSACTION(ttAMM_BID, 39, AMMBid, })) /** This transaction type deletes AMM in the empty state */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttAMM_DELETE, 40, AMMDelete, Delegation::delegatable, featureAMM, + mustDeleteAcct, ({ {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, })) /** This transactions creates a crosschain sequence number */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, Delegation::delegatable, featureXChainBridge, + noPriv, ({ {sfXChainBridge, soeREQUIRED}, {sfSignatureReward, soeREQUIRED}, @@ -403,6 +537,7 @@ TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, Delegation::delegatable, featureXChainBridge, + noPriv, ({ {sfXChainBridge, soeREQUIRED}, {sfXChainClaimID, soeREQUIRED}, @@ -414,6 +549,7 @@ TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, Delegation::delegatable, featureXChainBridge, + noPriv, ({ {sfXChainBridge, soeREQUIRED}, {sfXChainClaimID, soeREQUIRED}, @@ -426,6 +562,7 @@ TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, Delegation::delegatable, featureXChainBridge, + noPriv, ({ {sfXChainBridge, soeREQUIRED}, {sfDestination, soeREQUIRED}, @@ -437,6 +574,7 @@ TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, Delegation::delegatable, featureXChainBridge, + createAcct, ({ {sfXChainBridge, soeREQUIRED}, @@ -453,9 +591,11 @@ TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, })) /** This transaction adds an attestation to an account */ -TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateAttestation, +TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, + XChainAddAccountCreateAttestation, Delegation::delegatable, featureXChainBridge, + createAcct, ({ {sfXChainBridge, soeREQUIRED}, @@ -476,6 +616,7 @@ TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateA TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge, Delegation::delegatable, featureXChainBridge, + noPriv, ({ {sfXChainBridge, soeREQUIRED}, {sfSignatureReward, soeOPTIONAL}, @@ -486,6 +627,7 @@ TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge, TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, Delegation::delegatable, featureXChainBridge, + noPriv, ({ {sfXChainBridge, soeREQUIRED}, {sfSignatureReward, soeREQUIRED}, @@ -493,9 +635,13 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, })) /** This transaction type creates or updates a DID */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttDID_SET, 49, DIDSet, Delegation::delegatable, featureDID, + noPriv, ({ {sfDIDDocument, soeOPTIONAL}, {sfURI, soeOPTIONAL}, @@ -506,12 +652,17 @@ TRANSACTION(ttDID_SET, 49, DIDSet, TRANSACTION(ttDID_DELETE, 50, DIDDelete, Delegation::delegatable, featureDID, + noPriv, ({})) /** This transaction type creates an Oracle instance */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttORACLE_SET, 51, OracleSet, Delegation::delegatable, featurePriceOracle, + noPriv, ({ {sfOracleDocumentID, soeREQUIRED}, {sfProvider, soeOPTIONAL}, @@ -522,26 +673,38 @@ TRANSACTION(ttORACLE_SET, 51, OracleSet, })) /** This transaction type deletes an Oracle instance */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, Delegation::delegatable, featurePriceOracle, + noPriv, ({ {sfOracleDocumentID, soeREQUIRED}, })) /** This transaction type fixes a problem in the ledger state */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, Delegation::delegatable, fixNFTokenPageLinks, + noPriv, ({ {sfLedgerFixType, soeREQUIRED}, {sfOwner, soeOPTIONAL}, })) /** This transaction type creates a MPTokensIssuance instance */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, Delegation::delegatable, featureMPTokensV1, + createMPTIssuance, ({ {sfAssetScale, soeOPTIONAL}, {sfTransferFee, soeOPTIONAL}, @@ -552,17 +715,25 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, })) /** This transaction type destroys a MPTokensIssuance instance */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, Delegation::delegatable, featureMPTokensV1, + destroyMPTIssuance, ({ {sfMPTokenIssuanceID, soeREQUIRED}, })) /** This transaction type sets flags on a MPTokensIssuance or MPToken instance */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, Delegation::delegatable, featureMPTokensV1, + noPriv, ({ {sfMPTokenIssuanceID, soeREQUIRED}, {sfHolder, soeOPTIONAL}, @@ -573,18 +744,26 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, })) /** This transaction type authorizes a MPToken instance */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, Delegation::delegatable, featureMPTokensV1, + mustAuthorizeMPT, ({ {sfMPTokenIssuanceID, soeREQUIRED}, {sfHolder, soeOPTIONAL}, })) /** This transaction type create an Credential instance */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, Delegation::delegatable, featureCredentials, + noPriv, ({ {sfSubject, soeREQUIRED}, {sfCredentialType, soeREQUIRED}, @@ -596,6 +775,7 @@ TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept, Delegation::delegatable, featureCredentials, + noPriv, ({ {sfIssuer, soeREQUIRED}, {sfCredentialType, soeREQUIRED}, @@ -605,6 +785,7 @@ TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept, TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, Delegation::delegatable, featureCredentials, + noPriv, ({ {sfSubject, soeOPTIONAL}, {sfIssuer, soeOPTIONAL}, @@ -612,9 +793,13 @@ TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, })) /** This transaction type modify a NFToken */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, Delegation::delegatable, featureDynamicNFT, + noPriv, ({ {sfNFTokenID, soeREQUIRED}, {sfOwner, soeOPTIONAL}, @@ -622,35 +807,51 @@ TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, })) /** This transaction type creates or modifies a Permissioned Domain */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet, Delegation::delegatable, featurePermissionedDomains, + noPriv, ({ {sfDomainID, soeOPTIONAL}, {sfAcceptedCredentials, soeREQUIRED}, })) /** This transaction type deletes a Permissioned Domain */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete, Delegation::delegatable, featurePermissionedDomains, + noPriv, ({ {sfDomainID, soeREQUIRED}, })) /** This transaction type delegates authorized account specified permissions */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttDELEGATE_SET, 64, DelegateSet, Delegation::notDelegatable, featurePermissionDelegation, + noPriv, ({ {sfAuthorize, soeREQUIRED}, {sfPermissions, soeREQUIRED}, })) /** This transaction creates a single asset vault. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegatable, featureSingleAssetVault, + createPseudoAcct | createMPTIssuance, ({ {sfAsset, soeREQUIRED, soeMPTSupported}, {sfAssetsMaximum, soeOPTIONAL}, @@ -662,9 +863,13 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, })) /** This transaction updates a single asset vault. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttVAULT_SET, 66, VaultSet, Delegation::delegatable, featureSingleAssetVault, + noPriv, ({ {sfVaultID, soeREQUIRED}, {sfAssetsMaximum, soeOPTIONAL}, @@ -673,26 +878,38 @@ TRANSACTION(ttVAULT_SET, 66, VaultSet, })) /** This transaction deletes a single asset vault. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttVAULT_DELETE, 67, VaultDelete, Delegation::delegatable, featureSingleAssetVault, + mustDeleteAcct | destroyMPTIssuance, ({ {sfVaultID, soeREQUIRED}, })) /** This transaction trades assets for shares with a vault. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit, Delegation::delegatable, featureSingleAssetVault, + mayAuthorizeMPT, ({ {sfVaultID, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, })) /** This transaction trades shares for assets with a vault. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, Delegation::delegatable, featureSingleAssetVault, + mayDeleteMPT, ({ {sfVaultID, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, @@ -701,9 +918,13 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, })) /** This transaction claws back tokens from a vault. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback, Delegation::delegatable, featureSingleAssetVault, + mayDeleteMPT, ({ {sfVaultID, soeREQUIRED}, {sfHolder, soeREQUIRED}, @@ -711,9 +932,13 @@ TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback, })) /** This transaction type batches together transactions. */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttBATCH, 71, Batch, Delegation::notDelegatable, featureBatch, + noPriv, ({ {sfRawTransactions, soeREQUIRED}, {sfBatchSigners, soeOPTIONAL}, @@ -723,9 +948,13 @@ TRANSACTION(ttBATCH, 71, Batch, For details, see: https://xrpl.org/amendments.html */ +#if TRANSACTION_INCLUDE +# include +#endif TRANSACTION(ttAMENDMENT, 100, EnableAmendment, Delegation::notDelegatable, uint256{}, + noPriv, ({ {sfLedgerSequence, soeREQUIRED}, {sfAmendment, soeREQUIRED}, @@ -737,6 +966,7 @@ TRANSACTION(ttAMENDMENT, 100, EnableAmendment, TRANSACTION(ttFEE, 101, SetFee, Delegation::notDelegatable, uint256{}, + noPriv, ({ {sfLedgerSequence, soeOPTIONAL}, // Old version uses raw numbers @@ -757,6 +987,7 @@ TRANSACTION(ttFEE, 101, SetFee, TRANSACTION(ttUNL_MODIFY, 102, UNLModify, Delegation::notDelegatable, uint256{}, + noPriv, ({ {sfUNLModifyDisabling, soeREQUIRED}, {sfLedgerSequence, soeREQUIRED}, diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index d847cf6012..8609aedaef 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -722,11 +722,11 @@ JSS(write_load); // out: GetCounts #pragma push_macro("LEDGER_ENTRY_DUPLICATE") #undef LEDGER_ENTRY_DUPLICATE -#define LEDGER_ENTRY(tag, value, name, rpcName, fields) \ - JSS(name); \ +#define LEDGER_ENTRY(tag, value, name, rpcName, ...) \ + JSS(name); \ JSS(rpcName); -#define LEDGER_ENTRY_DUPLICATE(tag, value, name, rpcName, fields) JSS(rpcName); +#define LEDGER_ENTRY_DUPLICATE(tag, value, name, rpcName, ...) JSS(rpcName); #include diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index 45aded0030..3e27741c2f 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -626,8 +626,8 @@ xrpLiquid( std::uint32_t const ownerCount = confineOwnerCount( view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj); - // AMMs have no reserve requirement - auto const reserve = sle->isFieldPresent(sfAMMID) + // Pseudo-accounts have no reserve requirement + auto const reserve = isPseudoAccount(sle) ? XRPAmount{0} : view.fees().accountReserve(ownerCount); @@ -1039,7 +1039,7 @@ adjustOwnerCount( AccountID const id = (*sle)[sfAccount]; std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j); view.adjustOwnerCountHook(id, current, adjusted); - sle->setFieldU32(sfOwnerCount, adjusted); + sle->at(sfOwnerCount) = adjusted; view.update(sle); } @@ -1079,15 +1079,51 @@ pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey) return beast::zero; } -// Note, the list of the pseudo-account designator fields below MUST be -// maintained but it does NOT need to be amendment-gated, since a -// non-active amendment will not set any field, by definition. Specific -// properties of a pseudo-account are NOT checked here, that's what +// Pseudo-account designator fields MUST be maintained by including the +// SField::sMD_PseudoAccount flag in the SField definition. (Don't forget to +// "| SField::sMD_Default"!) The fields do NOT need to be amendment-gated, +// since a non-active amendment will not set any field, by definition. +// Specific properties of a pseudo-account are NOT checked here, that's what // InvariantCheck is for. -static std::array const pseudoAccountOwnerFields = { - &sfAMMID, // - &sfVaultID, // -}; +[[nodiscard]] std::vector const& +getPseudoAccountFields() +{ + static std::vector const pseudoFields = []() { + auto const ar = LedgerFormats::getInstance().findByType(ltACCOUNT_ROOT); + if (!ar) + { + // LCOV_EXCL_START + LogicError( + "ripple::isPseudoAccount : unable to find account root ledger " + "format"); + // LCOV_EXCL_STOP + } + auto const& soTemplate = ar->getSOTemplate(); + + std::vector pseudoFields; + for (auto const& field : soTemplate) + { + if (field.sField().shouldMeta(SField::sMD_PseudoAccount)) + pseudoFields.emplace_back(&field.sField()); + } + return pseudoFields; + }(); + return pseudoFields; +} + +[[nodiscard]] bool +isPseudoAccount(std::shared_ptr sleAcct) +{ + auto const& fields = getPseudoAccountFields(); + + // Intentionally use defensive coding here because it's cheap and makes the + // semantics of true return value clean. + return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT && + std::count_if( + fields.begin(), fields.end(), [&sleAcct](SField const* sf) -> bool { + return sleAcct->isFieldPresent(*sf); + }) > 0; +} Expected, TER> createPseudoAccount( @@ -1095,10 +1131,11 @@ createPseudoAccount( uint256 const& pseudoOwnerKey, SField const& ownerField) { + auto const& fields = getPseudoAccountFields(); XRPL_ASSERT( std::count_if( - pseudoAccountOwnerFields.begin(), - pseudoAccountOwnerFields.end(), + fields.begin(), + fields.end(), [&ownerField](SField const* sf) -> bool { return *sf == ownerField; }) == 1, @@ -1134,18 +1171,42 @@ createPseudoAccount( return account; } -[[nodiscard]] bool -isPseudoAccount(std::shared_ptr sleAcct) +[[nodiscard]] TER +canAddHolding(ReadView const& view, Issue const& issue) { - // Intentionally use defensive coding here because it's cheap and makes the - // semantics of true return value clean. - return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT && - std::count_if( - pseudoAccountOwnerFields.begin(), - pseudoAccountOwnerFields.end(), - [&sleAcct](SField const* sf) -> bool { - return sleAcct->isFieldPresent(*sf); - }) > 0; + if (issue.native()) + return tesSUCCESS; // No special checks for XRP + + auto const issuer = view.read(keylet::account(issue.getIssuer())); + if (!issuer) + return terNO_ACCOUNT; + else if (!issuer->isFlag(lsfDefaultRipple)) + return terNO_RIPPLE; + + return tesSUCCESS; +} + +[[nodiscard]] TER +canAddHolding(ReadView const& view, MPTIssue const& mptIssue) +{ + auto mptID = mptIssue.getMptID(); + auto issuance = view.read(keylet::mptIssuance(mptID)); + if (!issuance) + return tecOBJECT_NOT_FOUND; + if (!issuance->isFlag(lsfMPTCanTransfer)) + return tecNO_AUTH; + + return tesSUCCESS; +} + +[[nodiscard]] TER +canAddHolding(ReadView const& view, Asset const& asset) +{ + return std::visit( + [&](TIss const& issue) -> TER { + return canAddHolding(view, issue); + }, + asset.value()); } [[nodiscard]] TER diff --git a/src/libxrpl/protocol/SField.cpp b/src/libxrpl/protocol/SField.cpp index 1ffce099b8..57e5849c00 100644 --- a/src/libxrpl/protocol/SField.cpp +++ b/src/libxrpl/protocol/SField.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include #include #include @@ -27,7 +28,8 @@ namespace ripple { // Storage for static const members. SField::IsSigning const SField::notSigning; int SField::num = 0; -std::map SField::knownCodeToField; +std::unordered_map SField::knownCodeToField; +std::unordered_map SField::knownNameToField; // Give only this translation unit permission to construct SFields struct SField::private_access_tag_t @@ -45,7 +47,7 @@ TypedField::TypedField(private_access_tag_t pat, Args&&... args) } // Construct all compile-time SFields, and register them in the knownCodeToField -// database: +// and knownNameToField databases: // Use macros for most SField construction to enforce naming conventions. #pragma push_macro("UNTYPED_SFIELD") @@ -69,8 +71,8 @@ TypedField::TypedField(private_access_tag_t pat, Args&&... args) ##__VA_ARGS__); // SFields which, for historical reasons, do not follow naming conventions. -SField const sfInvalid(access, -1); -SField const sfGeneric(access, 0); +SField const sfInvalid(access, -1, ""); +SField const sfGeneric(access, 0, "Generic"); // The following two fields aren't used anywhere, but they break tests/have // downstream effects. SField const sfHash(access, STI_UINT256, 257, "hash"); @@ -99,19 +101,34 @@ SField::SField( , signingField(signing) , jsonName(fieldName.c_str()) { + XRPL_ASSERT( + !knownCodeToField.contains(fieldCode), + "ripple::SField::SField(tid,fv,fn,meta,signing) : fieldCode is unique"); + XRPL_ASSERT( + !knownNameToField.contains(fieldName), + "ripple::SField::SField(tid,fv,fn,meta,signing) : fieldName is unique"); knownCodeToField[fieldCode] = this; + knownNameToField[fieldName] = this; } -SField::SField(private_access_tag_t, int fc) +SField::SField(private_access_tag_t, int fc, char const* fn) : fieldCode(fc) , fieldType(STI_UNKNOWN) , fieldValue(0) + , fieldName(fn) , fieldMeta(sMD_Never) , fieldNum(++num) , signingField(IsSigning::yes) , jsonName(fieldName.c_str()) { + XRPL_ASSERT( + !knownCodeToField.contains(fieldCode), + "ripple::SField::SField(fc,fn) : fieldCode is unique"); + XRPL_ASSERT( + !knownNameToField.contains(fieldName), + "ripple::SField::SField(fc,fn) : fieldName is unique"); knownCodeToField[fieldCode] = this; + knownNameToField[fieldName] = this; } SField const& @@ -145,11 +162,11 @@ SField::compare(SField const& f1, SField const& f2) SField const& SField::getField(std::string const& fieldName) { - for (auto const& [_, f] : knownCodeToField) + auto it = knownNameToField.find(fieldName); + + if (it != knownNameToField.end()) { - (void)_; - if (f->fieldName == fieldName) - return *f; + return *(it->second); } return sfInvalid; } diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index c10c023ee9..1966c29b51 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -55,7 +55,8 @@ TxFormats::TxFormats() #undef TRANSACTION #define UNWRAP(...) __VA_ARGS__ -#define TRANSACTION(tag, value, name, delegatable, amendment, fields) \ +#define TRANSACTION( \ + tag, value, name, delegatable, amendment, privileges, fields) \ add(jss::name, tag, UNWRAP fields, commonFields); #include diff --git a/src/test/app/AMMCalc_test.cpp b/src/test/app/AMMCalc_test.cpp index bebf2844b6..7349b38766 100644 --- a/src/test/app/AMMCalc_test.cpp +++ b/src/test/app/AMMCalc_test.cpp @@ -67,7 +67,7 @@ class AMMCalc_test : public beast::unit_test::suite // drops else if (match[1] == "XRPA") return XRPAmount{std::stoll(match[2])}; - return amountFromString(gw[match[1]], match[2]); + return amountFromString(gw[match[1]].asset(), match[2]); } return std::nullopt; } diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index cb937038fe..dcbebd2dc3 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -181,9 +181,9 @@ private: BEAST_EXPECT(expectLedgerEntryRoot( env, alice, XRP(20'000) - XRP(50) - txfee(env, 1))); - BEAST_EXPECT(expectLine(env, bob, USD1(100))); - BEAST_EXPECT(expectLine(env, bob, USD2(0))); - BEAST_EXPECT(expectLine(env, carol, USD2(50))); + BEAST_EXPECT(expectHolding(env, bob, USD1(100))); + BEAST_EXPECT(expectHolding(env, bob, USD2(0))); + BEAST_EXPECT(expectHolding(env, carol, USD2(50))); } } @@ -220,7 +220,7 @@ private: BEAST_EXPECT(expectLedgerEntryRoot( env, carol, XRP(30'000) - (txfee(env, 1)))); BEAST_EXPECT(expectOffers(env, carol, 0)); - BEAST_EXPECT(expectLine(env, carol, USD(30'000))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'000))); // Order that can be filled env(offer(carol, XRP(100), USD(100)), @@ -230,7 +230,7 @@ private: XRP(10'000), USD(10'100), ammAlice.tokens())); BEAST_EXPECT(expectLedgerEntryRoot( env, carol, XRP(30'000) + XRP(100) - txfee(env, 2))); - BEAST_EXPECT(expectLine(env, carol, USD(29'900))); + BEAST_EXPECT(expectHolding(env, carol, USD(29'900))); BEAST_EXPECT(expectOffers(env, carol, 0)); }, {{XRP(10'100), USD(10'000)}}, @@ -254,7 +254,7 @@ private: BEAST_EXPECT(expectLedgerEntryRoot( env, carol, XRP(30'000) + XRP(100) - txfee(env, 1))); // AMM - BEAST_EXPECT(expectLine(env, carol, USD(29'900))); + BEAST_EXPECT(expectHolding(env, carol, USD(29'900))); BEAST_EXPECT(expectOffers(env, carol, 0)); }, {{XRP(10'100), USD(10'000)}}, @@ -327,7 +327,7 @@ private: USD(49), IOUAmount{273'861'278752583, -8})); - BEAST_EXPECT(expectLine(env, bob, STAmount{USD, 101})); + BEAST_EXPECT(expectHolding(env, bob, STAmount{USD, 101})); BEAST_EXPECT(expectLedgerEntryRoot( env, bob, XRP(300'000) - xrpTransferred - txfee(env, 1))); BEAST_EXPECT(expectOffers(env, bob, 0)); @@ -390,7 +390,7 @@ private: BEAST_EXPECT( ammBob.expectBalances(USD(300), XRP(1'000), ammBob.tokens())); - BEAST_EXPECT(expectLine(env, alice, USD(0))); + BEAST_EXPECT(expectHolding(env, alice, USD(0))); auto jrr = ledgerEntryRoot(env, alice); BEAST_EXPECT( @@ -423,7 +423,7 @@ private: BEAST_EXPECT(ammAlice.expectBalances( XRPAmount{9'900'990'100}, USD(10'100), ammAlice.tokens())); // initial 30,000 - 10,000AMM - 100pay - BEAST_EXPECT(expectLine(env, alice, USD(19'900))); + BEAST_EXPECT(expectHolding(env, alice, USD(19'900))); // initial 30,000 - 10,0000AMM + 99.009900pay - fee*3 BEAST_EXPECT(expectLedgerEntryRoot( env, @@ -453,7 +453,7 @@ private: env(pay(alice, bob, USD(100)), sendmax(XRP(100))); BEAST_EXPECT(ammAlice.expectBalances( XRP(10'100), USD(10'000), ammAlice.tokens())); - BEAST_EXPECT(expectLine(env, bob, USD(100))); + BEAST_EXPECT(expectHolding(env, bob, USD(100))); }, {{XRP(10'000), USD(10'100)}}, 0, @@ -533,7 +533,7 @@ private: STAmount{USD1, UINT64_C(5'030'181086519115), -12}, ammCarol.tokens())); BEAST_EXPECT(expectOffers(env, dan, 1, {{Amounts{XRP(200), EUR(20)}}})); - BEAST_EXPECT(expectLine(env, bob, STAmount{EUR1, 30})); + BEAST_EXPECT(expectHolding(env, bob, STAmount{EUR1, 30})); } void @@ -642,7 +642,7 @@ private: BEAST_EXPECT(ammAlice.expectBalances( XRP(10'000), USD(9'999), ammAlice.tokens())); BEAST_EXPECT(expectOffers(env, carol, 0)); - BEAST_EXPECT(expectLine(env, carol, USD(30'101))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'101))); BEAST_EXPECT(expectLedgerEntryRoot( env, carol, XRP(30'000) - XRP(100) - txfee(env, 1))); }, @@ -682,7 +682,7 @@ private: env(offer(alice, USD(100), XRP(200)), json(jss::Flags, tfSell)); BEAST_EXPECT( ammBob.expectBalances(XRP(1'100), USD(2'000), ammBob.tokens())); - BEAST_EXPECT(expectLine(env, alice, USD(200))); + BEAST_EXPECT(expectHolding(env, alice, USD(200))); BEAST_EXPECT(expectLedgerEntryRoot(env, alice, XRP(250))); BEAST_EXPECT(expectOffers(env, alice, 0)); } @@ -733,7 +733,7 @@ private: STAmount(XTS, UINT64_C(101'010101010101), -12), XXX(99), ammAlice.tokens())); - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, bob, STAmount{XTS, UINT64_C(98'989898989899), -12})); } else @@ -742,10 +742,10 @@ private: STAmount(XTS, UINT64_C(101'0101010101011), -13), XXX(99), ammAlice.tokens())); - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, bob, STAmount{XTS, UINT64_C(98'9898989898989), -13})); } - BEAST_EXPECT(expectLine(env, bob, XXX(101))); + BEAST_EXPECT(expectHolding(env, bob, XXX(101))); } void @@ -783,8 +783,8 @@ private: XRP(10'100), USD(10'000), ammAlice.tokens())); BEAST_EXPECT(ammBob.expectBalances( XRP(10'000), EUR(10'100), ammBob.tokens())); - BEAST_EXPECT(expectLine(env, carol, USD(15'100))); - BEAST_EXPECT(expectLine(env, carol, EUR(14'900))); + BEAST_EXPECT(expectHolding(env, carol, USD(15'100))); + BEAST_EXPECT(expectHolding(env, carol, EUR(14'900))); BEAST_EXPECT(expectOffers(env, carol, 0)); } @@ -816,8 +816,8 @@ private: BEAST_EXPECT(ammAlice.expectBalances( XRP(10'100), USD(10'000), ammAlice.tokens())); - BEAST_EXPECT(expectLine(env, carol, USD(15'100))); - BEAST_EXPECT(expectLine(env, carol, EUR(14'900))); + BEAST_EXPECT(expectHolding(env, carol, USD(15'100))); + BEAST_EXPECT(expectHolding(env, carol, EUR(14'900))); BEAST_EXPECT(expectOffers(env, carol, 0)); BEAST_EXPECT(expectOffers(env, bob, 0)); } @@ -850,8 +850,8 @@ private: BEAST_EXPECT(ammBob.expectBalances( XRP(10'000), EUR(10'100), ammBob.tokens())); - BEAST_EXPECT(expectLine(env, carol, USD(15'100))); - BEAST_EXPECT(expectLine(env, carol, EUR(14'900))); + BEAST_EXPECT(expectHolding(env, carol, USD(15'100))); + BEAST_EXPECT(expectHolding(env, carol, EUR(14'900))); BEAST_EXPECT(expectOffers(env, carol, 0)); BEAST_EXPECT(expectOffers(env, alice, 0)); } @@ -894,7 +894,7 @@ private: XRP(20'220), STAmount{USD, UINT64_C(197'8239366963403), -13}, ammBob.tokens())); - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, alice, STAmount{USD, UINT64_C(1'002'17606330366), -11})); BEAST_EXPECT(expectOffers(env, alice, 0)); } @@ -912,7 +912,7 @@ private: XRP(21'500), STAmount{USD, UINT64_C(186'046511627907), -12}, ammBob.tokens())); - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, alice, STAmount{USD, UINT64_C(1'013'953488372093), -12})); BEAST_EXPECT(expectOffers(env, alice, 0)); } @@ -953,7 +953,7 @@ private: // AMM doesn't pay the transfer fee BEAST_EXPECT(ammAlice.expectBalances( XRP(10'100), USD(10'000), ammAlice.tokens())); - BEAST_EXPECT(expectLine(env, carol, USD(30'100))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'100))); BEAST_EXPECT(expectOffers(env, carol, 0)); }, {{XRP(10'000), USD(10'100)}}, @@ -974,7 +974,7 @@ private: BEAST_EXPECT(ammAlice.expectBalances( XRP(10'000), USD(10'100), ammAlice.tokens())); // Carol pays 25% transfer fee - BEAST_EXPECT(expectLine(env, carol, USD(29'875))); + BEAST_EXPECT(expectHolding(env, carol, USD(29'875))); BEAST_EXPECT(expectOffers(env, carol, 0)); }, {{XRP(10'100), USD(10'000)}}, @@ -1011,9 +1011,9 @@ private: // AMM doesn't pay the transfer fee BEAST_EXPECT(ammAlice.expectBalances( XRP(10'100), USD(10'000), ammAlice.tokens())); - BEAST_EXPECT(expectLine(env, carol, USD(15'100))); + BEAST_EXPECT(expectHolding(env, carol, USD(15'100))); // Carol pays 25% transfer fee. - BEAST_EXPECT(expectLine(env, carol, EUR(14'875))); + BEAST_EXPECT(expectHolding(env, carol, EUR(14'875))); BEAST_EXPECT(expectOffers(env, carol, 0)); BEAST_EXPECT(expectOffers(env, bob, 0)); } @@ -1051,9 +1051,9 @@ private: // AMM doesn't pay the transfer fee BEAST_EXPECT(ammAlice.expectBalances( XRP(10'050), USD(10'000), ammAlice.tokens())); - BEAST_EXPECT(expectLine(env, carol, USD(15'050))); + BEAST_EXPECT(expectHolding(env, carol, USD(15'050))); // Carol pays 25% transfer fee. - BEAST_EXPECT(expectLine(env, carol, EUR(14'937.5))); + BEAST_EXPECT(expectHolding(env, carol, EUR(14'937.5))); BEAST_EXPECT(expectOffers(env, carol, 0)); BEAST_EXPECT( expectOffers(env, bob, 1, {{Amounts{EUR(50), XRP(50)}}})); @@ -1077,7 +1077,7 @@ private: env(pay(gw, carol, EUR(1'000)), sendmax(EUR(10'000))); env.close(); // 1000 / 0.8 - BEAST_EXPECT(expectLine(env, carol, EUR(1'250))); + BEAST_EXPECT(expectHolding(env, carol, EUR(1'250))); // The scenario: // o USD/XRP AMM is created. // o EUR/XRP Offer is created. @@ -1096,9 +1096,9 @@ private: // AMM doesn't pay the transfer fee BEAST_EXPECT(ammAlice.expectBalances( XRP(10'100), USD(10'000), ammAlice.tokens())); - BEAST_EXPECT(expectLine(env, carol, USD(100))); + BEAST_EXPECT(expectHolding(env, carol, USD(100))); // Carol pays 25% transfer fee: 1250 - 100(offer) - 25(transfer fee) - BEAST_EXPECT(expectLine(env, carol, EUR(1'125))); + BEAST_EXPECT(expectHolding(env, carol, EUR(1'125))); BEAST_EXPECT(expectOffers(env, carol, 0)); BEAST_EXPECT(expectOffers(env, bob, 0)); } @@ -1120,7 +1120,7 @@ private: env(pay(gw, alice, USD(11'000))); env(pay(gw, carol, EUR(1'000)), sendmax(EUR(10'000))); env.close(); - BEAST_EXPECT(expectLine(env, carol, EUR(1'000))); + BEAST_EXPECT(expectHolding(env, carol, EUR(1'000))); // The scenario: // o USD/XRP AMM is created. // o EUR/XRP Offer is created. @@ -1139,9 +1139,9 @@ private: // AMM pay doesn't transfer fee BEAST_EXPECT(ammAlice.expectBalances( XRP(10'100), USD(10'000), ammAlice.tokens())); - BEAST_EXPECT(expectLine(env, carol, USD(100))); + BEAST_EXPECT(expectHolding(env, carol, USD(100))); // Carol pays 25% transfer fee: 1000 - 100(offer) - 25(transfer fee) - BEAST_EXPECT(expectLine(env, carol, EUR(875))); + BEAST_EXPECT(expectHolding(env, carol, EUR(875))); BEAST_EXPECT(expectOffers(env, carol, 0)); BEAST_EXPECT(expectOffers(env, bob, 0)); } @@ -1170,7 +1170,7 @@ private: BEAST_EXPECT(ammBob.expectBalances( XRP(10'100), USD_bob(10'000), ammBob.tokens())); BEAST_EXPECT(expectOffers(env, alice, 0)); - BEAST_EXPECT(expectLine(env, alice, USD_bob(100))); + BEAST_EXPECT(expectHolding(env, alice, USD_bob(100))); } void @@ -1206,19 +1206,19 @@ private: env.close(); env(pay(dan, bob, D_BUX(100))); env.close(); - BEAST_EXPECT(expectLine(env, bob, D_BUX(100))); + BEAST_EXPECT(expectHolding(env, bob, D_BUX(100))); env(pay(ann, cam, D_BUX(60)), path(bob, dan), sendmax(A_BUX(200))); env.close(); - BEAST_EXPECT(expectLine(env, ann, A_BUX(none))); - BEAST_EXPECT(expectLine(env, ann, D_BUX(none))); - BEAST_EXPECT(expectLine(env, bob, A_BUX(72))); - BEAST_EXPECT(expectLine(env, bob, D_BUX(40))); - BEAST_EXPECT(expectLine(env, cam, A_BUX(none))); - BEAST_EXPECT(expectLine(env, cam, D_BUX(60))); - BEAST_EXPECT(expectLine(env, dan, A_BUX(none))); - BEAST_EXPECT(expectLine(env, dan, D_BUX(none))); + BEAST_EXPECT(expectHolding(env, ann, A_BUX(none))); + BEAST_EXPECT(expectHolding(env, ann, D_BUX(none))); + BEAST_EXPECT(expectHolding(env, bob, A_BUX(72))); + BEAST_EXPECT(expectHolding(env, bob, D_BUX(40))); + BEAST_EXPECT(expectHolding(env, cam, A_BUX(none))); + BEAST_EXPECT(expectHolding(env, cam, D_BUX(60))); + BEAST_EXPECT(expectHolding(env, dan, A_BUX(none))); + BEAST_EXPECT(expectHolding(env, dan, D_BUX(none))); AMM ammBob(env, bob, A_BUX(30), D_BUX(30)); @@ -1234,12 +1234,12 @@ private: BEAST_EXPECT( ammBob.expectBalances(A_BUX(30), D_BUX(30), ammBob.tokens())); - BEAST_EXPECT(expectLine(env, ann, A_BUX(none))); - BEAST_EXPECT(expectLine(env, ann, D_BUX(0))); - BEAST_EXPECT(expectLine(env, cam, A_BUX(none))); - BEAST_EXPECT(expectLine(env, cam, D_BUX(60))); - BEAST_EXPECT(expectLine(env, dan, A_BUX(0))); - BEAST_EXPECT(expectLine(env, dan, D_BUX(none))); + BEAST_EXPECT(expectHolding(env, ann, A_BUX(none))); + BEAST_EXPECT(expectHolding(env, ann, D_BUX(0))); + BEAST_EXPECT(expectHolding(env, cam, A_BUX(none))); + BEAST_EXPECT(expectHolding(env, cam, D_BUX(60))); + BEAST_EXPECT(expectHolding(env, dan, A_BUX(0))); + BEAST_EXPECT(expectHolding(env, dan, D_BUX(none))); } } @@ -1363,7 +1363,7 @@ private: env(pay(gw, bob, USD(50))); env.close(); - BEAST_EXPECT(expectLine(env, bob, USD(50))); + BEAST_EXPECT(expectHolding(env, bob, USD(50))); // Bob's offer should cross Alice's AMM env(offer(bob, XRP(50), USD(50))); @@ -1372,7 +1372,7 @@ private: BEAST_EXPECT( ammAlice.expectBalances(USD(1'050), XRP(1'000), ammAlice.tokens())); BEAST_EXPECT(expectOffers(env, bob, 0)); - BEAST_EXPECT(expectLine(env, bob, USD(0))); + BEAST_EXPECT(expectHolding(env, bob, USD(0))); } void @@ -1403,7 +1403,7 @@ private: env(pay(gw, bob, USD(50))); env.close(); - BEAST_EXPECT(expectLine(env, bob, USD(50))); + BEAST_EXPECT(expectHolding(env, bob, USD(50))); // Alice should not be able to create AMM without authorization. { @@ -1440,7 +1440,7 @@ private: BEAST_EXPECT( ammAlice.expectBalances(USD(1'050), XRP(1'000), ammAlice.tokens())); BEAST_EXPECT(expectOffers(env, bob, 0)); - BEAST_EXPECT(expectLine(env, bob, USD(0))); + BEAST_EXPECT(expectHolding(env, bob, USD(0))); } void @@ -1535,7 +1535,7 @@ private: // AMM offer is 51.282052XRP/11AUD, 11AUD/1.1 = 10AUD to bob BEAST_EXPECT( ammCarol.expectBalances(XRP(51), AUD(40), ammCarol.tokens())); - BEAST_EXPECT(expectLine(env, bob, AUD(10))); + BEAST_EXPECT(expectHolding(env, bob, AUD(10))); auto const result = find_paths(env, alice, bob, Account(bob)["USD"](25)); @@ -1950,10 +1950,10 @@ private: env(pay(alice, carol, USD(50)), path(~USD), sendmax(BTC(50))); - BEAST_EXPECT(expectLine(env, alice, BTC(50))); - BEAST_EXPECT(expectLine(env, bob, BTC(0))); - BEAST_EXPECT(expectLine(env, bob, USD(0))); - BEAST_EXPECT(expectLine(env, carol, USD(200))); + BEAST_EXPECT(expectHolding(env, alice, BTC(50))); + BEAST_EXPECT(expectHolding(env, bob, BTC(0))); + BEAST_EXPECT(expectHolding(env, bob, USD(0))); + BEAST_EXPECT(expectHolding(env, carol, USD(200))); BEAST_EXPECT( ammBob.expectBalances(BTC(150), USD(100), ammBob.tokens())); } @@ -1974,10 +1974,10 @@ private: env(pay(alice, carol, USD(50)), path(~XRP, ~USD), sendmax(BTC(50))); - BEAST_EXPECT(expectLine(env, alice, BTC(50))); - BEAST_EXPECT(expectLine(env, bob, BTC(0))); - BEAST_EXPECT(expectLine(env, bob, USD(0))); - BEAST_EXPECT(expectLine(env, carol, USD(200))); + BEAST_EXPECT(expectHolding(env, alice, BTC(50))); + BEAST_EXPECT(expectHolding(env, bob, BTC(0))); + BEAST_EXPECT(expectHolding(env, bob, USD(0))); + BEAST_EXPECT(expectHolding(env, carol, USD(200))); BEAST_EXPECT(ammBobBTC_XRP.expectBalances( BTC(150), XRP(100), ammBobBTC_XRP.tokens())); BEAST_EXPECT(ammBobXRP_USD.expectBalances( @@ -2003,8 +2003,8 @@ private: env, alice, xrpMinusFee(env, 10'000 - 50))); BEAST_EXPECT(expectLedgerEntryRoot( env, bob, XRP(10'000) - XRP(100) - ammCrtFee(env))); - BEAST_EXPECT(expectLine(env, bob, USD(0))); - BEAST_EXPECT(expectLine(env, carol, USD(200))); + BEAST_EXPECT(expectHolding(env, bob, USD(0))); + BEAST_EXPECT(expectHolding(env, carol, USD(200))); BEAST_EXPECT( ammBob.expectBalances(XRP(150), USD(100), ammBob.tokens())); } @@ -2024,10 +2024,10 @@ private: env(pay(alice, carol, XRP(50)), path(~XRP), sendmax(USD(50))); - BEAST_EXPECT(expectLine(env, alice, USD(50))); + BEAST_EXPECT(expectHolding(env, alice, USD(50))); BEAST_EXPECT(expectLedgerEntryRoot( env, bob, XRP(10'000) - XRP(150) - ammCrtFee(env))); - BEAST_EXPECT(expectLine(env, bob, USD(0))); + BEAST_EXPECT(expectHolding(env, bob, USD(0))); BEAST_EXPECT(expectLedgerEntryRoot(env, carol, XRP(10'000 + 50))); BEAST_EXPECT( ammBob.expectBalances(USD(150), XRP(100), ammBob.tokens())); @@ -2209,7 +2209,7 @@ private: sendmax(USD(0.4)), txflags(tfNoRippleDirect | tfPartialPayment)); - BEAST_EXPECT(expectLine(env, carol, EUR(1))); + BEAST_EXPECT(expectHolding(env, carol, EUR(1))); BEAST_EXPECT(ammBob.expectBalances( USD(8.4), XRPAmount{20}, ammBob.tokens())); } @@ -2244,7 +2244,7 @@ private: // alice buys 107.1428USD with 120GBP and pays 25% tr fee on 120GBP // 1,000 - 120*1.25 = 850GBP - BEAST_EXPECT(expectLine(env, alice, GBP(850))); + BEAST_EXPECT(expectHolding(env, alice, GBP(850))); if (!features[fixAMMv1_1]) { // 120GBP is swapped in for 107.1428USD @@ -2262,7 +2262,7 @@ private: } // 25% of 85.7142USD is paid in tr fee // 85.7142*1.25 = 107.1428USD - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount(USD, UINT64_C(1'085'714285714286), -12))); } @@ -2294,10 +2294,10 @@ private: // alice buys 120EUR with 120GBP via the offer // and pays 25% tr fee on 120GBP // 1,000 - 120*1.25 = 850GBP - BEAST_EXPECT(expectLine(env, alice, GBP(850))); + BEAST_EXPECT(expectHolding(env, alice, GBP(850))); // consumed offer is 120GBP/120EUR // ed doesn't pay tr fee - BEAST_EXPECT(expectLine(env, ed, EUR(880), GBP(1'120))); + BEAST_EXPECT(expectHolding(env, ed, EUR(880), GBP(1'120))); BEAST_EXPECT( expectOffers(env, ed, 1, {Amounts{GBP(880), EUR(880)}})); // 25% on 96EUR is paid in tr fee 96*1.25 = 120EUR @@ -2307,7 +2307,7 @@ private: STAmount{USD, UINT64_C(912'4087591240876), -13}, amm.tokens())); // 25% on 70.0729USD is paid in tr fee 70.0729*1.25 = 87.5912USD - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount(USD, UINT64_C(1'070'07299270073), -11))); } { @@ -2333,7 +2333,7 @@ private: txflags(tfNoRippleDirect | tfPartialPayment)); env.close(); - BEAST_EXPECT(expectLine(env, alice, GBP(850))); + BEAST_EXPECT(expectHolding(env, alice, GBP(850))); if (!features[fixAMMv1_1]) { // alice buys 107.1428EUR with 120GBP and pays 25% tr fee on @@ -2367,7 +2367,7 @@ private: amm2.tokens())); } // 25% on 63.1578USD is paid in tr fee 63.1578*1.25 = 78.9473USD - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount(USD, UINT64_C(1'063'157894736842), -12))); } { @@ -2386,7 +2386,7 @@ private: BEAST_EXPECT( amm.expectBalances(USD(1'100), EUR(1'000), amm.tokens())); // alice pays 25% tr fee on 100USD 1100-100*1.25 = 975USD - BEAST_EXPECT(expectLine(env, alice, USD(975), EUR(1'200))); + BEAST_EXPECT(expectHolding(env, alice, USD(975), EUR(1'200))); BEAST_EXPECT(expectOffers(env, alice, 0)); } @@ -2416,7 +2416,7 @@ private: // alice buys 125USD with 142.8571GBP and pays 25% tr fee // on 142.8571GBP // 1,000 - 142.8571*1.25 = 821.4285GBP - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, alice, STAmount(GBP, UINT64_C(821'4285714285712), -13))); // 142.8571GBP is swapped in for 125USD BEAST_EXPECT(amm.expectBalances( @@ -2425,7 +2425,7 @@ private: amm.tokens())); // 25% on 100USD is paid in tr fee // 100*1.25 = 125USD - BEAST_EXPECT(expectLine(env, carol, USD(1'100))); + BEAST_EXPECT(expectHolding(env, carol, USD(1'100))); } { // Payment via AMM with limit quality, deliver less @@ -2456,7 +2456,7 @@ private: // alice buys 28.125USD with 24GBP and pays 25% tr fee // on 24GBP // 1,200 - 24*1.25 = 1,170GBP - BEAST_EXPECT(expectLine(env, alice, GBP(1'170))); + BEAST_EXPECT(expectHolding(env, alice, GBP(1'170))); // 24GBP is swapped in for 28.125USD BEAST_EXPECT(amm.expectBalances( GBP(1'024), USD(1'171.875), amm.tokens())); @@ -2466,7 +2466,7 @@ private: // alice buys 28.125USD with 24GBP and pays 25% tr fee // on 24GBP // 1,200 - 24*1.25 =~ 1,170GBP - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, alice, STAmount{GBP, UINT64_C(1'169'999999999999), -12})); @@ -2478,7 +2478,7 @@ private: } // 25% on 22.5USD is paid in tr fee // 22.5*1.25 = 28.125USD - BEAST_EXPECT(expectLine(env, carol, USD(1'222.5))); + BEAST_EXPECT(expectHolding(env, carol, USD(1'222.5))); } { // Payment via offer and AMM with limit quality, deliver less @@ -2513,13 +2513,13 @@ private: // alice buys 70.4210EUR with 70.4210GBP via the offer // and pays 25% tr fee on 70.4210GBP // 1,400 - 70.4210*1.25 = 1400 - 88.0262 = 1311.9736GBP - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, alice, STAmount{GBP, UINT64_C(1'311'973684210527), -12})); // ed doesn't pay tr fee, the balances reflect consumed offer // 70.4210GBP/70.4210EUR - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, ed, STAmount{EUR, UINT64_C(1'329'578947368421), -12}, @@ -2543,13 +2543,13 @@ private: // alice buys 70.4210EUR with 70.4210GBP via the offer // and pays 25% tr fee on 70.4210GBP // 1,400 - 70.4210*1.25 = 1400 - 88.0262 = 1311.9736GBP - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, alice, STAmount{GBP, UINT64_C(1'311'973684210525), -12})); // ed doesn't pay tr fee, the balances reflect consumed offer // 70.4210GBP/70.4210EUR - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, ed, STAmount{EUR, UINT64_C(1'329'57894736842), -11}, @@ -2569,7 +2569,7 @@ private: amm.tokens())); } // 25% on 59.7321USD is paid in tr fee 59.7321*1.25 = 74.6651USD - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount(USD, UINT64_C(1'459'732142857143), -12))); } { @@ -2605,7 +2605,7 @@ private: // alice buys 53.3322EUR with 56.3368GBP via the amm // and pays 25% tr fee on 56.3368GBP // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, alice, STAmount{GBP, UINT64_C(1'329'578947368421), -12})); @@ -2622,7 +2622,7 @@ private: // alice buys 53.3322EUR with 56.3368GBP via the amm // and pays 25% tr fee on 56.3368GBP // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, alice, STAmount{GBP, UINT64_C(1'329'57894736842), -11})); @@ -2636,7 +2636,7 @@ private: } // 25% on 42.6658EUR is paid in tr fee 42.6658*1.25 = 53.3322EUR // 42.6658EUR/59.7321USD - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, ed, STAmount{USD, UINT64_C(1'340'267857142857), -12}, @@ -2649,7 +2649,7 @@ private: STAmount{EUR, UINT64_C(957'3341836734693), -13}, STAmount{USD, UINT64_C(1'340'267857142857), -12}}})); // 25% on 47.7857USD is paid in tr fee 47.7857*1.25 = 59.7321USD - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount(USD, UINT64_C(1'447'785714285714), -12))); } { @@ -2683,7 +2683,7 @@ private: // alice buys 53.3322EUR with 107.5308GBP // 25% on 86.0246GBP is paid in tr fee // 1,400 - 86.0246*1.25 = 1400 - 107.5308 = 1229.4691GBP - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, alice, STAmount{GBP, UINT64_C(1'292'469135802469), -12})); @@ -2704,7 +2704,7 @@ private: // alice buys 53.3322EUR with 107.5308GBP // 25% on 86.0246GBP is paid in tr fee // 1,400 - 86.0246*1.25 = 1400 - 107.5308 = 1229.4691GBP - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, alice, STAmount{GBP, UINT64_C(1'292'469135802466), -12})); @@ -2721,7 +2721,7 @@ private: amm2.tokens())); } // 25% on 66.7432USD is paid in tr fee 66.7432*1.25 = 83.4291USD - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount(USD, UINT64_C(1'466'743295019157), -12))); } { @@ -2778,7 +2778,7 @@ private: amm2.tokens())); } // 25% on 81.1111USD is paid in tr fee 81.1111*1.25 = 101.3888USD - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount{USD, UINT64_C(1'481'111111111111), -12})); } } @@ -2808,7 +2808,7 @@ private: BEAST_EXPECT( ammBob.expectBalances(XRP(1'050), USD(1'000), ammBob.tokens())); - BEAST_EXPECT(expectLine(env, carol, USD(2'050))); + BEAST_EXPECT(expectHolding(env, carol, USD(2'050))); BEAST_EXPECT(expectOffers(env, bob, 1, {{{XRP(100), USD(50)}}})); } } diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index c89aebf813..cfe1ffab16 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -97,8 +97,8 @@ private: AMM ammAlice(env, alice, USD(20'000), BTC(0.5)); BEAST_EXPECT(ammAlice.expectBalances( USD(20'000), BTC(0.5), IOUAmount{100, 0})); - BEAST_EXPECT(expectLine(env, alice, USD(0))); - BEAST_EXPECT(expectLine(env, alice, BTC(0))); + BEAST_EXPECT(expectHolding(env, alice, USD(0))); + BEAST_EXPECT(expectHolding(env, alice, BTC(0))); } // Require authorization is set, account is authorized @@ -1394,7 +1394,7 @@ private: BEAST_EXPECT(ammAlice.expectBalances( XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0})); // 30,000 less deposited 1,000 - BEAST_EXPECT(expectLine(env, carol, USD(29'000))); + BEAST_EXPECT(expectHolding(env, carol, USD(29'000))); // 30,000 less deposited 1,000 and 10 drops tx fee BEAST_EXPECT(expectLedgerEntryRoot( env, carol, XRPAmount{29'000'000'000 - baseFee})); @@ -1449,7 +1449,8 @@ private: IOUAmount{1, 7} + newLPTokens)); // 30,000 less deposited depositUSD - BEAST_EXPECT(expectLine(env, carol, USD(30'000) - depositUSD)); + BEAST_EXPECT( + expectHolding(env, carol, USD(30'000) - depositUSD)); // 30,000 less deposited depositXRP and 10 drops tx fee BEAST_EXPECT(expectLedgerEntryRoot( env, carol, XRP(30'000) - depositXRP - txfee(env, 1))); @@ -1553,15 +1554,15 @@ private: AMM ammAlice(env, alice, USD(20'000), BTC(0.5)); BEAST_EXPECT(ammAlice.expectBalances( USD(20'000), BTC(0.5), IOUAmount{100, 0})); - BEAST_EXPECT(expectLine(env, alice, USD(0))); - BEAST_EXPECT(expectLine(env, alice, BTC(0))); + BEAST_EXPECT(expectHolding(env, alice, USD(0))); + BEAST_EXPECT(expectHolding(env, alice, BTC(0))); fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct); // no transfer fee on deposit ammAlice.deposit(carol, 10); BEAST_EXPECT(ammAlice.expectBalances( USD(22'000), BTC(0.55), IOUAmount{110, 0})); - BEAST_EXPECT(expectLine(env, carol, USD(0))); - BEAST_EXPECT(expectLine(env, carol, BTC(0))); + BEAST_EXPECT(expectHolding(env, carol, USD(0))); + BEAST_EXPECT(expectHolding(env, carol, BTC(0))); } // Tiny deposits @@ -2281,7 +2282,7 @@ private: BEAST_EXPECT( ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0})); // 30,000 less deposited 1,000 - BEAST_EXPECT(expectLine(env, carol, USD(29'000))); + BEAST_EXPECT(expectHolding(env, carol, USD(29'000))); // 30,000 less deposited 1,000 and 10 drops tx fee BEAST_EXPECT(expectLedgerEntryRoot( env, carol, XRPAmount{29'000'000'000 - baseFee})); @@ -2290,7 +2291,7 @@ private: ammAlice.withdraw(carol, 1'000'000); BEAST_EXPECT( ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero()))); - BEAST_EXPECT(expectLine(env, carol, USD(30'000))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'000))); BEAST_EXPECT(expectLedgerEntryRoot( env, carol, XRPAmount{30'000'000'000 - 2 * baseFee})); }); @@ -2525,22 +2526,22 @@ private: AMM ammAlice(env, alice, USD(20'000), BTC(0.5)); BEAST_EXPECT(ammAlice.expectBalances( USD(20'000), BTC(0.5), IOUAmount{100, 0})); - BEAST_EXPECT(expectLine(env, alice, USD(0))); - BEAST_EXPECT(expectLine(env, alice, BTC(0))); + BEAST_EXPECT(expectHolding(env, alice, USD(0))); + BEAST_EXPECT(expectHolding(env, alice, BTC(0))); fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct); // no transfer fee on deposit ammAlice.deposit(carol, 10); BEAST_EXPECT(ammAlice.expectBalances( USD(22'000), BTC(0.55), IOUAmount{110, 0})); - BEAST_EXPECT(expectLine(env, carol, USD(0))); - BEAST_EXPECT(expectLine(env, carol, BTC(0))); + BEAST_EXPECT(expectHolding(env, carol, USD(0))); + BEAST_EXPECT(expectHolding(env, carol, BTC(0))); // no transfer fee on withdraw ammAlice.withdraw(carol, 10); BEAST_EXPECT(ammAlice.expectBalances( USD(20'000), BTC(0.5), IOUAmount{100, 0})); BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0})); - BEAST_EXPECT(expectLine(env, carol, USD(2'000))); - BEAST_EXPECT(expectLine(env, carol, BTC(0.05))); + BEAST_EXPECT(expectHolding(env, carol, USD(2'000))); + BEAST_EXPECT(expectHolding(env, carol, BTC(0.05))); } // Tiny withdraw @@ -3527,7 +3528,7 @@ private: // Alice doesn't have anymore lp tokens env(amm.bid({.account = alice, .bidMin = 500})); BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{500})); - BEAST_EXPECT(expectLine(env, alice, STAmount{lpIssue, 0})); + BEAST_EXPECT(expectHolding(env, alice, STAmount{lpIssue, 0})); // But trades with the discounted fee since she still owns the slot. // Alice pays 10011 drops in fees env(pay(alice, bob, USD(10)), path(~USD), sendmax(XRP(11))); @@ -3790,7 +3791,7 @@ private: BEAST_EXPECT(ammAlice.expectBalances( XRP(10'100), USD(10'000), ammAlice.tokens())); // Initial balance 30,000 + 100 - BEAST_EXPECT(expectLine(env, carol, USD(30'100))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'100))); // Initial balance 30,000 - 100(sendmax) - 10(tx fee) BEAST_EXPECT(expectLedgerEntryRoot( env, bob, XRP(30'000) - XRP(100) - txfee(env, 1))); @@ -3810,7 +3811,7 @@ private: BEAST_EXPECT(ammAlice.expectBalances( XRP(10'100), USD(10'000), ammAlice.tokens())); // Initial balance 30,000 + 100 - BEAST_EXPECT(expectLine(env, carol, USD(30'100))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'100))); // Initial balance 30,000 - 100(sendmax) - 10(tx fee) BEAST_EXPECT(expectLedgerEntryRoot( env, bob, XRP(30'000) - XRP(100) - txfee(env, 1))); @@ -3831,7 +3832,7 @@ private: BEAST_EXPECT(ammAlice.expectBalances( XRP(10'100), USD(10'000), ammAlice.tokens())); // Initial balance 30,000 + 100 - BEAST_EXPECT(expectLine(env, carol, USD(30'100))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'100))); // Initial balance 30,000 - 100(sendmax) - 10(tx fee) BEAST_EXPECT(expectLedgerEntryRoot( env, bob, XRP(30'000) - XRP(100) - txfee(env, 1))); @@ -3857,7 +3858,7 @@ private: BEAST_EXPECT(ammAlice.expectBalances( XRP(10'010), USD(10'000), ammAlice.tokens())); // Initial balance 30,000 + 10(limited by limitQuality) - BEAST_EXPECT(expectLine(env, carol, USD(30'010))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'010))); // Initial balance 30,000 - 10(limited by limitQuality) - 10(tx // fee) BEAST_EXPECT(expectLedgerEntryRoot( @@ -3897,7 +3898,7 @@ private: BEAST_EXPECT(ammAlice.expectBalances( XRP(10'010), USD(10'000), ammAlice.tokens())); // 10USD - 10% transfer fee - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount{USD, UINT64_C(30'009'09090909091), -11})); @@ -3984,7 +3985,7 @@ private: BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}})); } // Initial 30,000 + 100 - BEAST_EXPECT(expectLine(env, carol, STAmount{USD, 30'100})); + BEAST_EXPECT(expectHolding(env, carol, STAmount{USD, 30'100})); // Initial 1,000 - 30082730(AMM pool) - 70798251(offer) - 10(tx fee) BEAST_EXPECT(expectLedgerEntryRoot( env, @@ -4027,7 +4028,7 @@ private: STAmount(EUR, UINT64_C(49'98750312422), -11), STAmount(USD, UINT64_C(49'98750312422), -11)}}})); // Initial 30,000 + 99.99999999999 - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount{USD, UINT64_C(30'099'99999999999), -11})); @@ -4061,7 +4062,7 @@ private: BEAST_EXPECT(ammAlice.expectBalances( XRP(10'100), USD(10'000), ammAlice.tokens())); // Initial 30,000 + 200 - BEAST_EXPECT(expectLine(env, carol, USD(30'200))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'200))); } else { @@ -4069,7 +4070,7 @@ private: XRP(10'100), STAmount(USD, UINT64_C(10'000'00000000001), -11), ammAlice.tokens())); - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount(USD, UINT64_C(30'199'99999999999), -11))); @@ -4104,7 +4105,7 @@ private: env.close(); BEAST_EXPECT(ammAlice.expectBalances( XRP(1'050), USD(1'000), ammAlice.tokens())); - BEAST_EXPECT(expectLine(env, carol, USD(2'200))); + BEAST_EXPECT(expectHolding(env, carol, USD(2'200))); BEAST_EXPECT(expectOffers(env, bob, 0)); } @@ -4118,7 +4119,7 @@ private: BEAST_EXPECT(ammAlice.expectBalances( XRP(10'100), USD(10'000), ammAlice.tokens())); // Initial 1,000 + 100 - BEAST_EXPECT(expectLine(env, bob, USD(1'100))); + BEAST_EXPECT(expectHolding(env, bob, USD(1'100))); // Initial 30,000 - 100(offer) - 10(tx fee) BEAST_EXPECT(expectLedgerEntryRoot( env, bob, XRP(30'000) - XRP(100) - txfee(env, 1))); @@ -4145,9 +4146,9 @@ private: BEAST_EXPECT(ammAlice.expectBalances( GBP(1'100), EUR(1'000), ammAlice.tokens())); // Initial 30,000 - 100(offer) - 25% transfer fee - BEAST_EXPECT(expectLine(env, carol, GBP(29'875))); + BEAST_EXPECT(expectHolding(env, carol, GBP(29'875))); // Initial 30,000 + 100(offer) - BEAST_EXPECT(expectLine(env, carol, EUR(30'100))); + BEAST_EXPECT(expectHolding(env, carol, EUR(30'100))); BEAST_EXPECT(expectOffers(env, bob, 0)); }, {{GBP(1'000), EUR(1'100)}}, @@ -4285,12 +4286,12 @@ private: // = 58.825 = ~29941.17 // carol bought ~72.93EUR at the cost of ~70.68GBP // the offer is partially consumed - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount{GBP, UINT64_C(29'941'16770347333), -11})); // Initial 30,000 + ~49.3(offers = 39.3(AMM) + 10(LOB)) - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount{EUR, UINT64_C(30'049'31517120716), -11})); @@ -4324,20 +4325,20 @@ private: // = 88.35 = ~29911.64 // carol bought ~72.93EUR at the cost of ~70.68GBP // the offer is partially consumed - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount{GBP, UINT64_C(29'911'64396400896), -11})); // Initial 30,000 + ~72.93(offers = 62.93(AMM) + 10(LOB)) - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount{EUR, UINT64_C(30'072'93416277865), -11})); } // Initial 2000 + 10 = 2010 - BEAST_EXPECT(expectLine(env, bob, GBP(2'010))); + BEAST_EXPECT(expectHolding(env, bob, GBP(2'010))); // Initial 2000 - 10 * 1.25 = 1987.5 - BEAST_EXPECT(expectLine(env, ed, EUR(1'987.5))); + BEAST_EXPECT(expectHolding(env, ed, EUR(1'987.5))); }, {{GBP(1'000), EUR(1'100)}}, 0, @@ -4363,8 +4364,8 @@ private: env.close(); BEAST_EXPECT(ammAlice.expectBalances( GBP(1'100), EUR(1'000), ammAlice.tokens())); - BEAST_EXPECT(expectLine(env, bob, GBP(75))); - BEAST_EXPECT(expectLine(env, carol, EUR(30'080))); + BEAST_EXPECT(expectHolding(env, bob, GBP(75))); + BEAST_EXPECT(expectHolding(env, carol, EUR(30'080))); }, {{GBP(1'000), EUR(1'100)}}, 0, @@ -4401,12 +4402,12 @@ private: sendmax(CAN(195.3125)), txflags(tfPartialPayment)); env.close(); - BEAST_EXPECT(expectLine(env, bob, CAN(0))); - BEAST_EXPECT(expectLine(env, dan, CAN(356.25), GBP(43.75))); + BEAST_EXPECT(expectHolding(env, bob, CAN(0))); + BEAST_EXPECT(expectHolding(env, dan, CAN(356.25), GBP(43.75))); BEAST_EXPECT(ammAlice.expectBalances( GBP(10'125), EUR(10'000), ammAlice.tokens())); - BEAST_EXPECT(expectLine(env, ed, EUR(300), USD(100))); - BEAST_EXPECT(expectLine(env, carol, USD(80))); + BEAST_EXPECT(expectHolding(env, ed, EUR(300), USD(100))); + BEAST_EXPECT(expectHolding(env, carol, USD(80))); }, {{GBP(10'000), EUR(10'125)}}, 0, @@ -4523,7 +4524,7 @@ private: BEAST_EXPECT(btc_usd.expectBalances( BTC(10'100), USD(10'000), btc_usd.tokens())); - BEAST_EXPECT(expectLine(env, carol, USD(300))); + BEAST_EXPECT(expectHolding(env, carol, USD(300))); } // Dependent AMM @@ -4594,7 +4595,7 @@ private: STAmount{EUR, UINT64_C(10'917'2945958102), -10}, eth_eur.tokens())); } - BEAST_EXPECT(expectLine(env, carol, USD(300))); + BEAST_EXPECT(expectHolding(env, carol, USD(300))); } // AMM offers limit @@ -4620,7 +4621,7 @@ private: XRP(10'030), STAmount{USD, UINT64_C(9'970'089730807577), -12}, ammAlice.tokens())); - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount{USD, UINT64_C(30'029'91026919241), -11})); @@ -4631,7 +4632,7 @@ private: XRP(10'030), STAmount{USD, UINT64_C(9'970'089730807827), -12}, ammAlice.tokens())); - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount{USD, UINT64_C(30'029'91026919217), -11})); @@ -4663,14 +4664,14 @@ private: if (!features[fixAMMv1_1]) { // Carol gets ~100USD - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount{USD, UINT64_C(30'099'99999999999), -11})); } else { - BEAST_EXPECT(expectLine(env, carol, USD(30'100))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'100))); } BEAST_EXPECT(expectOffers( env, @@ -4717,7 +4718,7 @@ private: 1, {{{XRPAmount{50'074'628}, STAmount{USD, UINT64_C(50'07512950697), -11}}}})); - BEAST_EXPECT(expectLine(env, carol, USD(30'100))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'100))); } } @@ -4809,11 +4810,11 @@ private: env(offer(carol, STAmount{token2, 100}, STAmount{token1, 100})); env.close(); BEAST_EXPECT( - expectLine(env, alice, STAmount{token1, 10'000'100}) && - expectLine(env, alice, STAmount{token2, 9'999'900})); + expectHolding(env, alice, STAmount{token1, 10'000'100}) && + expectHolding(env, alice, STAmount{token2, 9'999'900})); BEAST_EXPECT( - expectLine(env, carol, STAmount{token2, 1'000'100}) && - expectLine(env, carol, STAmount{token1, 999'900})); + expectHolding(env, carol, STAmount{token2, 1'000'100}) && + expectHolding(env, carol, STAmount{token1, 999'900})); BEAST_EXPECT( expectOffers(env, alice, 0) && expectOffers(env, carol, 0)); }); @@ -5034,7 +5035,7 @@ private: BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000})); ammAlice.withdrawAll(carol, USD(3'000)); BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0})); - BEAST_EXPECT(expectLine(env, carol, USD(30'000))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'000))); // Set fee to 1% ammAlice.vote(alice, 1'000); BEAST_EXPECT(ammAlice.expectTradingFee(1'000)); @@ -5043,12 +5044,12 @@ private: ammAlice.deposit(carol, USD(3'000)); BEAST_EXPECT(ammAlice.expectLPTokens( carol, IOUAmount{994'981155689671, -12})); - BEAST_EXPECT(expectLine(env, carol, USD(27'000))); + BEAST_EXPECT(expectHolding(env, carol, USD(27'000))); // Set fee to 0 ammAlice.vote(alice, 0); ammAlice.withdrawAll(carol, USD(0)); // Carol gets back less than the original deposit - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount{USD, UINT64_C(29'994'96220068281), -11})); @@ -5109,13 +5110,13 @@ private: ammAlice.deposit(carol, USD(3'000)); BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000})); - BEAST_EXPECT(expectLine(env, carol, USD(27'000))); + BEAST_EXPECT(expectHolding(env, carol, USD(27'000))); // Set fee to 1% ammAlice.vote(alice, 1'000); BEAST_EXPECT(ammAlice.expectTradingFee(1'000)); // Single withdrawal. Carol gets ~5USD less than deposited. ammAlice.withdrawAll(carol, USD(0)); - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount{USD, UINT64_C(29'994'97487437186), -11})); @@ -5189,9 +5190,9 @@ private: {USD(1'000), EUR(1'000)}, Fund::Acct); // Alice contributed 1010EUR and 1000USD to the pool - BEAST_EXPECT(expectLine(env, alice, EUR(28'990))); - BEAST_EXPECT(expectLine(env, alice, USD(29'000))); - BEAST_EXPECT(expectLine(env, carol, USD(30'000))); + BEAST_EXPECT(expectHolding(env, alice, EUR(28'990))); + BEAST_EXPECT(expectHolding(env, alice, USD(29'000))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'000))); // Carol pays to Alice with no fee env(pay(carol, alice, EUR(10)), path(~EUR), @@ -5199,9 +5200,9 @@ private: txflags(tfNoRippleDirect)); env.close(); // Alice has 10EUR more and Carol has 10USD less - BEAST_EXPECT(expectLine(env, alice, EUR(29'000))); - BEAST_EXPECT(expectLine(env, alice, USD(29'000))); - BEAST_EXPECT(expectLine(env, carol, USD(29'990))); + BEAST_EXPECT(expectHolding(env, alice, EUR(29'000))); + BEAST_EXPECT(expectHolding(env, alice, USD(29'000))); + BEAST_EXPECT(expectHolding(env, carol, USD(29'990))); // Set fee to 1% ammAlice.vote(alice, 1'000); @@ -5213,10 +5214,10 @@ private: txflags(tfNoRippleDirect)); env.close(); // Bob sends 10.1~EUR to pay 10USD - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, bob, STAmount{EUR, UINT64_C(989'8989898989899), -13})); // Carol got 10USD - BEAST_EXPECT(expectLine(env, carol, USD(30'000))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'000))); BEAST_EXPECT(ammAlice.expectBalances( USD(1'000), STAmount{EUR, UINT64_C(1'010'10101010101), -11}, @@ -5233,8 +5234,8 @@ private: // No fee env(offer(carol, EUR(10), USD(10))); env.close(); - BEAST_EXPECT(expectLine(env, carol, USD(29'990))); - BEAST_EXPECT(expectLine(env, carol, EUR(30'010))); + BEAST_EXPECT(expectHolding(env, carol, USD(29'990))); + BEAST_EXPECT(expectHolding(env, carol, EUR(30'010))); // Change pool composition back env(offer(carol, USD(10), EUR(10))); env.close(); @@ -5245,11 +5246,11 @@ private: env.close(); // Alice gets fewer ~4.97EUR for ~5.02USD, the difference goes // to the pool - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount{USD, UINT64_C(29'995'02512562814), -11})); - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount{EUR, UINT64_C(30'004'97487437186), -11})); @@ -5299,16 +5300,16 @@ private: path(~USD), sendmax(EUR(15)), txflags(tfNoRippleDirect)); - BEAST_EXPECT(expectLine(env, ed, USD(2'010))); + BEAST_EXPECT(expectHolding(env, ed, USD(2'010))); if (!features[fixAMMv1_1]) { - BEAST_EXPECT(expectLine(env, bob, EUR(1'990))); + BEAST_EXPECT(expectHolding(env, bob, EUR(1'990))); BEAST_EXPECT(ammAlice.expectBalances( USD(1'000), EUR(1'005), ammAlice.tokens())); } else { - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, bob, STAmount(EUR, UINT64_C(1989'999999999999), -12))); BEAST_EXPECT(ammAlice.expectBalances( USD(1'000), @@ -5336,10 +5337,10 @@ private: path(~USD), sendmax(EUR(15)), txflags(tfNoRippleDirect)); - BEAST_EXPECT(expectLine(env, ed, USD(2'010))); + BEAST_EXPECT(expectHolding(env, ed, USD(2'010))); if (!features[fixAMMv1_1]) { - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, bob, STAmount{EUR, UINT64_C(1'989'987453007618), -12})); @@ -5350,7 +5351,7 @@ private: } else { - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, bob, STAmount{EUR, UINT64_C(1'989'987453007628), -12})); @@ -5381,8 +5382,8 @@ private: path(~USD), sendmax(EUR(15)), txflags(tfNoRippleDirect)); - BEAST_EXPECT(expectLine(env, ed, USD(2'010))); - BEAST_EXPECT(expectLine(env, bob, EUR(1'990))); + BEAST_EXPECT(expectHolding(env, ed, USD(2'010))); + BEAST_EXPECT(expectHolding(env, bob, EUR(1'990))); BEAST_EXPECT(ammAlice.expectBalances( USD(1'005), EUR(1'000), ammAlice.tokens())); BEAST_EXPECT(expectOffers(env, carol, 0)); @@ -5408,8 +5409,8 @@ private: path(~USD), sendmax(EUR(15)), txflags(tfNoRippleDirect)); - BEAST_EXPECT(expectLine(env, ed, USD(2'010))); - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding(env, ed, USD(2'010))); + BEAST_EXPECT(expectHolding( env, bob, STAmount{EUR, UINT64_C(1'989'993923296712), -12})); BEAST_EXPECT(ammAlice.expectBalances( USD(1'004), @@ -5480,47 +5481,47 @@ private: else BEAST_EXPECT(ammAlice.expectBalances( XRP(10'000), USD(10'000), IOUAmount{10'000'000})); - BEAST_EXPECT(expectLine(env, ben, USD(1'500'000))); - BEAST_EXPECT(expectLine(env, simon, USD(1'500'000))); - BEAST_EXPECT(expectLine(env, chris, USD(1'500'000))); - BEAST_EXPECT(expectLine(env, dan, USD(1'500'000))); + BEAST_EXPECT(expectHolding(env, ben, USD(1'500'000))); + BEAST_EXPECT(expectHolding(env, simon, USD(1'500'000))); + BEAST_EXPECT(expectHolding(env, chris, USD(1'500'000))); + BEAST_EXPECT(expectHolding(env, dan, USD(1'500'000))); if (!features[fixAMMv1_1] && !features[fixAMMv1_3]) - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, carol, STAmount{USD, UINT64_C(30'000'00000000001), -11})); else if (features[fixAMMv1_1] && !features[fixAMMv1_3]) - BEAST_EXPECT(expectLine(env, carol, USD(30'000))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'000))); else - BEAST_EXPECT(expectLine(env, carol, USD(30'000))); - BEAST_EXPECT(expectLine(env, ed, USD(1'500'000))); - BEAST_EXPECT(expectLine(env, paul, USD(1'500'000))); + BEAST_EXPECT(expectHolding(env, carol, USD(30'000))); + BEAST_EXPECT(expectHolding(env, ed, USD(1'500'000))); + BEAST_EXPECT(expectHolding(env, paul, USD(1'500'000))); if (!features[fixAMMv1_1] && !features[fixAMMv1_3]) - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, nataly, STAmount{USD, UINT64_C(1'500'000'000000002), -9})); else if (features[fixAMMv1_1] && !features[fixAMMv1_3]) - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, nataly, STAmount{USD, UINT64_C(1'500'000'000000005), -9})); else - BEAST_EXPECT(expectLine(env, nataly, USD(1'500'000))); + BEAST_EXPECT(expectHolding(env, nataly, USD(1'500'000))); ammAlice.withdrawAll(alice); BEAST_EXPECT(!ammAlice.ammExists()); if (!features[fixAMMv1_1]) - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, alice, STAmount{USD, UINT64_C(30'000'0000000013), -10})); else if (features[fixAMMv1_3]) - BEAST_EXPECT(expectLine( + BEAST_EXPECT(expectHolding( env, alice, STAmount{USD, UINT64_C(30'000'0000000003), -10})); else - BEAST_EXPECT(expectLine(env, alice, USD(30'000))); + BEAST_EXPECT(expectHolding(env, alice, USD(30'000))); // alice XRP balance is 30,000initial - 50 ammcreate fee - // 10drops fee BEAST_EXPECT( @@ -5883,7 +5884,7 @@ private: BEAST_EXPECT(amm->expectBalances( USD(1'000), ETH(1'000), amm->tokens())); } - BEAST_EXPECT(expectLine(env, bob, USD(2'100))); + BEAST_EXPECT(expectHolding(env, bob, USD(2'100))); q[i] = Quality(Amounts{ ETH(2'000) - env.balance(carol, ETH), env.balance(bob, USD) - USD(2'000)}); @@ -6056,7 +6057,7 @@ private: -13}}}})); } } - BEAST_EXPECT(expectLine(env, bob, USD(2'100))); + BEAST_EXPECT(expectHolding(env, bob, USD(2'100))); q[i] = Quality(Amounts{ ETH(2'000) - env.balance(carol, ETH), env.balance(bob, USD) - USD(2'000)}); @@ -6203,7 +6204,7 @@ private: sendmax(ETH(600))); env.close(); - BEAST_EXPECT(expectLine(env, bob, USD(2'100))); + BEAST_EXPECT(expectHolding(env, bob, USD(2'100))); if (i == 2 && !features[fixAMMv1_1]) { @@ -7484,7 +7485,7 @@ private: using namespace test::jtx; auto const testCase = [&](std::string suffix, FeatureBitset features) { - testcase("Failed pseudo-account allocation " + suffix); + testcase("Fail pseudo-account allocation " + suffix); std::string logs; Env env{*this, features, std::make_unique(&logs)}; env.fund(XRP(30'000), gw, alice); diff --git a/src/test/app/Credentials_test.cpp b/src/test/app/Credentials_test.cpp index 23aa7ad952..102d516b89 100644 --- a/src/test/app/Credentials_test.cpp +++ b/src/test/app/Credentials_test.cpp @@ -33,15 +33,6 @@ namespace ripple { namespace test { -static inline bool -checkVL( - std::shared_ptr const& sle, - SField const& field, - std::string const& expected) -{ - return strHex(expected) == strHex(sle->getFieldVL(field)); -} - struct Credentials_test : public beast::unit_test::suite { void diff --git a/src/test/app/DID_test.cpp b/src/test/app/DID_test.cpp index 1f28af2d6a..21fb6b584e 100644 --- a/src/test/app/DID_test.cpp +++ b/src/test/app/DID_test.cpp @@ -27,14 +27,6 @@ namespace ripple { namespace test { -bool -checkVL(Slice const& result, std::string expected) -{ - Serializer s; - s.addRaw(result); - return s.getString() == expected; -} - struct DID_test : public beast::unit_test::suite { void diff --git a/src/test/app/EscrowToken_test.cpp b/src/test/app/EscrowToken_test.cpp index 9c1868134f..6caedb53f1 100644 --- a/src/test/app/EscrowToken_test.cpp +++ b/src/test/app/EscrowToken_test.cpp @@ -3426,7 +3426,7 @@ struct EscrowToken_test : public beast::unit_test::suite auto const preAliceMPT = env.balance(alice, MPT); auto const preOutstanding = env.balance(gw, MPT); auto const preEscrowed = issuerMPTEscrowed(env, MPT); - BEAST_EXPECT(preOutstanding == MPT(10'000)); + BEAST_EXPECT(preOutstanding == MPT(-10'000)); BEAST_EXPECT(preEscrowed == 0); env(escrow::create(alice, gw, MPT(1'000)), @@ -3449,7 +3449,7 @@ struct EscrowToken_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(alice, MPT) == preAliceMPT - MPT(1'000)); BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0); - BEAST_EXPECT(env.balance(gw, MPT) == preOutstanding - MPT(1'000)); + BEAST_EXPECT(env.balance(gw, MPT) == preOutstanding + MPT(1'000)); BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == preEscrowed); } } @@ -3503,7 +3503,7 @@ struct EscrowToken_test : public beast::unit_test::suite BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 125); BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 125); - BEAST_EXPECT(env.balance(gw, MPT) == MPT(20'000)); + BEAST_EXPECT(env.balance(gw, MPT) == MPT(-20'000)); // bob can finish escrow env(escrow::finish(bob, alice, seq1), @@ -3522,7 +3522,7 @@ struct EscrowToken_test : public beast::unit_test::suite : MPT(20'000); BEAST_EXPECT(mptEscrowed(env, alice, MPT) == escrowedWithFix); BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == escrowedWithFix); - BEAST_EXPECT(env.balance(gw, MPT) == outstandingWithFix); + BEAST_EXPECT(env.balance(gw, MPT) == -outstandingWithFix); } // test locked rate: cancel @@ -3567,7 +3567,7 @@ struct EscrowToken_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(alice, MPT) == preAlice); BEAST_EXPECT(env.balance(bob, MPT) == preBob); - BEAST_EXPECT(env.balance(gw, MPT) == MPT(20'000)); + BEAST_EXPECT(env.balance(gw, MPT) == MPT(-20'000)); BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0); BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0); } @@ -3608,7 +3608,7 @@ struct EscrowToken_test : public beast::unit_test::suite BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 125); BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 125); - BEAST_EXPECT(env.balance(gw, MPT) == MPT(20'000)); + BEAST_EXPECT(env.balance(gw, MPT) == MPT(-20'000)); // bob can finish escrow env(escrow::finish(gw, alice, seq1), @@ -3620,7 +3620,7 @@ struct EscrowToken_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(alice, MPT) == preAlice - delta); BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 0); BEAST_EXPECT(issuerMPTEscrowed(env, MPT) == 0); - BEAST_EXPECT(env.balance(gw, MPT) == MPT(19'875)); + BEAST_EXPECT(env.balance(gw, MPT) == MPT(-19'875)); } } @@ -3826,7 +3826,7 @@ struct EscrowToken_test : public beast::unit_test::suite BEAST_EXPECT(mptEscrowed(env, alice, MPT) == 10); BEAST_EXPECT(env.balance(bob, MPT) == MPT(0)); BEAST_EXPECT(mptEscrowed(env, bob, MPT) == 0); - BEAST_EXPECT(env.balance(gw, MPT) == MPT(10)); + BEAST_EXPECT(env.balance(gw, MPT) == MPT(-10)); mptGw.authorize({.account = bob, .flags = tfMPTUnauthorize}); mptGw.destroy( {.id = mptGw.issuanceID(), diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index ae2a1c45df..c91149b2f7 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -31,6 +31,7 @@ #include namespace ripple { +namespace test { class Invariants_test : public beast::unit_test::suite { @@ -112,13 +113,13 @@ class Invariants_test : public beast::unit_test::suite { terActual = ac.checkInvariants(terActual, fee); BEAST_EXPECT(terExpect == terActual); + auto const messages = sink.messages().str(); BEAST_EXPECT( - sink.messages().str().starts_with("Invariant failed:") || - sink.messages().str().starts_with( - "Transaction caused an exception")); + messages.starts_with("Invariant failed:") || + messages.starts_with("Transaction caused an exception")); for (auto const& m : expect_logs) { - if (sink.messages().str().find(m) == std::string::npos) + if (messages.find(m) == std::string::npos) { // uncomment if you want to log the invariant failure // message log << " --> " << m << std::endl; @@ -1435,6 +1436,127 @@ class Invariants_test : public beast::unit_test::suite {tecINVARIANT_FAILED, tecINVARIANT_FAILED}); } + void + testValidPseudoAccounts() + { + testcase << "valid pseudo accounts"; + + using namespace jtx; + + AccountID pseudoAccountID; + Preclose createPseudo = + [&, this](Account const& a, Account const& b, Env& env) { + PrettyAsset const xrpAsset{xrpIssue(), 1'000'000}; + + // Create vault + Vault vault{env}; + auto [tx, vKeylet] = + vault.create({.owner = a, .asset = xrpAsset}); + env(tx); + env.close(); + if (auto const vSle = env.le(vKeylet); BEAST_EXPECT(vSle)) + { + pseudoAccountID = vSle->at(sfAccount); + } + + return BEAST_EXPECT(env.le(keylet::account(pseudoAccountID))); + }; + + /* Cases to check + "pseudo-account has 0 pseudo-account fields set" + "pseudo-account has 2 pseudo-account fields set" + "pseudo-account sequence changed" + "pseudo-account flags are not set" + "pseudo-account has a regular key" + */ + struct Mod + { + std::string expectedFailure; + std::function func; + }; + auto const mods = std::to_array({ + { + "pseudo-account has 0 pseudo-account fields set", + [this](SLE::pointer& sle) { + BEAST_EXPECT(sle->at(~sfVaultID)); + sle->at(~sfVaultID) = std::nullopt; + }, + }, + { + "pseudo-account sequence changed", + [](SLE::pointer& sle) { sle->at(sfSequence) = 12345; }, + }, + { + "pseudo-account flags are not set", + [](SLE::pointer& sle) { sle->at(sfFlags) = lsfNoFreeze; }, + }, + { + "pseudo-account has a regular key", + [](SLE::pointer& sle) { + sle->at(sfRegularKey) = Account("regular").id(); + }, + }, + }); + + for (auto const& mod : mods) + { + doInvariantCheck( + {{mod.expectedFailure}}, + [&](Account const& A1, Account const&, ApplyContext& ac) { + auto sle = ac.view().peek(keylet::account(pseudoAccountID)); + if (!sle) + return false; + mod.func(sle); + ac.view().update(sle); + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_SET, [](STObject& tx) {}}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, + createPseudo); + } + for (auto const pField : getPseudoAccountFields()) + { + // createPseudo creates a vault, so sfVaultID will be set, and + // setting it again will not cause an error + if (pField == &sfVaultID) + continue; + doInvariantCheck( + {{"pseudo-account has 2 pseudo-account fields set"}}, + [&](Account const& A1, Account const&, ApplyContext& ac) { + auto sle = ac.view().peek(keylet::account(pseudoAccountID)); + if (!sle) + return false; + + auto const vaultID = ~sle->at(~sfVaultID); + BEAST_EXPECT(vaultID && !sle->isFieldPresent(*pField)); + sle->setFieldH256(*pField, *vaultID); + + ac.view().update(sle); + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_SET, [](STObject& tx) {}}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, + createPseudo); + } + + // Take one of the regular accounts and set the sequence to 0, which + // will make it look like a pseudo-account + doInvariantCheck( + {{"pseudo-account has 0 pseudo-account fields set"}, + {"pseudo-account sequence changed"}, + {"pseudo-account flags are not set"}}, + [&](Account const& A1, Account const&, ApplyContext& ac) { + auto sle = ac.view().peek(keylet::account(A1.id())); + if (!sle) + return false; + sle->at(sfSequence) = 0; + ac.view().update(sle); + return true; + }); + } + void testPermissionedDEX() { @@ -1622,10 +1744,12 @@ public: testValidNewAccountRoot(); testNFTokenPageInvariants(); testPermissionedDomainInvariants(); + testValidPseudoAccounts(); testPermissionedDEX(); } }; BEAST_DEFINE_TESTSUITE(Invariants, app, ripple); +} // namespace test } // namespace ripple diff --git a/src/test/app/LPTokenTransfer_test.cpp b/src/test/app/LPTokenTransfer_test.cpp index e95e974547..eccd864e71 100644 --- a/src/test/app/LPTokenTransfer_test.cpp +++ b/src/test/app/LPTokenTransfer_test.cpp @@ -287,11 +287,11 @@ class LPTokenTransfer_test : public jtx::AMMTest // with fixFrozenLPTokenTransfer enabled, alice's offer can no // longer cross with carol's offer BEAST_EXPECT( - expectLine(env, alice, STAmount{token1, 10'000'000}) && - expectLine(env, alice, STAmount{token2, 10'000'000})); + expectHolding(env, alice, STAmount{token1, 10'000'000}) && + expectHolding(env, alice, STAmount{token2, 10'000'000})); BEAST_EXPECT( - expectLine(env, carol, STAmount{token2, 10'000'000}) && - expectLine(env, carol, STAmount{token1, 10'000'000})); + expectHolding(env, carol, STAmount{token2, 10'000'000}) && + expectHolding(env, carol, STAmount{token1, 10'000'000})); BEAST_EXPECT( expectOffers(env, alice, 1) && expectOffers(env, carol, 0)); } @@ -300,11 +300,11 @@ class LPTokenTransfer_test : public jtx::AMMTest // alice's offer still crosses with carol's offer despite carol's // token1 is frozen BEAST_EXPECT( - expectLine(env, alice, STAmount{token1, 10'000'100}) && - expectLine(env, alice, STAmount{token2, 9'999'900})); + expectHolding(env, alice, STAmount{token1, 10'000'100}) && + expectHolding(env, alice, STAmount{token2, 9'999'900})); BEAST_EXPECT( - expectLine(env, carol, STAmount{token2, 10'000'100}) && - expectLine(env, carol, STAmount{token1, 9'999'900})); + expectHolding(env, carol, STAmount{token2, 10'000'100}) && + expectHolding(env, carol, STAmount{token1, 9'999'900})); BEAST_EXPECT( expectOffers(env, alice, 0) && expectOffers(env, carol, 0)); } diff --git a/src/test/app/ValidatorSite_test.cpp b/src/test/app/ValidatorSite_test.cpp index 579cd79a5a..cd60a5ed99 100644 --- a/src/test/app/ValidatorSite_test.cpp +++ b/src/test/app/ValidatorSite_test.cpp @@ -37,7 +37,6 @@ #include namespace ripple { -namespace test { namespace detail { constexpr char const* realValidatorContents() @@ -56,6 +55,7 @@ auto constexpr default_expires = std::chrono::seconds{3600}; auto constexpr default_effective_overlap = std::chrono::seconds{30}; } // namespace detail +namespace test { class ValidatorSite_test : public beast::unit_test::suite { private: diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index 3cd52eaad3..159bfd0796 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -1339,7 +1339,7 @@ class Vault_test : public beast::unit_test::suite env.close(); Vault vault{env}; - Asset asset = issuer["IOU"]; + Asset asset = issuer["IOU"].asset(); auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); @@ -1358,7 +1358,7 @@ class Vault_test : public beast::unit_test::suite env.close(); Vault vault{env}; - Asset asset = issuer["IOU"]; + Asset asset = issuer["IOU"].asset(); auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); env(tx, ter(terNO_RIPPLE)); @@ -1374,7 +1374,7 @@ class Vault_test : public beast::unit_test::suite env.close(); Vault vault{env}; - Asset asset = issuer["IOU"]; + Asset asset = issuer["IOU"].asset(); { auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); @@ -3165,7 +3165,7 @@ class Vault_test : public beast::unit_test::suite { using namespace test::jtx; - testcase("failed pseudo-account allocation"); + testcase("fail pseudo-account allocation"); Env env{*this, testable_amendments() | featureSingleAssetVault}; Account const owner{"owner"}; Vault vault{env}; @@ -3502,7 +3502,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(100, 0))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(1000, 0))); + STAmount(d.share, Number(-1000, 0))); { testcase("Scale redeem exact"); @@ -3527,7 +3527,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(90, 0))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(900, 0))); + STAmount(d.share, Number(-900, 0))); } { @@ -3562,7 +3562,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(900 - 25, -1))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(900 - 25, 0))); + STAmount(d.share, -Number(900 - 25, 0))); } { @@ -3589,7 +3589,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(875 - 21, -1))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(875 - 21, 0))); + STAmount(d.share, -Number(875 - 21, 0))); } { @@ -3650,7 +3650,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(100, 0))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(1000, 0))); + STAmount(d.share, Number(-1000, 0))); { testcase("Scale withdraw exact"); @@ -3678,7 +3678,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(90, 0))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(900, 0))); + STAmount(d.share, Number(-900, 0))); } { @@ -3725,7 +3725,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(900 - 25, -1))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(900 - 25, 0))); + STAmount(d.share, -Number(900 - 25, 0))); } { @@ -3754,7 +3754,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(875 - 38, -1))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(875 - 38, 0))); + STAmount(d.share, -Number(875 - 38, 0))); } { @@ -3783,7 +3783,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(837 - 37, -1))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(837 - 37, 0))); + STAmount(d.share, -Number(837 - 37, 0))); } { @@ -3806,7 +3806,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(800 - 1, -1))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(800 - 1, 0))); + STAmount(d.share, -Number(800 - 1, 0))); } { @@ -3869,7 +3869,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(100, 0))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(1000, 0))); + STAmount(d.share, -Number(1000, 0))); { testcase("Scale clawback exact"); // assetsToSharesWithdraw: @@ -3897,7 +3897,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(90, 0))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(900, 0))); + STAmount(d.share, -Number(900, 0))); } { @@ -3937,7 +3937,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(900 - 25, -1))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(900 - 25, 0))); + STAmount(d.share, -Number(900 - 25, 0))); } { @@ -3967,7 +3967,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(875 - 38, -1))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(875 - 38, 0))); + STAmount(d.share, -Number(875 - 38, 0))); } { @@ -3997,7 +3997,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(837 - 37, -1))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(837 - 37, 0))); + STAmount(d.share, -Number(837 - 37, 0))); } { @@ -4021,7 +4021,7 @@ class Vault_test : public beast::unit_test::suite STAmount(d.asset, Number(800 - 1, -1))); BEAST_EXPECT( env.balance(d.vaultAccount, d.shares) == - STAmount(d.share, Number(800 - 1, 0))); + STAmount(d.share, -Number(800 - 1, 0))); } { diff --git a/src/test/basics/FileUtilities_test.cpp b/src/test/basics/FileUtilities_test.cpp index 9071ac7231..d1a1d50216 100644 --- a/src/test/basics/FileUtilities_test.cpp +++ b/src/test/basics/FileUtilities_test.cpp @@ -31,7 +31,7 @@ public: void testGetFileContents() { - using namespace ripple::test::detail; + using namespace ripple::detail; using namespace boost::system; constexpr char const* expectedContents = diff --git a/src/test/basics/FeeUnits_test.cpp b/src/test/basics/Units_test.cpp similarity index 92% rename from src/test/basics/FeeUnits_test.cpp rename to src/test/basics/Units_test.cpp index f9be632644..33dce42abb 100644 --- a/src/test/basics/FeeUnits_test.cpp +++ b/src/test/basics/Units_test.cpp @@ -17,13 +17,13 @@ */ #include -#include #include +#include namespace ripple { namespace test { -class feeunits_test : public beast::unit_test::suite +class units_test : public beast::unit_test::suite { private: void @@ -35,16 +35,16 @@ private: XRPAmount x{100}; BEAST_EXPECT(x.drops() == 100); BEAST_EXPECT( - (std::is_same_v)); + (std::is_same_v)); auto y = 4u * x; BEAST_EXPECT(y.value() == 400); BEAST_EXPECT( - (std::is_same_v)); + (std::is_same_v)); auto z = 4 * y; BEAST_EXPECT(z.value() == 1600); BEAST_EXPECT( - (std::is_same_v)); + (std::is_same_v)); FeeLevel32 f{10}; FeeLevel32 baseFee{100}; @@ -55,7 +55,7 @@ private: BEAST_EXPECT(drops.value() == 1000); BEAST_EXPECT((std::is_same_v< std::remove_reference_t::unit_type, - feeunit::dropTag>)); + unit::dropTag>)); BEAST_EXPECT((std::is_same_v< std::remove_reference_t, @@ -65,11 +65,11 @@ private: XRPAmount x{100}; BEAST_EXPECT(x.value() == 100); BEAST_EXPECT( - (std::is_same_v)); + (std::is_same_v)); auto y = 4u * x; BEAST_EXPECT(y.value() == 400); BEAST_EXPECT( - (std::is_same_v)); + (std::is_same_v)); FeeLevel64 f{10}; FeeLevel64 baseFee{100}; @@ -80,7 +80,7 @@ private: BEAST_EXPECT(drops.value() == 1000); BEAST_EXPECT((std::is_same_v< std::remove_reference_t::unit_type, - feeunit::dropTag>)); + unit::dropTag>)); BEAST_EXPECT((std::is_same_v< std::remove_reference_t, XRPAmount>)); @@ -89,12 +89,12 @@ private: FeeLevel64 x{1024}; BEAST_EXPECT(x.value() == 1024); BEAST_EXPECT( - (std::is_same_v)); + (std::is_same_v)); std::uint64_t m = 4; auto y = m * x; BEAST_EXPECT(y.value() == 4096); BEAST_EXPECT( - (std::is_same_v)); + (std::is_same_v)); XRPAmount basefee{10}; FeeLevel64 referencefee{256}; @@ -105,7 +105,7 @@ private: BEAST_EXPECT(drops.value() == 40); BEAST_EXPECT((std::is_same_v< std::remove_reference_t::unit_type, - feeunit::dropTag>)); + unit::dropTag>)); BEAST_EXPECT((std::is_same_v< std::remove_reference_t, XRPAmount>)); @@ -181,7 +181,7 @@ private: void testFunctions() { - // Explicitly test every defined function for the TaggedFee class + // Explicitly test every defined function for the ValueUnit class // since some of them are templated, but not used anywhere else. using FeeLevel32 = FeeLevel; @@ -191,8 +191,8 @@ private: return FeeLevel64{x}; }; + [[maybe_unused]] FeeLevel64 defaulted; - (void)defaulted; FeeLevel64 test{0}; BEAST_EXPECT(test.fee() == 0); @@ -278,8 +278,8 @@ private: return FeeLevelDouble{x}; }; + [[maybe_unused]] FeeLevelDouble defaulted; - (void)defaulted; FeeLevelDouble test{0}; BEAST_EXPECT(test.fee() == 0); @@ -371,7 +371,7 @@ public: } }; -BEAST_DEFINE_TESTSUITE(feeunits, basics, ripple); +BEAST_DEFINE_TESTSUITE(units, basics, ripple); } // namespace test } // namespace ripple diff --git a/src/test/basics/base_uint_test.cpp b/src/test/basics/base_uint_test.cpp index 6ee9f0901a..458032d203 100644 --- a/src/test/basics/base_uint_test.cpp +++ b/src/test/basics/base_uint_test.cpp @@ -152,6 +152,7 @@ struct base_uint_test : beast::unit_test::suite uset.insert(u); BEAST_EXPECT(raw.size() == u.size()); BEAST_EXPECT(to_string(u) == "0102030405060708090A0B0C"); + BEAST_EXPECT(to_short_string(u) == "01020304..."); BEAST_EXPECT(*u.data() == 1); BEAST_EXPECT(u.signum() == 1); BEAST_EXPECT(!!u); @@ -174,6 +175,7 @@ struct base_uint_test : beast::unit_test::suite test96 v{~u}; uset.insert(v); BEAST_EXPECT(to_string(v) == "FEFDFCFBFAF9F8F7F6F5F4F3"); + BEAST_EXPECT(to_short_string(v) == "FEFDFCFB..."); BEAST_EXPECT(*v.data() == 0xfe); BEAST_EXPECT(v.signum() == 1); BEAST_EXPECT(!!v); @@ -194,6 +196,7 @@ struct base_uint_test : beast::unit_test::suite test96 z{beast::zero}; uset.insert(z); BEAST_EXPECT(to_string(z) == "000000000000000000000000"); + BEAST_EXPECT(to_short_string(z) == "00000000..."); BEAST_EXPECT(*z.data() == 0); BEAST_EXPECT(*z.begin() == 0); BEAST_EXPECT(*std::prev(z.end(), 1) == 0); @@ -214,6 +217,7 @@ struct base_uint_test : beast::unit_test::suite BEAST_EXPECT(n == z); n--; BEAST_EXPECT(to_string(n) == "FFFFFFFFFFFFFFFFFFFFFFFF"); + BEAST_EXPECT(to_short_string(n) == "FFFFFFFF..."); n = beast::zero; BEAST_EXPECT(n == z); @@ -224,6 +228,7 @@ struct base_uint_test : beast::unit_test::suite test96 x{zm1 ^ zp1}; uset.insert(x); BEAST_EXPECTS(to_string(x) == "FFFFFFFFFFFFFFFFFFFFFFFE", to_string(x)); + BEAST_EXPECTS(to_short_string(x) == "FFFFFFFF...", to_short_string(x)); BEAST_EXPECT(uset.size() == 4); diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index 8b0fce1e20..a1a6a079cc 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -128,7 +128,7 @@ backend=sqlite /** Write a rippled config file and remove when done. */ -class RippledCfgGuard : public ripple::test::detail::FileDirGuard +class RippledCfgGuard : public ripple::detail::FileDirGuard { private: path dataDir_; @@ -239,7 +239,7 @@ moreripplevalidators.net /** Write a validators.txt file and remove when done. */ -class ValidatorsTxtGuard : public test::detail::FileDirGuard +class ValidatorsTxtGuard : public detail::FileDirGuard { public: ValidatorsTxtGuard( @@ -345,7 +345,7 @@ port_wss_admin { // read from file absolute path auto const cwd = current_path(); - ripple::test::detail::DirGuard const g0(*this, "test_db"); + ripple::detail::DirGuard const g0(*this, "test_db"); path const dataDirRel("test_data_dir"); path const dataDirAbs(cwd / g0.subdir() / dataDirRel); detail::RippledCfgGuard const g( diff --git a/src/test/csf/Digraph.h b/src/test/csf/Digraph.h index 3f079eac17..e65a7af913 100644 --- a/src/test/csf/Digraph.h +++ b/src/test/csf/Digraph.h @@ -30,9 +30,6 @@ #include namespace ripple { -namespace test { -namespace csf { - namespace detail { // Dummy class when no edge data needed for graph struct NoEdgeData @@ -41,6 +38,9 @@ struct NoEdgeData } // namespace detail +namespace test { +namespace csf { + /** Directed graph Basic directed graph that uses an adjacency list to represent out edges. diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index 21a239e3d7..68d8d3e53f 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -469,6 +469,9 @@ public: Returns 0 if the trust line does not exist. */ // VFALCO NOTE This should return a unit-less amount + PrettyAmount + balance(Account const& account, Asset const& asset) const; + PrettyAmount balance(Account const& account, Issue const& issue) const; diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index d4a39b6498..7d14f23c92 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -27,7 +27,9 @@ #include #include #include +#include #include +#include #include #include @@ -44,6 +46,252 @@ namespace ripple { namespace test { namespace jtx { +/** Generic helper class for helper clases that set a field on a JTx. + + Not every helper will be able to use this because of conversions and other + issues, but for classes where it's straightforward, this can simplify things. +*/ +template < + class SField, + class StoredValue = typename SField::type::value_type, + class OutputValue = StoredValue> +struct JTxField +{ + using SF = SField; + using SV = StoredValue; + using OV = OutputValue; + +protected: + SF const& sfield_; + SV value_; + +public: + explicit JTxField(SF const& sfield, SV const& value) + : sfield_(sfield), value_(value) + { + } + + virtual ~JTxField() = default; + + virtual OV + value() const = 0; + + virtual void + operator()(Env&, JTx& jt) const + { + jt.jv[sfield_.jsonName] = value(); + } +}; + +template +struct JTxField +{ + using SF = SField; + using SV = StoredValue; + using OV = SV; + +protected: + SF const& sfield_; + SV value_; + +public: + explicit JTxField(SF const& sfield, SV const& value) + : sfield_(sfield), value_(value) + { + } + + void + operator()(Env&, JTx& jt) const + { + jt.jv[sfield_.jsonName] = value_; + } +}; + +struct timePointField + : public JTxField +{ + using SF = SF_UINT32; + using SV = NetClock::time_point; + using OV = NetClock::rep; + using base = JTxField; + +protected: + using base::value_; + +public: + explicit timePointField(SF const& sfield, SV const& value) + : JTxField(sfield, value) + { + } + + OV + value() const override + { + return value_.time_since_epoch().count(); + } +}; + +struct uint256Field : public JTxField +{ + using SF = SF_UINT256; + using SV = uint256; + using OV = std::string; + using base = JTxField; + +protected: + using base::value_; + +public: + explicit uint256Field(SF const& sfield, SV const& value) + : JTxField(sfield, value) + { + } + + OV + value() const override + { + return to_string(value_); + } +}; + +struct accountIDField : public JTxField +{ + using SF = SF_ACCOUNT; + using SV = AccountID; + using OV = std::string; + using base = JTxField; + +protected: + using base::value_; + +public: + explicit accountIDField(SF const& sfield, SV const& value) + : JTxField(sfield, value) + { + } + + OV + value() const override + { + return toBase58(value_); + } +}; + +struct blobField : public JTxField +{ + using SF = SF_VL; + using SV = std::string; + using base = JTxField; + + using JTxField::JTxField; + + explicit blobField(SF const& sfield, Slice const& cond) + : JTxField(sfield, strHex(cond)) + { + } + + template + explicit blobField(SF const& sfield, std::array const& c) + : blobField(sfield, makeSlice(c)) + { + } +}; + +template +struct valueUnitField + : public JTxField, ValueType> +{ + using SF = SField; + using SV = unit::ValueUnit; + using OV = ValueType; + using base = JTxField; + + static_assert(std::is_same_v); + +protected: + using base::value_; + +public: + using JTxField::JTxField; + + OV + value() const override + { + return value_.value(); + } +}; + +template +struct JTxFieldWrapper +{ + using JF = JTxField; + using SF = typename JF::SF; + using SV = typename JF::SV; + +protected: + SF const& sfield_; + +public: + explicit JTxFieldWrapper(SF const& sfield) : sfield_(sfield) + { + } + + JF + operator()(SV const& value) const + { + return JTxField(sfield_, value); + } +}; + +template <> +struct JTxFieldWrapper +{ + using JF = blobField; + using SF = JF::SF; + using SV = JF::SV; + +protected: + SF const& sfield_; + +public: + explicit JTxFieldWrapper(SF const& sfield) : sfield_(sfield) + { + } + + JF + operator()(SV const& cond) const + { + return JF(sfield_, makeSlice(cond)); + } + + JF + operator()(Slice const& cond) const + { + return JF(sfield_, cond); + } + + template + JF + operator()(std::array const& c) const + { + return operator()(makeSlice(c)); + } +}; + +template < + class SField, + class UnitTag, + class ValueType = typename SField::type::value_type> +using valueUnitWrapper = + JTxFieldWrapper>; + +template +using simpleField = JTxFieldWrapper>; + +/** General field definitions, or fields used in multiple transaction namespaces + */ +auto const data = JTxFieldWrapper(sfData); + // TODO We only need this long "requires" clause as polyfill, for C++20 // implementations which are missing header. Replace with // `std::ranges::range`, and accordingly use std::ranges::begin/end @@ -111,6 +359,25 @@ checkArraySize(Json::Value const& val, unsigned int size); std::uint32_t ownerCount(test::jtx::Env const& env, test::jtx::Account const& account); +[[nodiscard]] +inline bool +checkVL(Slice const& result, std::string const& expected) +{ + Serializer s; + s.addRaw(result); + return s.getString() == expected; +} + +[[nodiscard]] +inline bool +checkVL( + std::shared_ptr const& sle, + SField const& field, + std::string const& expected) +{ + return strHex(expected) == strHex(sle->getFieldVL(field)); +} + /* Path finding */ /******************************************************************************/ void @@ -186,7 +453,7 @@ PrettyAmount xrpMinusFee(Env const& env, std::int64_t xrpAmount); bool -expectLine( +expectHolding( Env& env, AccountID const& account, STAmount const& value, @@ -194,18 +461,18 @@ expectLine( template bool -expectLine( +expectHolding( Env& env, AccountID const& account, STAmount const& value, Amts const&... amts) { - return expectLine(env, account, value, false) && - expectLine(env, account, amts...); + return expectHolding(env, account, value, false) && + expectHolding(env, account, amts...); } bool -expectLine(Env& env, AccountID const& account, None const& value); +expectHolding(Env& env, AccountID const& account, None const& value); bool expectOffers( diff --git a/src/test/jtx/amount.h b/src/test/jtx/amount.h index 344a2ab73c..a793f3a287 100644 --- a/src/test/jtx/amount.h +++ b/src/test/jtx/amount.h @@ -24,9 +24,9 @@ #include #include -#include #include #include +#include #include #include @@ -34,6 +34,15 @@ #include namespace ripple { +namespace detail { + +struct epsilon_multiple +{ + std::size_t n; +}; + +} // namespace detail + namespace test { namespace jtx { @@ -57,7 +66,7 @@ struct AnyAmount; // struct None { - Issue issue; + Asset asset; }; //------------------------------------------------------------------------------ @@ -133,6 +142,12 @@ public: return amount_; } + inline int + signum() const + { + return amount_.signum(); + } + operator STAmount const&() const { return amount_; @@ -165,17 +180,17 @@ struct PrettyAsset { private: Asset asset_; - unsigned int scale_; + std::uint32_t scale_; public: template requires std::convertible_to - PrettyAsset(A const& asset, unsigned int scale = 1) + PrettyAsset(A const& asset, std::uint32_t scale = 1) : PrettyAsset{Asset{asset}, scale} { } - PrettyAsset(Asset const& asset, unsigned int scale = 1) + PrettyAsset(Asset const& asset, std::uint32_t scale = 1) : asset_(asset), scale_(scale) { } @@ -199,10 +214,22 @@ public: template PrettyAmount operator()(T v) const + { + return operator()(Number(v)); + } + + PrettyAmount + operator()(Number v) const { STAmount amount{asset_, v * scale_}; return {amount, ""}; } + + None + operator()(none_t) const + { + return {asset_}; + } }; //------------------------------------------------------------------------------ @@ -312,15 +339,6 @@ drops(XRPAmount i) //------------------------------------------------------------------------------ -namespace detail { - -struct epsilon_multiple -{ - std::size_t n; -}; - -} // namespace detail - // The smallest possible IOU STAmount struct epsilon_t { @@ -360,6 +378,11 @@ public: { return {currency, account.id()}; } + Asset + asset() const + { + return issue(); + } /** Implicit conversion to Issue or Asset. @@ -370,9 +393,9 @@ public: { return issue(); } - operator Asset() const + operator PrettyAsset() const { - return issue(); + return asset(); } template < @@ -438,14 +461,32 @@ public: return issuanceID; } - /** Implicit conversion to MPTIssue. + /** Explicit conversion to MPTIssue or asset. + */ + ripple::MPTIssue + mptIssue() const + { + return MPTIssue{issuanceID}; + } + Asset + asset() const + { + return mptIssue(); + } + + /** Implicit conversion to MPTIssue or asset. This allows passing an MPT value where an MPTIssue is expected. */ operator ripple::MPTIssue() const { - return MPTIssue{issuanceID}; + return mptIssue(); + } + + operator PrettyAsset() const + { + return asset(); } template @@ -461,6 +502,13 @@ public: PrettyAmount operator()(detail::epsilon_multiple) const; + /** Returns None-of-Issue */ + None + operator()(none_t) const + { + return {mptIssue()}; + } + friend BookSpec operator~(MPT const& mpt) { diff --git a/src/test/jtx/balance.h b/src/test/jtx/balance.h index 3a2cf0423f..0c4a6cca1d 100644 --- a/src/test/jtx/balance.h +++ b/src/test/jtx/balance.h @@ -38,9 +38,9 @@ namespace jtx { class balance { private: - bool none_; - Account account_; - STAmount value_; + bool const none_; + Account const account_; + STAmount const value_; public: balance(Account const& account, none_t) @@ -49,7 +49,7 @@ public: } balance(Account const& account, None const& value) - : none_(true), account_(account), value_(value.issue) + : none_(true), account_(account), value_(value.asset) { } diff --git a/src/test/jtx/escrow.h b/src/test/jtx/escrow.h index 3147b44c65..483db578b0 100644 --- a/src/test/jtx/escrow.h +++ b/src/test/jtx/escrow.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -94,86 +95,14 @@ std::array const cb3 = { 0x57, 0x0D, 0x15, 0x85, 0x8B, 0xD4, 0x81, 0x01, 0x04}}; /** Set the "FinishAfter" time tag on a JTx */ -struct finish_time -{ -private: - NetClock::time_point value_; - -public: - explicit finish_time(NetClock::time_point const& value) : value_(value) - { - } - - void - operator()(Env&, JTx& jt) const - { - jt.jv[sfFinishAfter.jsonName] = value_.time_since_epoch().count(); - } -}; +auto const finish_time = JTxFieldWrapper(sfFinishAfter); /** Set the "CancelAfter" time tag on a JTx */ -struct cancel_time -{ -private: - NetClock::time_point value_; +auto const cancel_time = JTxFieldWrapper(sfCancelAfter); -public: - explicit cancel_time(NetClock::time_point const& value) : value_(value) - { - } +auto const condition = JTxFieldWrapper(sfCondition); - void - operator()(jtx::Env&, jtx::JTx& jt) const - { - jt.jv[sfCancelAfter.jsonName] = value_.time_since_epoch().count(); - } -}; - -struct condition -{ -private: - std::string value_; - -public: - explicit condition(Slice const& cond) : value_(strHex(cond)) - { - } - - template - explicit condition(std::array const& c) - : condition(makeSlice(c)) - { - } - - void - operator()(Env&, JTx& jt) const - { - jt.jv[sfCondition.jsonName] = value_; - } -}; - -struct fulfillment -{ -private: - std::string value_; - -public: - explicit fulfillment(Slice condition) : value_(strHex(condition)) - { - } - - template - explicit fulfillment(std::array f) - : fulfillment(makeSlice(f)) - { - } - - void - operator()(Env&, JTx& jt) const - { - jt.jv[sfFulfillment.jsonName] = value_; - } -}; +auto const fulfillment = JTxFieldWrapper(sfFulfillment); } // namespace escrow diff --git a/src/test/jtx/fee.h b/src/test/jtx/fee.h index 7d54804f87..3e3740b80d 100644 --- a/src/test/jtx/fee.h +++ b/src/test/jtx/fee.h @@ -37,6 +37,7 @@ class fee { private: bool manual_ = true; + bool increment_ = false; std::optional amount_; public: @@ -44,6 +45,10 @@ public: { } + explicit fee(increment_t) : increment_(true) + { + } + explicit fee(none_t) { } diff --git a/src/test/jtx/flags.h b/src/test/jtx/flags.h index aa048c3e55..8d3fa4f25c 100644 --- a/src/test/jtx/flags.h +++ b/src/test/jtx/flags.h @@ -27,22 +27,6 @@ #include namespace ripple { -namespace test { -namespace jtx { - -// JSON generators - -/** Add and/or remove flag. */ -Json::Value -fset(Account const& account, std::uint32_t on, std::uint32_t off = 0); - -/** Remove account flag. */ -inline Json::Value -fclear(Account const& account, std::uint32_t off) -{ - return fset(account, 0, off); -} - namespace detail { class flags_helper @@ -123,6 +107,22 @@ protected: } // namespace detail +namespace test { +namespace jtx { + +// JSON generators + +/** Add and/or remove flag. */ +Json::Value +fset(Account const& account, std::uint32_t on, std::uint32_t off = 0); + +/** Remove account flag. */ +inline Json::Value +fclear(Account const& account, std::uint32_t off) +{ + return fset(account, 0, off); +} + /** Match set account flags */ class flags : private detail::flags_helper { diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index d6956b30c7..ae99e1b5d6 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -218,7 +218,9 @@ Env::balance(Account const& account, MPTIssue const& mptIssue) const if (!sle) return {STAmount(mptIssue, 0), account.name()}; - STAmount const amount{mptIssue, sle->getFieldU64(sfOutstandingAmount)}; + // Make it negative + STAmount const amount{ + mptIssue, sle->getFieldU64(sfOutstandingAmount), 0, true}; return {amount, lookup(issuer).name()}; } else @@ -233,6 +235,14 @@ Env::balance(Account const& account, MPTIssue const& mptIssue) const } } +PrettyAmount +Env::balance(Account const& account, Asset const& asset) const +{ + return std::visit( + [&](auto const& issue) { return balance(account, issue); }, + asset.value()); +} + PrettyAmount Env::limit(Account const& account, Issue const& issue) const { diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index 5f8c53877a..20f24f0d84 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -103,7 +103,7 @@ xrpMinusFee(Env const& env, std::int64_t xrpAmount) }; [[nodiscard]] bool -expectLine( +expectHolding( Env& env, AccountID const& account, STAmount const& value, @@ -137,9 +137,33 @@ expectLine( } [[nodiscard]] bool -expectLine(Env& env, AccountID const& account, None const& value) +expectHolding( + Env& env, + AccountID const& account, + None const&, + Issue const& issue) { - return !env.le(keylet::line(account, value.issue)); + return !env.le(keylet::line(account, issue)); +} + +[[nodiscard]] bool +expectHolding( + Env& env, + AccountID const& account, + None const&, + MPTIssue const& mptIssue) +{ + return !env.le(keylet::mptoken(mptIssue.getMptID(), account)); +} + +[[nodiscard]] bool +expectHolding(Env& env, AccountID const& account, None const& value) +{ + return std::visit( + [&](auto const& issue) { + return expectHolding(env, account, value, issue); + }, + value.asset.value()); } [[nodiscard]] bool diff --git a/src/test/jtx/impl/amount.cpp b/src/test/jtx/impl/amount.cpp index a1dbd25652..a04a6c87d2 100644 --- a/src/test/jtx/impl/amount.cpp +++ b/src/test/jtx/impl/amount.cpp @@ -91,12 +91,18 @@ operator<<(std::ostream& os, PrettyAmount const& amount) os << to_places(d, 6) << " XRP"; } - else + else if (amount.value().holds()) { os << amount.value().getText() << "/" << to_string(amount.value().issue().currency) << "(" << amount.name() << ")"; } + else + { + auto const& mptIssue = amount.value().asset().get(); + os << amount.value().getText() << "/" << to_string(mptIssue) << "(" + << amount.name() << ")"; + } return os; } diff --git a/src/test/jtx/impl/balance.cpp b/src/test/jtx/impl/balance.cpp index 42330658eb..decbd816e1 100644 --- a/src/test/jtx/impl/balance.cpp +++ b/src/test/jtx/impl/balance.cpp @@ -24,38 +24,73 @@ namespace test { namespace jtx { void -balance::operator()(Env& env) const +doBalance( + Env& env, + AccountID const& account, + bool none, + STAmount const& value, + Issue const& issue) { - if (isXRP(value_.issue())) + if (isXRP(issue)) { - auto const sle = env.le(account_); - if (none_) + auto const sle = env.le(keylet::account(account)); + if (none) { env.test.expect(!sle); } else if (env.test.expect(sle)) { - env.test.expect(sle->getFieldAmount(sfBalance) == value_); + env.test.expect(sle->getFieldAmount(sfBalance) == value); } } else { - auto const sle = env.le(keylet::line(account_.id(), value_.issue())); - if (none_) + auto const sle = env.le(keylet::line(account, issue)); + if (none) { env.test.expect(!sle); } else if (env.test.expect(sle)) { auto amount = sle->getFieldAmount(sfBalance); - amount.setIssuer(value_.issue().account); - if (account_.id() > value_.issue().account) + amount.setIssuer(issue.account); + if (account > issue.account) amount.negate(); - env.test.expect(amount == value_); + env.test.expect(amount == value); } } } +void +doBalance( + Env& env, + AccountID const& account, + bool none, + STAmount const& value, + MPTIssue const& mptIssue) +{ + auto const sle = env.le(keylet::mptoken(mptIssue.getMptID(), account)); + if (none) + { + env.test.expect(!sle); + } + else if (env.test.expect(sle)) + { + STAmount const amount{mptIssue, sle->getFieldU64(sfMPTAmount)}; + env.test.expect(amount == value); + } +} + +void +balance::operator()(Env& env) const +{ + return std::visit( + [&](auto const& issue) { + doBalance(env, account_.id(), none_, value_, issue); + }, + value_.asset().value()); +} + } // namespace jtx } // namespace test } // namespace ripple diff --git a/src/test/jtx/impl/fee.cpp b/src/test/jtx/impl/fee.cpp index 71e3dd089a..a58105a85f 100644 --- a/src/test/jtx/impl/fee.cpp +++ b/src/test/jtx/impl/fee.cpp @@ -26,13 +26,16 @@ namespace test { namespace jtx { void -fee::operator()(Env&, JTx& jt) const +fee::operator()(Env& env, JTx& jt) const { if (!manual_) return; jt.fill_fee = false; - if (amount_) - jt[jss::Fee] = amount_->getJson(JsonOptions::none); + assert(!increment_ || !amount_); + if (increment_) + jt[sfFee] = STAmount(env.current()->fees().increment).getJson(); + else if (amount_) + jt[sfFee] = amount_->getJson(JsonOptions::none); } } // namespace jtx diff --git a/src/test/jtx/impl/owners.cpp b/src/test/jtx/impl/owners.cpp index 386ec29a37..b55986fccb 100644 --- a/src/test/jtx/impl/owners.cpp +++ b/src/test/jtx/impl/owners.cpp @@ -20,9 +20,6 @@ #include namespace ripple { -namespace test { -namespace jtx { - namespace detail { std::uint32_t @@ -39,7 +36,7 @@ owned_count_of(ReadView const& view, AccountID const& id, LedgerEntryType type) void owned_count_helper( - Env& env, + test::jtx::Env& env, AccountID const& id, LedgerEntryType type, std::uint32_t value) @@ -49,6 +46,9 @@ owned_count_helper( } // namespace detail +namespace test { +namespace jtx { + void owners::operator()(Env& env) const { diff --git a/src/test/jtx/owners.h b/src/test/jtx/owners.h index 9b6f6a6df5..baf5d9da7d 100644 --- a/src/test/jtx/owners.h +++ b/src/test/jtx/owners.h @@ -29,8 +29,6 @@ #include namespace ripple { -namespace test { -namespace jtx { namespace detail { @@ -39,13 +37,16 @@ owned_count_of(ReadView const& view, AccountID const& id, LedgerEntryType type); void owned_count_helper( - Env& env, + test::jtx::Env& env, AccountID const& id, LedgerEntryType type, std::uint32_t value); } // namespace detail +namespace test { +namespace jtx { + // Helper for aliases template class owner_count diff --git a/src/test/jtx/require.h b/src/test/jtx/require.h index bec21235a6..3215ac0abb 100644 --- a/src/test/jtx/require.h +++ b/src/test/jtx/require.h @@ -26,14 +26,12 @@ #include namespace ripple { -namespace test { -namespace jtx { namespace detail { template inline void -require_args(requires_t& vec, Cond const& cond, Args const&... args) +require_args(test::jtx::requires_t& vec, Cond const& cond, Args const&... args) { vec.push_back(cond); if constexpr (sizeof...(args) > 0) @@ -42,6 +40,9 @@ require_args(requires_t& vec, Cond const& cond, Args const&... args) } // namespace detail +namespace test { +namespace jtx { + /** Compose many condition functors into one */ template require_t diff --git a/src/test/jtx/tags.h b/src/test/jtx/tags.h index bb64295f05..4d55929d69 100644 --- a/src/test/jtx/tags.h +++ b/src/test/jtx/tags.h @@ -49,6 +49,16 @@ struct disabled_t }; static disabled_t const disabled; +/** Used for fee() calls that use an owner reserve increment */ +struct increment_t +{ + increment_t() + { + } +}; + +static increment_t const increment; + } // namespace jtx } // namespace test diff --git a/src/test/nodestore/import_test.cpp b/src/test/nodestore/import_test.cpp index 11009ec5be..ea5f23548a 100644 --- a/src/test/nodestore/import_test.cpp +++ b/src/test/nodestore/import_test.cpp @@ -61,7 +61,6 @@ multi(32gb): */ namespace ripple { -namespace NodeStore { namespace detail { @@ -191,6 +190,8 @@ fmtdur(std::chrono::duration const& d) } // namespace detail +namespace NodeStore { + //------------------------------------------------------------------------------ class progress diff --git a/src/test/unit_test/FileDirGuard.h b/src/test/unit_test/FileDirGuard.h index 091bc80d20..d3cabc2092 100644 --- a/src/test/unit_test/FileDirGuard.h +++ b/src/test/unit_test/FileDirGuard.h @@ -29,7 +29,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include namespace ripple { -namespace test { namespace detail { /** @@ -178,7 +177,6 @@ public: }; } // namespace detail -} // namespace test } // namespace ripple #endif // TEST_UNIT_TEST_DIRGUARD_H diff --git a/src/test/unit_test/multi_runner.cpp b/src/test/unit_test/multi_runner.cpp index 087e37dac2..3755428ee3 100644 --- a/src/test/unit_test/multi_runner.cpp +++ b/src/test/unit_test/multi_runner.cpp @@ -30,7 +30,6 @@ #include namespace ripple { -namespace test { namespace detail { @@ -388,6 +387,8 @@ multi_runner_base::add_failures(std::size_t failures) } // namespace detail +namespace test { + //------------------------------------------------------------------------------ multi_runner_parent::multi_runner_parent() : os_(std::cout) @@ -645,10 +646,11 @@ multi_runner_child::on_log(std::string const& msg) message_queue_send(MessageType::log, s.str()); } +} // namespace test + namespace detail { template class multi_runner_base; template class multi_runner_base; } // namespace detail -} // namespace test } // namespace ripple diff --git a/src/test/unit_test/multi_runner.h b/src/test/unit_test/multi_runner.h index 08512d1882..bce62fb131 100644 --- a/src/test/unit_test/multi_runner.h +++ b/src/test/unit_test/multi_runner.h @@ -40,7 +40,6 @@ #include namespace ripple { -namespace test { namespace detail { @@ -212,6 +211,8 @@ public: } // namespace detail +namespace test { + //------------------------------------------------------------------------------ /** Manager for children running unit tests diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index beaf85ce2e..05b8f5e5fa 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -304,8 +304,8 @@ public: static_cast(std::thread::hardware_concurrency()); // Be more aggressive about the number of threads to use - // for the job queue if the server is configured as "large" - // or "huge" if there are enough cores. + // for the job queue if the server is configured as + // "large" or "huge" if there are enough cores. if (config->NODE_SIZE >= 4 && count >= 16) count = 6 + std::min(count, 8); else if (config->NODE_SIZE >= 3 && count >= 8) diff --git a/src/xrpld/app/misc/detail/LoadFeeTrack.cpp b/src/xrpld/app/misc/detail/LoadFeeTrack.cpp index 776e9fa50b..4728df9272 100644 --- a/src/xrpld/app/misc/detail/LoadFeeTrack.cpp +++ b/src/xrpld/app/misc/detail/LoadFeeTrack.cpp @@ -22,8 +22,7 @@ #include #include #include -#include -#include +#include #include diff --git a/src/xrpld/app/tx/detail/DeleteAccount.cpp b/src/xrpld/app/tx/detail/DeleteAccount.cpp index deb1743991..02f84adcc3 100644 --- a/src/xrpld/app/tx/detail/DeleteAccount.cpp +++ b/src/xrpld/app/tx/detail/DeleteAccount.cpp @@ -31,10 +31,10 @@ #include #include #include -#include #include #include #include +#include namespace ripple { diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index ace7437098..f1d1db79a0 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -81,8 +81,8 @@ constexpr HashRouterFlags SF_CF_VALID = HashRouterFlags::PRIVATE6; TxConsequences EscrowCreate::makeTxConsequences(PreflightContext const& ctx) { - return TxConsequences{ - ctx.tx, isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::zero}; + auto const amount = ctx.tx[sfAmount]; + return TxConsequences{ctx.tx, isXRP(amount) ? amount.xrp() : beast::zero}; } template diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index f20a49366b..87ca9ea6c1 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -28,14 +28,88 @@ #include #include #include -#include #include +#include #include #include +#include #include namespace ripple { +/* +assert(enforce) + +There are several asserts (or XRPL_ASSERTs) in this file that check a variable +named `enforce` when an invariant fails. At first glance, those asserts may look +incorrect, but they are not. + +Those asserts take advantage of two facts: +1. `asserts` are not (normally) executed in release builds. +2. Invariants should *never* fail, except in tests that specifically modify + the open ledger to break them. + +This makes `assert(enforce)` sort of a second-layer of invariant enforcement +aimed at _developers_. It's designed to fire if a developer writes code that +violates an invariant, and runs it in unit tests or a develop build that _does +not have the relevant amendments enabled_. It's intentionally a pain in the neck +so that bad code gets caught and fixed as early as possible. +*/ + +enum Privilege { + noPriv = + 0x0000, // The transaction can not do any of the enumerated operations + createAcct = + 0x0001, // The transaction can create a new ACCOUNT_ROOT object. + createPseudoAcct = 0x0002, // The transaction can create a pseudo account, + // which implies createAcct + mustDeleteAcct = + 0x0004, // The transaction must delete an ACCOUNT_ROOT object + mayDeleteAcct = 0x0008, // The transaction may delete an ACCOUNT_ROOT + // object, but does not have to + overrideFreeze = 0x0010, // The transaction can override some freeze rules + changeNFTCounts = 0x0020, // The transaction can mint or burn an NFT + createMPTIssuance = + 0x0040, // The transaction can create a new MPT issuance + destroyMPTIssuance = 0x0080, // The transaction can destroy an MPT issuance + mustAuthorizeMPT = 0x0100, // The transaction MUST create or delete an MPT + // object (except by issuer) + mayAuthorizeMPT = 0x0200, // The transaction MAY create or delete an MPT + // object (except by issuer) + mayDeleteMPT = + 0x0400, // The transaction MAY delete an MPT object. May not create. +}; +constexpr Privilege +operator|(Privilege lhs, Privilege rhs) +{ + return safe_cast( + safe_cast>(lhs) | + safe_cast>(rhs)); +} + +#pragma push_macro("TRANSACTION") +#undef TRANSACTION + +#define TRANSACTION(tag, value, name, delegatable, amendment, privileges, ...) \ + case tag: { \ + return (privileges) & priv; \ + } + +bool +hasPrivilege(STTx const& tx, Privilege priv) +{ + switch (tx.getTxnType()) + { +#include + // Deprecated types + default: + return false; + } +}; + +#undef TRANSACTION +#pragma pop_macro("TRANSACTION") + void TransactionFeeCheck::visitEntry( bool, @@ -379,10 +453,7 @@ AccountRootsNotDeleted::finalize( // transaction when the total AMM LP Tokens balance goes to 0. // A successful AccountDelete or AMMDelete MUST delete exactly // one account root. - if ((tx.getTxnType() == ttACCOUNT_DELETE || - tx.getTxnType() == ttAMM_DELETE || - tx.getTxnType() == ttVAULT_DELETE) && - result == tesSUCCESS) + if (hasPrivilege(tx, mustDeleteAcct) && result == tesSUCCESS) { if (accountsDeleted_ == 1) return true; @@ -399,9 +470,8 @@ AccountRootsNotDeleted::finalize( // A successful AMMWithdraw/AMMClawback MAY delete one account root // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw // deletes the AMM account, accountsDeleted_ is set if it is deleted. - if ((tx.getTxnType() == ttAMM_WITHDRAW || - tx.getTxnType() == ttAMM_CLAWBACK) && - result == tesSUCCESS && accountsDeleted_ == 1) + if (hasPrivilege(tx, mayDeleteAcct) && result == tesSUCCESS && + accountsDeleted_ == 1) return true; if (accountsDeleted_ == 0) @@ -436,7 +506,8 @@ AccountRootsDeletedClean::finalize( // feature is enabled. Enabled, or not, though, a fatal-level message will // be logged [[maybe_unused]] bool const enforce = - view.rules().enabled(featureInvariantsV1_1); + view.rules().enabled(featureInvariantsV1_1) || + view.rules().enabled(featureSingleAssetVault); auto const objectExists = [&view, enforce, &j](auto const& keylet) { (void)enforce; @@ -455,6 +526,8 @@ AccountRootsDeletedClean::finalize( JLOG(j.fatal()) << "Invariant failed: account deletion left behind a " << typeName << " object"; + // The comment above starting with "assert(enforce)" explains this + // assert. XRPL_ASSERT( enforce, "ripple::AccountRootsDeletedClean::finalize::objectExists : " @@ -489,11 +562,16 @@ AccountRootsDeletedClean::finalize( return false; } - // Keys directly stored in the AccountRoot object - if (auto const ammKey = accountSLE->at(~sfAMMID)) + // If the account is a pseudo account, then the linked object must + // also be deleted. e.g. AMM, Vault, etc. + for (auto const& field : getPseudoAccountFields()) { - if (objectExists(keylet::amm(*ammKey)) && enforce) - return false; + if (accountSLE->isFieldPresent(*field)) + { + auto const key = accountSLE->getFieldH256(*field); + if (objectExists(keylet::unchecked(key)) && enforce) + return false; + } } } @@ -513,41 +591,23 @@ LedgerEntryTypesMatch::visitEntry( if (after) { +#pragma push_macro("LEDGER_ENTRY") +#undef LEDGER_ENTRY + +#define LEDGER_ENTRY(tag, ...) case tag: + switch (after->getType()) { - case ltACCOUNT_ROOT: - case ltDELEGATE: - case ltDIR_NODE: - case ltRIPPLE_STATE: - case ltTICKET: - case ltSIGNER_LIST: - case ltOFFER: - case ltLEDGER_HASHES: - case ltAMENDMENTS: - case ltFEE_SETTINGS: - case ltESCROW: - case ltPAYCHAN: - case ltCHECK: - case ltDEPOSIT_PREAUTH: - case ltNEGATIVE_UNL: - case ltNFTOKEN_PAGE: - case ltNFTOKEN_OFFER: - case ltAMM: - case ltBRIDGE: - case ltXCHAIN_OWNED_CLAIM_ID: - case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID: - case ltDID: - case ltORACLE: - case ltMPTOKEN_ISSUANCE: - case ltMPTOKEN: - case ltCREDENTIAL: - case ltPERMISSIONED_DOMAIN: - case ltVAULT: - break; +#include + + break; default: invalidTypeAdded_ = true; break; } + +#undef LEDGER_ENTRY +#pragma pop_macro("LEDGER_ENTRY") } } @@ -713,6 +773,8 @@ TransfersNotFrozen::finalize( // just in case so rippled doesn't crash in release. if (!issuerSle) { + // The comment above starting with "assert(enforce)" explains this + // assert. XRPL_ASSERT( enforce, "ripple::TransfersNotFrozen::finalize : enforce " @@ -901,7 +963,7 @@ TransfersNotFrozen::validateFrozenState( } // AMMClawbacks are allowed to override some freeze rules - if ((!isAMMLine || globalFreeze) && tx.getTxnType() == ttAMM_CLAWBACK) + if ((!isAMMLine || globalFreeze) && hasPrivilege(tx, overrideFreeze)) { JLOG(j.debug()) << "Invariant check allowing funds to be moved " << (change.balanceChangeSign > 0 ? "to" : "from") @@ -912,6 +974,7 @@ TransfersNotFrozen::validateFrozenState( JLOG(j.fatal()) << "Invariant failed: Attempting to move frozen funds for " << tx.getTransactionID(); + // The comment above starting with "assert(enforce)" explains this assert. XRPL_ASSERT( enforce, "ripple::TransfersNotFrozen::validateFrozenState : enforce " @@ -961,17 +1024,12 @@ ValidNewAccountRoot::finalize( } // From this point on we know exactly one account was created. - if ((tx.getTxnType() == ttPAYMENT || tx.getTxnType() == ttAMM_CREATE || - tx.getTxnType() == ttVAULT_CREATE || - tx.getTxnType() == ttXCHAIN_ADD_CLAIM_ATTESTATION || - tx.getTxnType() == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) && - result == tesSUCCESS) + if (hasPrivilege(tx, createAcct | createPseudoAcct) && result == tesSUCCESS) { bool const pseudoAccount = (pseudoAccount_ && view.rules().enabled(featureSingleAssetVault)); - if (pseudoAccount && tx.getTxnType() != ttAMM_CREATE && - tx.getTxnType() != ttVAULT_CREATE) + if (pseudoAccount && !hasPrivilege(tx, createPseudoAcct)) { JLOG(j.fatal()) << "Invariant failed: pseudo-account created by a " "wrong transaction type"; @@ -1010,7 +1068,7 @@ ValidNewAccountRoot::finalize( JLOG(j.fatal()) << "Invariant failed: account root created illegally"; return false; -} +} // namespace ripple //------------------------------------------------------------------------------ @@ -1205,8 +1263,7 @@ NFTokenCountTracking::finalize( ReadView const& view, beast::Journal const& j) { - if (TxType const txType = tx.getTxnType(); - txType != ttNFTOKEN_MINT && txType != ttNFTOKEN_BURN) + if (!hasPrivilege(tx, changeNFTCounts)) { if (beforeMintedTotal != afterMintedTotal) { @@ -1391,13 +1448,12 @@ ValidMPTIssuance::finalize( STTx const& tx, TER const result, XRPAmount const _fee, - ReadView const& _view, + ReadView const& view, beast::Journal const& j) { if (result == tesSUCCESS) { - if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_CREATE || - tx.getTxnType() == ttVAULT_CREATE) + if (hasPrivilege(tx, createMPTIssuance)) { if (mptIssuancesCreated_ == 0) { @@ -1418,8 +1474,7 @@ ValidMPTIssuance::finalize( return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0; } - if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_DESTROY || - tx.getTxnType() == ttVAULT_DELETE) + if (hasPrivilege(tx, destroyMPTIssuance)) { if (mptIssuancesDeleted_ == 0) { @@ -1440,8 +1495,17 @@ ValidMPTIssuance::finalize( return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1; } - if (tx.getTxnType() == ttMPTOKEN_AUTHORIZE || - tx.getTxnType() == ttVAULT_DEPOSIT) + // ttESCROW_FINISH may authorize an MPT, but it can't have the + // mayAuthorizeMPT privilege, because that may cause + // non-amendment-gated side effects. + bool const enforceEscrowFinish = (tx.getTxnType() == ttESCROW_FINISH) && + (view.rules().enabled(featureSingleAssetVault) + /* + TODO: Uncomment when LendingProtocol is defined + || view.rules().enabled(featureLendingProtocol)*/ + ); + if (hasPrivilege(tx, mustAuthorizeMPT | mayAuthorizeMPT) || + enforceEscrowFinish) { bool const submittedByIssuer = tx.isFieldPresent(sfHolder); @@ -1467,7 +1531,7 @@ ValidMPTIssuance::finalize( return false; } else if ( - !submittedByIssuer && (tx.getTxnType() != ttVAULT_DEPOSIT) && + !submittedByIssuer && hasPrivilege(tx, mustAuthorizeMPT) && (mptokensCreated_ + mptokensDeleted_ != 1)) { // if the holder submitted this tx, then a mptoken must be @@ -1480,41 +1544,21 @@ ValidMPTIssuance::finalize( return true; } - - if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_SET) + if (tx.getTxnType() == ttESCROW_FINISH) { - if (mptIssuancesDeleted_ > 0) - { - JLOG(j.fatal()) << "Invariant failed: MPT issuance set " - "succeeded while removing MPT issuances"; - } - else if (mptIssuancesCreated_ > 0) - { - JLOG(j.fatal()) << "Invariant failed: MPT issuance set " - "succeeded while creating MPT issuances"; - } - else if (mptokensDeleted_ > 0) - { - JLOG(j.fatal()) << "Invariant failed: MPT issuance set " - "succeeded while removing MPTokens"; - } - else if (mptokensCreated_ > 0) - { - JLOG(j.fatal()) << "Invariant failed: MPT issuance set " - "succeeded while creating MPTokens"; - } - - return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 && - mptokensCreated_ == 0 && mptokensDeleted_ == 0; + // ttESCROW_FINISH may authorize an MPT, but it can't have the + // mayAuthorizeMPT privilege, because that may cause + // non-amendment-gated side effects. + XRPL_ASSERT_PARTS( + !enforceEscrowFinish, + "ripple::ValidMPTIssuance::finalize", + "not escrow finish tx"); + return true; } - if (tx.getTxnType() == ttESCROW_FINISH) - return true; - - if ((tx.getTxnType() == ttVAULT_CLAWBACK || - tx.getTxnType() == ttVAULT_WITHDRAW) && - mptokensDeleted_ == 1 && mptokensCreated_ == 0 && - mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0) + if (hasPrivilege(tx, mayDeleteMPT) && mptokensDeleted_ == 1 && + mptokensCreated_ == 0 && mptIssuancesCreated_ == 0 && + mptIssuancesDeleted_ == 0) return true; } @@ -1640,6 +1684,104 @@ ValidPermissionedDomain::finalize( (sleStatus_[1] ? check(*sleStatus_[1], j) : true); } +//------------------------------------------------------------------------------ + +void +ValidPseudoAccounts::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (isDelete) + // Deletion is ignored + return; + + if (after && after->getType() == ltACCOUNT_ROOT) + { + bool const isPseudo = [&]() { + // isPseudoAccount checks that any of the pseudo-account fields are + // set. + if (isPseudoAccount(after)) + return true; + // Not all pseudo-accounts have a zero sequence, but all accounts + // with a zero sequence had better be pseudo-accounts. + if (after->at(sfSequence) == 0) + return true; + + return false; + }(); + if (isPseudo) + { + // Pseudo accounts must have the following properties: + // 1. Exactly one of the pseudo-account fields is set. + // 2. The sequence number is not changed. + // 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth + // flags are set. + // 4. The RegularKey is not set. + { + std::vector const& fields = + getPseudoAccountFields(); + + auto const numFields = std::count_if( + fields.begin(), + fields.end(), + [&after](SField const* sf) -> bool { + return after->isFieldPresent(*sf); + }); + if (numFields != 1) + { + std::stringstream error; + error << "pseudo-account has " << numFields + << " pseudo-account fields set"; + errors_.emplace_back(error.str()); + } + } + if (before && before->at(sfSequence) != after->at(sfSequence)) + { + errors_.emplace_back("pseudo-account sequence changed"); + } + if (!after->isFlag( + lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth)) + { + errors_.emplace_back("pseudo-account flags are not set"); + } + if (after->isFieldPresent(sfRegularKey)) + { + errors_.emplace_back("pseudo-account has a regular key"); + } + } + } +} + +bool +ValidPseudoAccounts::finalize( + STTx const& tx, + TER const, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + bool const enforce = view.rules().enabled(featureSingleAssetVault); + + // The comment above starting with "assert(enforce)" explains this assert. + XRPL_ASSERT( + errors_.empty() || enforce, + "ripple::ValidPseudoAccounts::finalize : no bad " + "changes or enforce invariant"); + if (!errors_.empty()) + { + for (auto const& error : errors_) + { + JLOG(j.fatal()) << "Invariant failed: " << error; + } + if (enforce) + return false; + } + return true; +} + +//------------------------------------------------------------------------------ + void ValidPermissionedDEX::visitEntry( bool, diff --git a/src/xrpld/app/tx/detail/InvariantCheck.h b/src/xrpld/app/tx/detail/InvariantCheck.h index 529c05ce0e..5444f2f3a9 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.h +++ b/src/xrpld/app/tx/detail/InvariantCheck.h @@ -619,6 +619,34 @@ public: beast::Journal const&); }; +/** + * @brief Invariants: Pseudo-accounts have valid and consisent properties + * + * Pseudo-accounts have certain properties, and some of those properties are + * unique to pseudo-accounts. Check that all pseudo-accounts are following the + * rules, and that only pseudo-accounts look like pseudo-accounts. + * + */ +class ValidPseudoAccounts +{ + std::vector errors_; + +public: + void + visitEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&); + + bool + finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const&); +}; + class ValidPermissionedDEX { bool regularOffers_ = false; @@ -725,7 +753,8 @@ using InvariantChecks = std::tuple< ValidMPTIssuance, ValidPermissionedDomain, ValidPermissionedDEX, - ValidAMM>; + ValidAMM, + ValidPseudoAccounts>; /** * @brief get a tuple of all invariant checks diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 8f881d7252..fd396e4556 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -206,7 +206,10 @@ preflight2(PreflightContext const& ctx) //------------------------------------------------------------------------------ Transactor::Transactor(ApplyContext& ctx) - : ctx_(ctx), j_(ctx.journal), account_(ctx.tx.getAccountID(sfAccount)) + : ctx_(ctx) + , sink_(ctx.journal, to_short_string(ctx.tx.getTransactionID()) + " ") + , j_(sink_) + , account_(ctx.tx.getAccountID(sfAccount)) { } diff --git a/src/xrpld/app/tx/detail/Transactor.h b/src/xrpld/app/tx/detail/Transactor.h index 42d4861a63..e94b93523d 100644 --- a/src/xrpld/app/tx/detail/Transactor.h +++ b/src/xrpld/app/tx/detail/Transactor.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -138,6 +139,7 @@ class Transactor { protected: ApplyContext& ctx_; + beast::WrappedSink sink_; beast::Journal const j_; AccountID const account_; diff --git a/src/xrpld/app/tx/detail/applySteps.cpp b/src/xrpld/app/tx/detail/applySteps.cpp index 03ef7244f8..543bedcd47 100644 --- a/src/xrpld/app/tx/detail/applySteps.cpp +++ b/src/xrpld/app/tx/detail/applySteps.cpp @@ -18,57 +18,20 @@ //============================================================================== #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#pragma push_macro("TRANSACTION") +#undef TRANSACTION + +// Do nothing +#define TRANSACTION(...) +#define TRANSACTION_INCLUDE 1 + +#include + +#undef TRANSACTION +#pragma pop_macro("TRANSACTION") + +// DO NOT INCLUDE TRANSACTOR HEADER FILES HERE. +// See the instructions at the top of transactions.macro instead. #include diff --git a/src/xrpld/rpc/detail/RPCHelpers.cpp b/src/xrpld/rpc/detail/RPCHelpers.cpp index 4b28d44253..edb91611be 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCHelpers.cpp @@ -941,7 +941,7 @@ chooseLedgerEntryType(Json::Value const& params) #pragma push_macro("LEDGER_ENTRY") #undef LEDGER_ENTRY -#define LEDGER_ENTRY(tag, value, name, rpcName, fields) \ +#define LEDGER_ENTRY(tag, value, name, rpcName, ...) \ {jss::name, jss::rpcName, tag}, #include