From 9cb049276dcf166540f74bfc85c9d8581a163527 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Wed, 20 May 2026 21:44:09 +0200 Subject: [PATCH 001/135] feat: Propagate underlying MPT flags to vault shares (#7077) Signed-off-by: dependabot[bot] Co-authored-by: Denis Angell Co-authored-by: Fomo <508629+shortthefomo@users.noreply.github.com> Co-authored-by: Bart Co-authored-by: Ayaz Salikhov Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- include/xrpl/ledger/View.h | 2 +- include/xrpl/ledger/helpers/MPTokenHelpers.h | 70 +- .../xrpl/ledger/helpers/RippleStateHelpers.h | 4 +- include/xrpl/ledger/helpers/TokenHelpers.h | 33 +- .../xrpl/protocol/detail/ledger_entries.macro | 1 + include/xrpl/protocol/detail/sfields.macro | 1 + .../ledger_entries/MPTokenIssuance.h | 35 + include/xrpl/tx/invariants/MPTInvariant.h | 15 + .../transactors/token/MPTokenIssuanceCreate.h | 23 +- src/libxrpl/ledger/View.cpp | 31 +- src/libxrpl/ledger/helpers/MPTokenHelpers.cpp | 153 +++- src/libxrpl/ledger/helpers/TokenHelpers.cpp | 28 +- src/libxrpl/tx/invariants/MPTInvariant.cpp | 88 +++ .../lending/LoanBrokerCoverWithdraw.cpp | 10 +- .../token/MPTokenIssuanceCreate.cpp | 22 + .../tx/transactors/vault/VaultCreate.cpp | 26 +- .../tx/transactors/vault/VaultWithdraw.cpp | 9 +- src/test/app/Invariants_test.cpp | 110 ++- src/test/app/Loan_test.cpp | 229 ++++-- src/test/app/MPToken_test.cpp | 19 +- src/test/app/Vault_test.cpp | 680 +++++++++++++++++- .../ledger_entries/MPTokenIssuanceTests.cpp | 27 + 22 files changed, 1483 insertions(+), 133 deletions(-) diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index 4958a89d8c..0d76c98a73 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -57,7 +57,7 @@ isVaultPseudoAccountFrozen( ReadView const& view, AccountID const& account, MPTIssue const& mptShare, - int depth); + std::uint8_t depth); [[nodiscard]] bool isLPTokenFrozen( diff --git a/include/xrpl/ledger/helpers/MPTokenHelpers.h b/include/xrpl/ledger/helpers/MPTokenHelpers.h index 6544b18dd1..491bd1933e 100644 --- a/include/xrpl/ledger/helpers/MPTokenHelpers.h +++ b/include/xrpl/ledger/helpers/MPTokenHelpers.h @@ -27,14 +27,18 @@ isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue); isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue); [[nodiscard]] bool -isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth = 0); +isFrozen( + ReadView const& view, + AccountID const& account, + MPTIssue const& mptIssue, + std::uint8_t depth = 0); [[nodiscard]] bool isAnyFrozen( ReadView const& view, std::initializer_list const& accounts, MPTIssue const& mptIssue, - int depth = 0); + std::uint8_t depth = 0); //------------------------------------------------------------------------------ // @@ -88,7 +92,7 @@ requireAuth( MPTIssue const& mptIssue, AccountID const& account, AuthType authType = AuthType::Legacy, - int depth = 0); + std::uint8_t depth = 0); /** Enforce account has MPToken to match its authorization. * @@ -104,22 +108,68 @@ enforceMPTokenAuthorization( XRPAmount const& priorBalance, beast::Journal j); -/** Check if the destination account is allowed - * to receive MPT. Return tecNO_AUTH if it doesn't - * and tesSUCCESS otherwise. +/** Resolve the underlying asset of a vault share. + * + * Reads sfReferenceHolding from @p sleShareIssuance to determine which + * asset the vault wraps. @p sleHolding must be the SLE that + * sfReferenceHolding points to — either an ltMPTOKEN (returns its + * MPTIssue) or an ltRIPPLE_STATE (returns its low/high Issue). + * + * @pre Both SLEs must exist and @p sleHolding must be of type ltMPTOKEN + * or ltRIPPLE_STATE. Passing any other type is undefined behaviour. + * @param sleShareIssuance MPTokenIssuance SLE for the vault share token. + * @param sleHolding SLE referenced by sfReferenceHolding. + * @return The underlying Asset (MPTIssue or Issue). + */ +[[nodiscard]] Asset +assetOfHolding(SLE const& sleShareIssuance, SLE const& sleHolding); + +/** Check whether @p to may receive the given MPT from @p from. + * + * The check passes when any of the following is true: + * - @p waive is WaiveMPTCanTransfer::Yes (recovery-path exemption), or + * - @p from or @p to is the issuer, or + * - lsfMPTCanTransfer is set on the MPTokenIssuance. + * + * For vault shares (MPTokenIssuances that carry sfReferenceHolding) the + * check recurses into the underlying asset's transferability. This + * recursion is defensive; vault-of-vault-shares is rejected at vault + * creation, so in practice depth never exceeds 1. + * + * @param view Ledger state to read from. + * @param mptIssue The MPT issuance being transferred. + * @param from Sending account. + * @param to Receiving account. + * @param waive WaiveMPTCanTransfer::Yes skips the lsfMPTCanTransfer + * check. Use for recovery paths (e.g. unwinding SAV or + * Lending Protocol positions after an issuer revokes + * transferability). + * @param depth Recursion depth; bounded at kMaxAssetCheckDepth. + * @return tesSUCCESS if the transfer is allowed, tecNO_AUTH otherwise. */ [[nodiscard]] TER canTransfer( ReadView const& view, MPTIssue const& mptIssue, AccountID const& from, - AccountID const& to); + AccountID const& to, + WaiveMPTCanTransfer waive = WaiveMPTCanTransfer::No, + std::uint8_t depth = 0); -/** Check if Asset can be traded on DEX. return tecNO_PERMISSION - * if it doesn't and tesSUCCESS otherwise. +/** Check whether @p asset may be traded on the DEX. + * + * For IOU assets the check delegates to the existing offer/AMM freeze + * logic. For MPT assets it checks lsfMPTCanTrade on the MPTokenIssuance. + * Vault shares recurse into the underlying asset's tradability via + * sfReferenceHolding; depth is bounded at kMaxAssetCheckDepth. + * + * @param view Ledger state to read from. + * @param asset The asset to check. + * @param depth Recursion depth; bounded at kMaxAssetCheckDepth. + * @return tesSUCCESS if trading is allowed, tecNO_PERMISSION otherwise. */ [[nodiscard]] TER -canTrade(ReadView const& view, Asset const& asset); +canTrade(ReadView const& view, Asset const& asset, std::uint8_t depth = 0); //------------------------------------------------------------------------------ // diff --git a/include/xrpl/ledger/helpers/RippleStateHelpers.h b/include/xrpl/ledger/helpers/RippleStateHelpers.h index 17b0f7673e..2616a6d5c9 100644 --- a/include/xrpl/ledger/helpers/RippleStateHelpers.h +++ b/include/xrpl/ledger/helpers/RippleStateHelpers.h @@ -93,7 +93,7 @@ isFrozen(ReadView const& view, AccountID const& account, Issue const& issue) // Overload with depth parameter for uniformity with MPTIssue version. // The depth parameter is ignored for IOUs since they don't have vault recursion. [[nodiscard]] inline bool -isFrozen(ReadView const& view, AccountID const& account, Issue const& issue, int /*depth*/) +isFrozen(ReadView const& view, AccountID const& account, Issue const& issue, std::uint8_t /*depth*/) { return isFrozen(view, account, issue); } @@ -110,7 +110,7 @@ isDeepFrozen( ReadView const& view, AccountID const& account, Issue const& issue, - int = 0 /*ignored*/) + std::uint8_t = 0 /*ignored*/) { return isDeepFrozen(view, account, issue.currency, issue.account); } diff --git a/include/xrpl/ledger/helpers/TokenHelpers.h b/include/xrpl/ledger/helpers/TokenHelpers.h index 3d41ac47cd..b47286a5d5 100644 --- a/include/xrpl/ledger/helpers/TokenHelpers.h +++ b/include/xrpl/ledger/helpers/TokenHelpers.h @@ -34,6 +34,15 @@ enum class WaiveTransferFee : bool { No = false, Yes }; /** Controls whether accountSend is allowed to overflow OutstandingAmount **/ enum class AllowMPTOverflow : bool { No = false, Yes }; +/** Controls whether canTransfer enforces lsfMPTCanTransfer on MPTs. + * + * Default is No (enforce). Use Yes at call sites that must remain available + * even when an MPT issuer has cleared lsfMPTCanTransfer - for example, + * unwinding existing positions in SAV or the Lending Protocol. Has no + * effect on the IOU branch of canTransfer. + */ +enum class WaiveMPTCanTransfer : bool { No = false, Yes }; + /* Check if MPToken (for MPT) or trust line (for IOU) exists: * - StrongAuth - before checking if authorization is required * - WeakAuth @@ -63,7 +72,11 @@ isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& * purely defensive, as we currently do not allow such vaults to be created. */ [[nodiscard]] bool -isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0); +isFrozen( + ReadView const& view, + AccountID const& account, + Asset const& asset, + std::uint8_t depth = 0); [[nodiscard]] TER checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue); @@ -85,14 +98,14 @@ isAnyFrozen( ReadView const& view, std::initializer_list const& accounts, Asset const& asset, - int depth = 0); + std::uint8_t depth = 0); [[nodiscard]] bool isDeepFrozen( ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, - int depth = 0); + std::uint8_t depth = 0); /** * isFrozen check is recursive for MPT shares in a vault, descending to @@ -100,7 +113,11 @@ isDeepFrozen( * purely defensive, as we currently do not allow such vaults to be created. */ [[nodiscard]] bool -isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0); +isDeepFrozen( + ReadView const& view, + AccountID const& account, + Asset const& asset, + std::uint8_t depth = 0); [[nodiscard]] TER checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue); @@ -234,7 +251,13 @@ requireAuth( AuthType authType = AuthType::Legacy); [[nodiscard]] TER -canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, AccountID const& to); +canTransfer( + ReadView const& view, + Asset const& asset, + AccountID const& from, + AccountID const& to, + WaiveMPTCanTransfer waive = WaiveMPTCanTransfer::No, + std::uint8_t depth = 0); //------------------------------------------------------------------------------ // diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index c4b392a92f..19b8b44fe6 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -400,6 +400,7 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({ {sfPreviousTxnLgrSeq, SoeRequired}, {sfDomainID, SoeOptional}, {sfMutableFlags, SoeDefault}, + {sfReferenceHolding, SoeOptional}, })) /** A ledger object which tracks MPToken diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 5d1689dce9..01bb4fc480 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -205,6 +205,7 @@ TYPED_SFIELD(sfParentBatchID, UINT256, 36) TYPED_SFIELD(sfLoanBrokerID, UINT256, 37, SField::kSmdPseudoAccount | SField::kSmdDefault) TYPED_SFIELD(sfLoanID, UINT256, 38) +TYPED_SFIELD(sfReferenceHolding, UINT256, 39) // number (common) TYPED_SFIELD(sfNumber, NUMBER, 1) diff --git a/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h b/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h index 8377fdf1a4..7f772b1c74 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h +++ b/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h @@ -278,6 +278,30 @@ public: { return this->sle_->isFieldPresent(sfMutableFlags); } + + /** + * @brief Get sfReferenceHolding (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getReferenceHolding() const + { + if (hasReferenceHolding()) + return this->sle_->at(sfReferenceHolding); + return std::nullopt; + } + + /** + * @brief Check if sfReferenceHolding is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasReferenceHolding() const + { + return this->sle_->isFieldPresent(sfReferenceHolding); + } }; /** @@ -469,6 +493,17 @@ public: return *this; } + /** + * @brief Set sfReferenceHolding (SoeOptional) + * @return Reference to this builder for method chaining. + */ + MPTokenIssuanceBuilder& + setReferenceHolding(std::decay_t const& value) + { + object_[sfReferenceHolding] = value; + return *this; + } + /** * @brief Build and return the completed MPTokenIssuance wrapper. * @param index The ledger entry index. diff --git a/include/xrpl/tx/invariants/MPTInvariant.h b/include/xrpl/tx/invariants/MPTInvariant.h index 7f405814e7..f2b0a18131 100644 --- a/include/xrpl/tx/invariants/MPTInvariant.h +++ b/include/xrpl/tx/invariants/MPTInvariant.h @@ -2,10 +2,13 @@ #include #include +#include #include #include #include +#include +#include namespace xrpl { @@ -20,6 +23,18 @@ class ValidMPTIssuance // MPToken by an issuer bool mptCreatedByIssuer_ = false; + /// sfReferenceHolding is intended to be set exactly once at vault + /// creation and immutable thereafter; true when that rule was violated. + bool referenceHoldingSetOnCreate_ = false; + + /// True when sfReferenceHolding was mutated on an existing MPTokenIssuance. + bool referenceHoldingMutated_ = false; + + /// MPTokens and RippleStates deleted during apply. finalize() checks each + /// holder's AccountRoot to detect vault pseudo-account holdings deleted + /// outside VaultDelete. All these checks are gated on fixCleanup3_2_0. + std::vector> deletedHoldings_; + public: void visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); diff --git a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h index 3861c19afc..6c59d85548 100644 --- a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h +++ b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h @@ -6,23 +6,28 @@ namespace xrpl { +// NOLINTBEGIN(readability-redundant-member-init) struct MPTCreateArgs { std::optional priorBalance; AccountID const& account; std::uint32_t sequence = 0; std::uint32_t flags = 0; - std::optional maxAmount = - std::nullopt; // NOLINT(readability-redundant-member-init) - std::optional assetScale = - std::nullopt; // NOLINT(readability-redundant-member-init) - std::optional transferFee = - std::nullopt; // NOLINT(readability-redundant-member-init) + std::optional maxAmount = std::nullopt; + std::optional assetScale = std::nullopt; + std::optional transferFee = std::nullopt; std::optional const& metadata{}; - std::optional domainId = std::nullopt; // NOLINT(readability-redundant-member-init) - std::optional mutableFlags = - std::nullopt; // NOLINT(readability-redundant-member-init) + std::optional domainId = std::nullopt; + std::optional mutableFlags = std::nullopt; + // Set only by callers that issue an MPT representing a wrapped asset + // (e.g. VaultCreate's share token). The keylet must point to an + // existing MPToken or RippleState owned by `account`. Surfaces on + // the resulting MPTokenIssuance via the optional sfReferenceHolding + // field. Used by readers (canTransfer, canTrade, freezing) to + // inherit the underlying asset's transferability. + std::optional referenceHolding = std::nullopt; }; +// NOLINTEND(readability-redundant-member-init) class MPTokenIssuanceCreate : public Transactor { diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index 4b67bb9867..c62d79dcac 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -57,19 +58,45 @@ isVaultPseudoAccountFrozen( ReadView const& view, AccountID const& account, MPTIssue const& mptShare, - int depth) + std::uint8_t depth) { if (!view.rules().enabled(featureSingleAssetVault)) return false; if (depth >= kMaxAssetCheckDepth) - return true; // LCOV_EXCL_LINE + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::View::isVaultPseudoAccountFrozen : reached asset check depth"); + return true; + // LCOV_EXCL_STOP + } auto const mptIssuance = view.read(keylet::mptIssuance(mptShare.getMptID())); if (mptIssuance == nullptr) return false; // zero MPToken won't block deletion of MPTokenIssuance auto const issuer = mptIssuance->getAccountID(sfIssuer); + + // Post-fixCleanup3_2_0: vault shares carry sfReferenceHolding pointing + // to the vault pseudo's MPToken or RippleState for the underlying. + // Read it to derive the underlying asset and recurse, skipping the + // issuer-account-then-vault chain. Pre-amendment shares (no field) + // fall back to the chain lookup below. + if (mptIssuance->isFieldPresent(sfReferenceHolding)) + { + auto const sleHolding = + view.read(keylet::unchecked(mptIssuance->getFieldH256(sfReferenceHolding))); + if (!sleHolding) + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : dangling sfReferenceHolding"); + return false; + // LCOV_EXCL_STOP + } + return isAnyFrozen( + view, {issuer, account}, assetOfHolding(*mptIssuance, *sleHolding), depth + 1); + } + auto const mptIssuer = view.read(keylet::account(issuer)); if (mptIssuer == nullptr) { diff --git a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp index 925f37c8ed..542a5b60ed 100644 --- a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp @@ -55,7 +55,11 @@ isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue cons } bool -isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth) +isFrozen( + ReadView const& view, + AccountID const& account, + MPTIssue const& mptIssue, + std::uint8_t depth) { return isGlobalFrozen(view, mptIssue) || isIndividualFrozen(view, account, mptIssue) || isVaultPseudoAccountFrozen(view, account, mptIssue, depth); @@ -66,7 +70,7 @@ isAnyFrozen( ReadView const& view, std::initializer_list const& accounts, MPTIssue const& mptIssue, - int depth) + std::uint8_t depth) { if (isGlobalFrozen(view, mptIssue)) return true; @@ -303,7 +307,7 @@ requireAuth( MPTIssue const& mptIssue, AccountID const& account, AuthType authType, - int depth) + std::uint8_t depth) { auto const mptID = keylet::mptIssuance(mptIssue.getMptID()); auto const sleIssuance = view.read(mptID); @@ -321,7 +325,12 @@ requireAuth( if (featureSAVEnabled) { if (depth >= kMaxAssetCheckDepth) - return tecINTERNAL; // LCOV_EXCL_LINE + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::MPTokenHelpers::requireAuth : reached asset check depth"); + return tecINTERNAL; + // LCOV_EXCL_STOP + } // requireAuth is recursive if the issuer is a vault pseudo-account auto const sleIssuer = view.read(keylet::account(mptIssuer)); @@ -489,28 +498,88 @@ enforceMPTokenAuthorization( // LCOV_EXCL_STOP } +[[nodiscard]] Asset +assetOfHolding(SLE const& sleShareIssuance, SLE const& sleHolding) +{ + XRPL_ASSERT_PARTS( + sleHolding.getType() == ltRIPPLE_STATE || sleHolding.getType() == ltMPTOKEN, + "xrpl::assetOfHolding", + "unexpected holding type"); + XRPL_ASSERT_PARTS( + sleShareIssuance.getType() == ltMPTOKEN_ISSUANCE, + "xrpl::assetOfHolding", + "not SLE MPTokenIssuance"); + + if (sleHolding.getType() == ltMPTOKEN) + return MPTIssue{sleHolding.getFieldH192(sfMPTokenIssuanceID)}; + + auto const vaultPseudo = sleShareIssuance.at(sfIssuer); + auto const lowLimit = sleHolding.getFieldAmount(sfLowLimit); + auto const highLimit = sleHolding.getFieldAmount(sfHighLimit); + auto const& iouIssuer = + (lowLimit.getIssuer() != vaultPseudo) ? lowLimit.getIssuer() : highLimit.getIssuer(); + return Issue{lowLimit.get().currency, iouIssuer}; +} + TER canTransfer( ReadView const& view, MPTIssue const& mptIssue, AccountID const& from, - AccountID const& to) + AccountID const& to, + WaiveMPTCanTransfer waive, + std::uint8_t depth) { auto const mptID = keylet::mptIssuance(mptIssue.getMptID()); auto const sleIssuance = view.read(mptID); if (!sleIssuance) return tecOBJECT_NOT_FOUND; + auto const issuer = (*sleIssuance)[sfIssuer]; + if (waive == WaiveMPTCanTransfer::Yes || from == issuer || to == issuer) + return tesSUCCESS; + if (!sleIssuance->isFlag(lsfMPTCanTransfer)) + return TER{tecNO_AUTH}; + + // Post-fixCleanup3_2_0: vault shares carry sfReferenceHolding pointing + // to the vault pseudo's MPToken or RippleState for the underlying asset. + // Third-party transfers inherit the underlying's transferability. + // Issuer-involving transfers and waived callers returned tesSUCCESS above. + // + // The recursive call always passes WaiveMPTCanTransfer::No so that + // a waived outer caller does not transitively unlock the underlying. + if (view.rules().enabled(fixCleanup3_2_0) && sleIssuance->isFieldPresent(sfReferenceHolding)) { - if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer]) - return TER{tecNO_AUTH}; + // Defensive depth bound on the inheritance recursion. Unreachable + // in practice (vault-of-vault-shares is forbidden at VaultCreate). + if (depth >= kMaxAssetCheckDepth) + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::MPTokenHelpers::canTransfer : reached asset check depth"); + return tecINTERNAL; + // LCOV_EXCL_STOP + } + + auto const sleHolding = + view.read(keylet::unchecked(sleIssuance->getFieldH256(sfReferenceHolding))); + if (!sleHolding) + return tefINTERNAL; // LCOV_EXCL_LINE + + return canTransfer( + view, + assetOfHolding(*sleIssuance, *sleHolding), + from, + to, + WaiveMPTCanTransfer::No, + depth + 1); } + return tesSUCCESS; } TER -canTrade(ReadView const& view, Asset const& asset) +canTrade(ReadView const& view, Asset const& asset, std::uint8_t depth) { return asset.visit( [&](Issue const&) -> TER { return tesSUCCESS; }, @@ -520,6 +589,31 @@ canTrade(ReadView const& view, Asset const& asset) return tecOBJECT_NOT_FOUND; if (!sleIssuance->isFlag(lsfMPTCanTrade)) return tecNO_PERMISSION; + + // Post-fixCleanup3_2_0: vault shares inherit the underlying + // asset's tradability. A share whose underlying has been + // removed from trading cannot itself be placed on the DEX. + if (view.rules().enabled(fixCleanup3_2_0) && + sleIssuance->isFieldPresent(sfReferenceHolding)) + { + // Defensive depth bound on the inheritance recursion. + // Unreachable in practice (vault-of-vault-shares + // forbidden at VaultCreate). + if (depth >= kMaxAssetCheckDepth) + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::MPTokenHelpers::canTrade : reached asset check depth"); + return tecINTERNAL; + // LCOV_EXCL_STOP + } + auto const sleHolding = + view.read(keylet::unchecked(sleIssuance->getFieldH256(sfReferenceHolding))); + if (!sleHolding) + return tefINTERNAL; // LCOV_EXCL_LINE + + return canTrade(view, assetOfHolding(*sleIssuance, *sleHolding), depth + 1); + } + return tesSUCCESS; }); } @@ -892,7 +986,12 @@ issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amo } static TER -checkMPTAllowed(ReadView const& view, TxType txType, Asset const& asset, AccountID const& accountID) +checkMPTAllowed( + ReadView const& view, + TxType txType, + Asset const& asset, + AccountID const& accountID, + std::uint8_t depth = 0) { if (!asset.holds()) return tesSUCCESS; @@ -914,17 +1013,15 @@ checkMPTAllowed(ReadView const& view, TxType txType, Asset const& asset, Account if (!issuanceSle) return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE - auto const flags = issuanceSle->getFlags(); - - if ((flags & lsfMPTLocked) != 0u) + if (issuanceSle->isFlag(lsfMPTLocked)) return tecLOCKED; // LCOV_EXCL_LINE // Offer crossing and Payment - if ((flags & lsfMPTCanTrade) == 0) + if (!issuanceSle->isFlag(lsfMPTCanTrade)) return tecNO_PERMISSION; if (accountID != issuer) { - if ((flags & lsfMPTCanTransfer) == 0) + if (!issuanceSle->isFlag(lsfMPTCanTransfer)) return tecNO_PERMISSION; auto const mptSle = view.read(keylet::mptoken(issuanceKey.key, accountID)); @@ -935,6 +1032,34 @@ checkMPTAllowed(ReadView const& view, TxType txType, Asset const& asset, Account if (mptSle->isFlag(lsfMPTLocked)) return tecLOCKED; + + // Post-fixCleanup3_2_0: vault shares inherit the underlying + // asset's checks here too. Without this, a share could be + // placed on the AMM, in an Offer, or in a Check even after + // the issuer has restricted the underlying. Mirrors the + // canTransfer / canTrade inheritance for path-find-adjacent + // operations that don't go through canTransfer directly. + if (view.rules().enabled(fixCleanup3_2_0) && + issuanceSle->isFieldPresent(sfReferenceHolding)) + { + // Defensive depth bound on the inheritance recursion. + // Reachable only post-fixCleanup3_2_0 and unreachable in + // practice (vault-of-vault-shares forbidden at VaultCreate). + if (depth >= kMaxAssetCheckDepth) + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::MPTokenHelpers::checkMPTAllowed : reached asset check depth"); + return tecINTERNAL; + // LCOV_EXCL_STOP + } + auto const sleHolding = + view.read(keylet::unchecked(issuanceSle->getFieldH256(sfReferenceHolding))); + if (!sleHolding) + return tefINTERNAL; // LCOV_EXCL_LINE + + return checkMPTAllowed( + view, txType, assetOfHolding(*issuanceSle, *sleHolding), accountID, depth + 1); + } } return tesSUCCESS; diff --git a/src/libxrpl/ledger/helpers/TokenHelpers.cpp b/src/libxrpl/ledger/helpers/TokenHelpers.cpp index c9dccb884d..71019761ed 100644 --- a/src/libxrpl/ledger/helpers/TokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/TokenHelpers.cpp @@ -63,7 +63,7 @@ isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& } bool -isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth) +isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, std::uint8_t depth) { return std::visit( [&](auto const& issue) { return isFrozen(view, account, issue, depth); }, asset.value()); @@ -107,7 +107,7 @@ isAnyFrozen( ReadView const& view, std::initializer_list const& accounts, Asset const& asset, - int depth) + std::uint8_t depth) { return asset.visit( [&](Issue const& issue) { return isAnyFrozen(view, accounts, issue); }, @@ -115,7 +115,11 @@ isAnyFrozen( } bool -isDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth) +isDeepFrozen( + ReadView const& view, + AccountID const& account, + MPTIssue const& mptIssue, + std::uint8_t depth) { // Unlike IOUs, frozen / locked MPTs are not allowed to send or receive // funds, so checking "deep frozen" is the same as checking "frozen". @@ -123,7 +127,7 @@ isDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mpt } bool -isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth) +isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, std::uint8_t depth) { return std::visit( [&](auto const& issue) { return isDeepFrozen(view, account, issue, depth); }, @@ -500,13 +504,19 @@ requireAuth(ReadView const& view, Asset const& asset, AccountID const& account, } TER -canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, AccountID const& to) +canTransfer( + ReadView const& view, + Asset const& asset, + AccountID const& from, + AccountID const& to, + WaiveMPTCanTransfer waive, + std::uint8_t depth) { - return std::visit( - [&](TIss const& issue) -> TER { - return canTransfer(view, issue, from, to); + return asset.visit( + [&](MPTIssue const& issue) -> TER { + return canTransfer(view, issue, from, to, waive, depth); }, - asset.value()); + [&](Issue const& issue) -> TER { return canTransfer(view, issue, from, to); }); } //------------------------------------------------------------------------------ diff --git a/src/libxrpl/tx/invariants/MPTInvariant.cpp b/src/libxrpl/tx/invariants/MPTInvariant.cpp index 43379b0b86..ecba0b6227 100644 --- a/src/libxrpl/tx/invariants/MPTInvariant.cpp +++ b/src/libxrpl/tx/invariants/MPTInvariant.cpp @@ -5,11 +5,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -30,6 +32,13 @@ ValidMPTIssuance::visitEntry( std::shared_ptr const& before, std::shared_ptr const& after) { + // The sfReferenceHolding tracking and the deleted-holding capture are + // only meaningful post-fixCleanup3_2_0 (the field is never set + // pre-amendment, and the holding-deletion rule does not apply). + // Skip both blocks when the amendment is off so we avoid wasted work + // on the hot path. + bool const fix320Enabled = isFeatureEnabled(fixCleanup3_2_0); + if (after && after->getType() == ltMPTOKEN_ISSUANCE) { if (isDelete) @@ -39,6 +48,21 @@ ValidMPTIssuance::visitEntry( else if (!before) { mptIssuancesCreated_++; + if (fix320Enabled && after->isFieldPresent(sfReferenceHolding)) + referenceHoldingSetOnCreate_ = true; + } + else if (fix320Enabled) + { + // Modified issuance: detect any change to sfReferenceHolding. + bool const beforePresent = before->isFieldPresent(sfReferenceHolding); + bool const afterPresent = after->isFieldPresent(sfReferenceHolding); + if (beforePresent != afterPresent || + (afterPresent && + before->getFieldH256(sfReferenceHolding) != + after->getFieldH256(sfReferenceHolding))) + { + referenceHoldingMutated_ = true; + } } } @@ -47,6 +71,8 @@ ValidMPTIssuance::visitEntry( if (isDelete) { mptokensDeleted_++; + if (fix320Enabled) + deletedHoldings_.push_back(after); } else if (!before) { @@ -56,6 +82,11 @@ ValidMPTIssuance::visitEntry( mptCreatedByIssuer_ = true; } } + + // Capture deleted RippleState SLEs so finalize() can verify none of + // them were owned by a vault pseudo-account outside VaultDelete. + if (fix320Enabled && isDelete && after && after->getType() == ltRIPPLE_STATE) + deletedHoldings_.push_back(after); } bool @@ -68,6 +99,63 @@ ValidMPTIssuance::finalize( { auto const& rules = view.rules(); bool const mptV2Enabled = rules.enabled(featureMPTokensV2); + + // Post-fixCleanup3_2_0: + // - sfReferenceHolding is set only by VaultCreate at share-issuance + // creation, and is immutable thereafter. + // - A vault pseudo-account's MPToken or RippleState may only be + // deleted by VaultDelete; the share's sfReferenceHolding pointer + // must not dangle outside that controlled lifecycle. + if (rules.enabled(fixCleanup3_2_0)) + { + bool invariantPasses = true; + if (referenceHoldingMutated_) + { + JLOG(j.fatal()) << "Invariant failed: sfReferenceHolding was modified " + "on an existing MPTokenIssuance"; + invariantPasses = false; + } + if (referenceHoldingSetOnCreate_ && tx.getTxnType() != ttVAULT_CREATE) + { + JLOG(j.fatal()) << "Invariant failed: sfReferenceHolding set on a new " + "MPTokenIssuance by a non-VaultCreate transaction"; + invariantPasses = false; + } + if (!deletedHoldings_.empty() && tx.getTxnType() != ttVAULT_DELETE) + { + auto const isVaultPseudo = [&](AccountID const& acct) { + auto const sle = view.read(keylet::account(acct)); + return sle && sle->isFieldPresent(sfVaultID); + }; + for (auto const& sleHolding : deletedHoldings_) + { + bool offending = false; + if (sleHolding->getType() == ltMPTOKEN) + { + offending = isVaultPseudo(sleHolding->at(sfAccount)); + } + else // ltRIPPLE_STATE + { + auto const lowLimit = sleHolding->getFieldAmount(sfLowLimit); + auto const highLimit = sleHolding->getFieldAmount(sfHighLimit); + // Each limit's STAmount.issuer is the COUNTERPARTY of + // that side's owner: lowLimit's issuer is the high + // account, highLimit's issuer is the low account. + offending = + isVaultPseudo(lowLimit.getIssuer()) || isVaultPseudo(highLimit.getIssuer()); + } + if (offending) + { + JLOG(j.fatal()) << "Invariant failed: vault pseudo-account holding " + "deleted by a non-VaultDelete transaction"; + invariantPasses = false; + } + } + } + if (!invariantPasses) + return false; + } + if (isTesSuccess(result) || (mptV2Enabled && result == tecINCOMPLETE)) { [[maybe_unused]] diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp index 17a9683690..331c44b1e8 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -95,8 +96,13 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx) // The broker's pseudo-account is the source of funds. auto const pseudoAccountID = sleBroker->at(sfAccount); - // Cannot transfer a non-transferable Asset - if (auto const ret = canTransfer(ctx.view, vaultAsset, pseudoAccountID, dstAcct)) + // Post-fixCleanup3_2_0: cover withdraw is a recovery path that bypasses + // the lsfMPTCanTransfer flag check, so an issuer cannot trap a broker's + // first-loss capital. Other transferability checks (IOU NoRipple, freeze, + // requireAuth) still apply. + auto const waive = ctx.view.rules().enabled(fixCleanup3_2_0) ? WaiveMPTCanTransfer::Yes + : WaiveMPTCanTransfer::No; + if (auto const ret = canTransfer(ctx.view, vaultAsset, pseudoAccountID, dstAcct, waive)) return ret; // Withdrawal to a 3rd party destination account is essentially a transfer. diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp index 1440b67309..64d0b01f5e 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,11 @@ MPTokenIssuanceCreate::getFlagsMask(PreflightContext const& ctx) NotTEC MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) { + // sfReferenceHolding is set only internally by VaultCreate. Reject + // any user-submitted MPTokenIssuanceCreate that attempts to carry it. + if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx.isFieldPresent(sfReferenceHolding)) + return temMALFORMED; + // If the mutable flags field is included, at least one flag must be // specified. if (auto const mutableFlags = ctx.tx[~sfMutableFlags]; mutableFlags && @@ -141,6 +147,22 @@ MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreate if (args.mutableFlags) (*mptIssuance)[sfMutableFlags] = *args.mutableFlags; + if (args.referenceHolding) + { + // Defensive: the holding must already exist and be of an + // expected type. Callers (currently only VaultCreate) + // populate this after the pseudo-account's MPToken / + // RippleState has been installed. A missing holding here + // would dangle the pointer and is a programmer error. + auto const sleHolding = view.read(keylet::unchecked(*args.referenceHolding)); + if (!sleHolding) + return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + auto const type = sleHolding->getType(); + if (type != ltMPTOKEN && type != ltRIPPLE_STATE) + return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + (*mptIssuance)[sfReferenceHolding] = *args.referenceHolding; + } + view.insert(mptIssuance); } diff --git a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp index c6e1f28d57..7490f44619 100644 --- a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp @@ -1,12 +1,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -162,9 +164,9 @@ VaultCreate::doApply() auto maybePseudo = createPseudoAccount(view(), vault->key(), sfVaultID); if (!maybePseudo) return maybePseudo.error(); // LCOV_EXCL_LINE - auto& pseudo = *maybePseudo; - auto pseudoId = pseudo->at(sfAccount); - auto asset = tx[sfAsset]; + auto const& pseudo = *maybePseudo; + AccountID const pseudoId = pseudo->at(sfAccount); + auto const asset = tx[sfAsset]; if (auto ter = addEmptyHolding(view(), pseudoId, preFeeBalance_, asset, j_); !isTesSuccess(ter)) return ter; @@ -182,13 +184,24 @@ VaultCreate::doApply() // Note, here we are **not** creating an MPToken for the assets held in // the vault. That MPToken or TrustLine/RippleState is created above, in // addEmptyHolding. Here we are creating MPTokenIssuance for the shares - // in the vault - auto maybeShare = MPTokenIssuanceCreate::create( + // in the vault. + // + // Post-fixCleanup3_2_0: surface the vault pseudo's holding (MPToken + // for MPT, RippleState for IOU) on the share via sfReferenceHolding. + // XRP underlyings leave it unset. + auto const referenceHolding = [&]() -> std::optional { + if (!view().rules().enabled(fixCleanup3_2_0) || asset.native()) + return std::nullopt; + return asset.holds() + ? keylet::mptoken(asset.get().getMptID(), pseudoId).key + : keylet::line(pseudoId, asset.get()).key; + }(); + auto const maybeShare = MPTokenIssuanceCreate::create( view(), j_, { .priorBalance = std::nullopt, - .account = pseudoId->value(), + .account = pseudoId, .sequence = 1, .flags = mptFlags, .assetScale = scale, @@ -196,6 +209,7 @@ VaultCreate::doApply() .metadata = tx[~sfMPTokenMetadata], .domainId = tx[~sfDomainID], .mutableFlags = std::nullopt, + .referenceHolding = referenceHolding, }); if (!maybeShare) return maybeShare.error(); // LCOV_EXCL_LINE diff --git a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp index 136f9e17c6..ced82f6735 100644 --- a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp @@ -65,7 +65,14 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx) auto const& vaultAccount = vault->at(sfAccount); auto const& account = ctx.tx[sfAccount]; auto const& dstAcct = ctx.tx[~sfDestination].value_or(account); - if (auto ter = canTransfer(ctx.view, vaultAsset, vaultAccount, dstAcct); !isTesSuccess(ter)) + // Post-fixCleanup3_2_0: withdraw is a recovery path that bypasses the + // lsfMPTCanTransfer flag check, so an issuer cannot trap depositor funds. + // Other transferability checks (IOU NoRipple, freeze, requireAuth) still + // apply. + auto const waive = ctx.view.rules().enabled(fixCleanup3_2_0) ? WaiveMPTCanTransfer::Yes + : WaiveMPTCanTransfer::No; + if (auto ter = canTransfer(ctx.view, vaultAsset, vaultAccount, dstAcct, waive); + !isTesSuccess(ter)) { JLOG(ctx.j.debug()) << "VaultWithdraw: vault assets are non-transferable."; return ter; diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 775e18656b..21094e0142 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -1783,7 +1783,7 @@ class Invariants_test : public beast::unit_test::Suite for (std::size_t n = 0; n < numCreds; ++n) { auto credType = "cred_type" + std::to_string(n); - credentials.push_back({a2, credType}); + credentials.push_back({.issuer = a2, .credType = credType}); } std::uint32_t const seq = env.seq(a1); @@ -4103,6 +4103,114 @@ class Invariants_test : public beast::unit_test::Suite return true; }); } + + // sfReferenceHolding can only be set on creation by VaultCreate. A + // non-VaultCreate transaction that creates an MPTokenIssuance with + // sfReferenceHolding present must trip the invariant. + doInvariantCheck( + {{"sfReferenceHolding set on a new MPTokenIssuance by a " + "non-VaultCreate transaction"}}, + [](Account const& a1, Account const&, ApplyContext& ac) { + auto const sleAcct = ac.view().peek(keylet::account(a1.id())); + if (!sleAcct) + return false; + MPTIssue const mpt{makeMptID(sleAcct->getFieldU32(sfSequence), a1)}; + auto sleNew = std::make_shared(keylet::mptIssuance(mpt.getMptID())); + sleNew->setFieldH256(sfReferenceHolding, uint256{1}); + ac.view().insert(sleNew); + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_SET, [](STObject&) {}}); + + // sfReferenceHolding is immutable: changing the field on an + // existing MPTokenIssuance must trip the invariant. Set up a real + // vault via preclose (so the share issuance carries + // sfReferenceHolding), then mutate it in precheck to produce a + // before/after pair. + { + uint256 vaultKey; + doInvariantCheck( + {{"sfReferenceHolding was modified on an existing " + "MPTokenIssuance"}}, + [&](Account const&, Account const&, ApplyContext& ac) { + auto const sleVault = ac.view().peek(keylet::vault(vaultKey)); + if (!sleVault) + return false; + auto sleIssuance = + ac.view().peek(keylet::mptIssuance(sleVault->at(sfShareMPTID))); + if (!sleIssuance) + return false; + sleIssuance->setFieldH256(sfReferenceHolding, uint256{2}); + ac.view().update(sleIssuance); + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_SET, [](STObject&) {}}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, + [&](Account const& a1, Account const&, Env& env) { + Account const issuer{"issuer"}; + env.fund(XRP(10'000), issuer); + env.close(); + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock}); + PrettyAsset const asset = mptt.issuanceID(); + mptt.authorize({.account = a1}); + env.close(); + + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = a1, .asset = asset}); + env(tx); + env.close(); + vaultKey = keylet.key; + return true; + }); + } + + // A vault pseudo-account's MPToken cannot be deleted by anything + // other than a VaultDelete transaction. Set up a vault, then have + // an arbitrary tx erase the pseudo's MPToken in precheck. + { + uint256 vaultKey; + doInvariantCheck( + {{"vault pseudo-account holding deleted by a " + "non-VaultDelete transaction"}}, + [&](Account const&, Account const&, ApplyContext& ac) { + auto const sleVault = ac.view().peek(keylet::vault(vaultKey)); + if (!sleVault) + return false; + auto const sleIssuance = + ac.view().peek(keylet::mptIssuance(sleVault->at(sfShareMPTID))); + if (!sleIssuance || !sleIssuance->isFieldPresent(sfReferenceHolding)) + return false; + auto sleHolding = ac.view().peek( + keylet::unchecked(sleIssuance->getFieldH256(sfReferenceHolding))); + if (!sleHolding) + return false; + ac.view().erase(sleHolding); + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_SET, [](STObject&) {}}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, + [&](Account const& a1, Account const&, Env& env) { + Account const issuer{"issuer"}; + env.fund(XRP(10'000), issuer); + env.close(); + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock}); + PrettyAsset const asset = mptt.issuanceID(); + mptt.authorize({.account = a1}); + env.close(); + + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = a1, .asset = asset}); + env(tx); + env.close(); + vaultKey = keylet.key; + return true; + }); + } } // Test the invariant overwrite fix for both pre- and post-amendment diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index a8132887fc..201bb6942a 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -5254,7 +5255,7 @@ protected: auto const loanSequence = brokerStateBefore->at(sfLoanSequence); auto const keylet = keylet::loan(broker.brokerID, loanSequence); - auto const closeStartDate = (parentCloseTime() / 10 + 1) * 10; + auto const closeStartDate = ((parentCloseTime() / 10) + 1) * 10; auto const grace = 5'000; auto const interval = kMaxTime - closeStartDate - grace; auto const total = 1; @@ -5283,7 +5284,7 @@ protected: env(pay(issuer, borrower, iouAsset(Number{1'055'524'81, -2}))); // Start date when the ledger is closed will be larger - auto const closeStartDate = (parentCloseTime() / 10 + 1) * 10; + auto const closeStartDate = ((parentCloseTime() / 10) + 1) * 10; auto const grace = 5'000; auto const maxLoanTime = kMaxTime - closeStartDate - grace; auto const total = [&]() { @@ -5397,7 +5398,7 @@ protected: void testCoverDepositWithdrawNonTransferableMPT(FeatureBitset feature) { - testcase("CoverDeposit and CoverWithdraw reject MPT without CanTransfer"); + testcase("CoverDeposit blocked, CoverWithdraw allowed when CanTransfer cleared"); using namespace jtx; using namespace loanBroker; @@ -5409,19 +5410,14 @@ protected: env.fund(XRP(100'000), issuer, alice); env.close(); - MPTTester mpt{env, issuer, kMptInitNoFund}; - - mpt.create({.flags = tfMPTCanTransfer, .mutableFlags = tmfMPTCanMutateCanTransfer}); - - env.close(); - + MPTTester mpt( + {.env = env, + .issuer = issuer, + .holders = {alice}, + .pay = 100, + .flags = tfMPTCanTransfer, + .mutableFlags = tmfMPTCanMutateCanTransfer}); PrettyAsset const asset = mpt["MPT"]; - mpt.authorize({.account = alice}); - env.close(); - - // Issuer can fund the holder even if CanTransfer is not set. - env(pay(issuer, alice, asset(100))); - env.close(); Vault const vault{env}; auto const [createTx, vaultKeylet] = vault.create({.owner = alice, .asset = asset}); @@ -5438,30 +5434,9 @@ protected: Account const pseudoAccount{"Loan Broker pseudo-account", brokerSle->at(sfAccount)}; - // Remove CanTransfer after the broker is set up. - mpt.set({.mutableFlags = tmfMPTClearCanTransfer}); - env.close(); - - // Standard Payment path should forbid third-party transfers. - auto const err = feature[featureMPTokensV2] ? tecNO_PERMISSION : tecNO_AUTH; - env(pay(alice, pseudoAccount, asset(1)), Ter(err)); - env.close(); - - // Cover cannot be transferred to broker account + // First, deposit some cover while CanTransfer is set so we have an + // existing position to withdraw from after the governance action. auto const depositAmount = asset(1); - env(coverDeposit(alice, brokerKeylet.key, depositAmount), Ter{tecNO_AUTH}); - env.close(); - - if (auto const refreshed = env.le(brokerKeylet); BEAST_EXPECT(refreshed)) - { - BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 0); - env.require(Balance(pseudoAccount, asset(0))); - } - - // Set CanTransfer again and transfer some deposit - mpt.set({.mutableFlags = tmfMPTSetCanTransfer}); - env.close(); - env(coverDeposit(alice, brokerKeylet.key, depositAmount)); env.close(); @@ -5471,26 +5446,177 @@ protected: env.require(Balance(pseudoAccount, depositAmount)); } - // Remove CanTransfer after the deposit + // Issuer governance: clear CanTransfer. mpt.set({.mutableFlags = tmfMPTClearCanTransfer}); env.close(); - // Cover cannot be transferred from broker account - env(coverWithdraw(alice, brokerKeylet.key, depositAmount), Ter{tecNO_AUTH}); + // Standard Payment path still forbids third-party transfers. + auto const err = feature[featureMPTokensV2] ? tecNO_PERMISSION : tecNO_AUTH; + env(pay(alice, pseudoAccount, asset(1)), Ter(err)); env.close(); - // Set CanTransfer again and withdraw - mpt.set({.mutableFlags = tmfMPTSetCanTransfer}); - env.close(); - - env(coverWithdraw(alice, brokerKeylet.key, depositAmount)); + // New cover deposits are blocked - this would create new exposure. + env(coverDeposit(alice, brokerKeylet.key, depositAmount), Ter{tecNO_AUTH}); env.close(); if (auto const refreshed = env.le(brokerKeylet); BEAST_EXPECT(refreshed)) { - BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 0); - env.require(Balance(pseudoAccount, asset(0))); + BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 1); + env.require(Balance(pseudoAccount, depositAmount)); } + + bool const postAmendment = feature[fixCleanup3_2_0]; + if (postAmendment) + { + // Post-fixCleanup3_2_0: existing cover can always be withdrawn + // even when CanTransfer is cleared, so the broker is not trapped. + env(coverWithdraw(alice, brokerKeylet.key, depositAmount)); + env.close(); + + if (auto const refreshed = env.le(brokerKeylet); BEAST_EXPECT(refreshed)) + { + BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 0); + env.require(Balance(pseudoAccount, asset(0))); + } + } + else + { + // Pre-fixCleanup3_2_0 regression: cover withdraw was blocked, + // trapping the broker's first-loss capital. + env(coverWithdraw(alice, brokerKeylet.key, depositAmount), Ter{tecNO_AUTH}); + env.close(); + + if (auto const refreshed = env.le(brokerKeylet); BEAST_EXPECT(refreshed)) + { + BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 1); + env.require(Balance(pseudoAccount, depositAmount)); + } + } + } + + void + testLoanSetBlockedLoanPayAllowedWhenCanTransferCleared() + { + testcase("LoanSet blocked, LoanPay allowed when CanTransfer cleared"); + using namespace jtx; + using namespace loan; + + Env env(*this, all_); + + Account const issuer{"issuer"}; + Account const lender{"lender"}; + Account const borrower{"borrower"}; + + env.fund(XRP(1'000'000), issuer, lender, borrower); + env.close(); + + MPTTester mpt( + {.env = env, + .issuer = issuer, + .holders = {lender, borrower}, + .flags = tfMPTCanTransfer | tfMPTCanLock, + .mutableFlags = tmfMPTCanMutateCanTransfer}); + PrettyAsset const asset = mpt.issuanceID(); + env(pay(issuer, lender, asset(10'000'000))); + // Fund the borrower with enough to cover principal+interest+fees + env(pay(issuer, borrower, asset(100'000))); + env.close(); + + // Create vault and broker while CanTransfer is set. + auto const broker = createVaultAndBroker(env, asset, lender); + + auto const loanSetFee = Fee(env.current()->fees().base * 2); + + // Create an existing loan while CanTransfer is set. + env(set(borrower, broker.brokerID, 1'000), + Sig(sfCounterpartySignature, lender), + loanSetFee); + env.close(); + auto const loanKeylet = keylet::loan(broker.brokerID, 1); + BEAST_EXPECT(env.le(loanKeylet)); + + // Issuer governance: clear CanTransfer. + mpt.set({.mutableFlags = tmfMPTClearCanTransfer}); + env.close(); + + // Issuing a NEW loan is blocked - it would create new exposure into + // a pool the issuer is restricting. + env(set(borrower, broker.brokerID, 1'000), + Sig(sfCounterpartySignature, lender), + loanSetFee, + Ter{tecNO_AUTH}); + env.close(); + + // Repaying an existing loan is always allowed - blocking it would + // create irrecoverable bad debt and trap SAV depositor principal. + env(pay(borrower, loanKeylet.key, asset(1'000))); + env.close(); + } + + void + testLendingCanTradeClearedNoImpact() + { + testcase("Lending: CanTrade cleared has no impact"); + using namespace jtx; + using namespace loan; + using namespace loanBroker; + + Env env(*this, all_); + + Account const issuer{"issuer"}; + Account const lender{"lender"}; + Account const borrower{"borrower"}; + + env.fund(XRP(1'000'000), issuer, lender, borrower); + env.close(); + + MPTTester mpt( + {.env = env, + .issuer = issuer, + .holders = {lender, borrower}, + .flags = tfMPTCanTransfer | tfMPTCanTrade | tfMPTCanLock, + .mutableFlags = tmfMPTCanMutateCanTrade}); + PrettyAsset const asset = mpt.issuanceID(); + env(pay(issuer, lender, asset(10'000'000))); + env(pay(issuer, borrower, asset(100'000))); + env.close(); + + auto const broker = createVaultAndBroker(env, asset, lender); + + // Sanity: while CanTrade is set, the asset can be placed on the DEX. + env(offer(lender, XRP(1), asset(10))); + env.close(); + + // Issuer governance: clear CanTrade. Loan origination and repayment + // are not trades: nothing in the Lending Protocol should be impacted. + mpt.set({.mutableFlags = tmfMPTClearCanTrade}); + env.close(); + + // Control: clearing CanTrade is observable on the DEX path. + env(offer(lender, XRP(1), asset(10)), Ter{tecNO_PERMISSION}); + env.close(); + + auto const loanSetFee = Fee(env.current()->fees().base * 2); + + // New cover deposits still work. + env(coverDeposit(lender, broker.brokerID, asset(100))); + env.close(); + + // New loan issuance still works. + env(loan::set(borrower, broker.brokerID, 1'000), + Sig(sfCounterpartySignature, lender), + loanSetFee); + env.close(); + auto const loanKeylet = keylet::loan(broker.brokerID, 1); + BEAST_EXPECT(env.le(loanKeylet)); + + // Repayment still works. + env(pay(borrower, loanKeylet.key, asset(1'000))); + env.close(); + + // Cover withdrawal still works. + env(coverWithdraw(lender, broker.brokerID, asset(100))); + env.close(); } #if LOAN_TODO @@ -6882,7 +7008,7 @@ protected: auto credType = "credential1"; - pdomain::Credentials const credentials1{{issuer, credType}}; + pdomain::Credentials const credentials1{{.issuer = issuer, .credType = credType}}; env(pdomain::setTx(issuer, credentials1)); env.close(); @@ -6985,7 +7111,7 @@ protected: auto credType = "credential1"; - pdomain::Credentials const credentials1{{issuer, credType}}; + pdomain::Credentials const credentials1{{.issuer = issuer, .credType = credType}}; env(pdomain::setTx(issuer, credentials1)); env.close(); @@ -7631,6 +7757,9 @@ public: auto const all = jtx::testableAmendments(); testCoverDepositWithdrawNonTransferableMPT(all); testCoverDepositWithdrawNonTransferableMPT(all - featureMPTokensV2); + testCoverDepositWithdrawNonTransferableMPT(all - fixCleanup3_2_0); + testLoanSetBlockedLoanPayAllowedWhenCanTransferCleared(); + testLendingCanTradeClearedNoImpact(); testPoCUnsignedUnderflowOnFullPayAfterEarlyPeriodic(); testDisabled(); diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 1e7763ac62..0e48db6080 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -191,6 +192,22 @@ class MPToken_test : public beast::unit_test::Suite .metadata = "test", .err = temMALFORMED}); } + + // sfReferenceHolding is populated internally only by VaultCreate. + // A user-submitted MPTokenIssuanceCreate carrying the field must be + // rejected at preflight under fixCleanup3_2_0. + if (features[fixCleanup3_2_0]) + { + Env env{*this, features}; + env.fund(XRP(1'000), alice); + env.close(); + + json::Value jv; + jv[sfAccount] = alice.human(); + jv[sfTransactionType] = jss::MPTokenIssuanceCreate; + jv[sfReferenceHolding] = to_string(uint256{1}); + env(jv, Ter(temMALFORMED)); + } } void @@ -1777,7 +1794,7 @@ class MPToken_test : public beast::unit_test::Suite env.close(); // Bob authorize credentials - env(deposit::authCredentials(bob, {{dpIssuer, credType}})); + env(deposit::authCredentials(bob, {{.issuer = dpIssuer, .credType = credType}})); env.close(); // alice try to send 100 MPT to bob, not authorized diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index 318a6dc45c..af6f4841f4 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -2241,7 +2242,7 @@ class Vault_test : public beast::unit_test::Suite PrettyAsset const& asset, Vault& vault, MPTTester& mptt) { - testcase("MPT non-transferable"); + testcase("MPT non-transferable: block deposit, allow withdraw"); auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); env(tx); @@ -2251,22 +2252,19 @@ class Vault_test : public beast::unit_test::Suite env(tx); env.close(); - // Remove CanTransfer + // Issuer governance: clear CanTransfer. New exposure must be + // blocked, but recovery paths must remain open so existing + // depositors are not trapped. mptt.set({.mutableFlags = tmfMPTClearCanTransfer}); env.close(); + // New deposit is blocked. env(tx, Ter{tecNO_AUTH}); env.close(); + // Existing depositor can always withdraw, even though the asset + // is no longer freely transferable. tx = vault.withdraw({.depositor = depositor, .id = keylet.key, .amount = asset(100)}); - - env(tx, Ter{tecNO_AUTH}); - env.close(); - - // Restore CanTransfer - mptt.set({.mutableFlags = tmfMPTSetCanTransfer}); - env.close(); - env(tx); env.close(); @@ -2274,6 +2272,245 @@ class Vault_test : public beast::unit_test::Suite env(vault.del({.owner = owner, .id = keylet.key})); }); + { + testcase("MPT non-transferable: pre-fixCleanup3_2_0 withdraw blocked"); + + // Regression: before fixCleanup3_2_0 a depositor was trapped if + // the issuer cleared lsfMPTCanTransfer. Verify that the legacy + // (broken) behavior is preserved when the amendment is disabled. + Env env{*this, testableAmendments() - fixCleanup3_2_0}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + Account const depositor{"depositor"}; + env.fund(XRP(10'000), issuer, owner, depositor); + env.close(); + Vault const vault{env}; + + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create( + {.flags = tfMPTCanTransfer | tfMPTCanLock, + .mutableFlags = tmfMPTCanMutateCanTransfer}); + PrettyAsset const asset = mptt.issuanceID(); + mptt.authorize({.account = owner}); + mptt.authorize({.account = depositor}); + env(pay(issuer, depositor, asset(1'000))); + env.close(); + + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + env(vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(100)})); + env.close(); + + mptt.set({.mutableFlags = tmfMPTClearCanTransfer}); + env.close(); + + // Pre-amendment: deposit blocked (matches new behavior). + env(vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(100)}), + Ter{tecNO_AUTH}); + env.close(); + + // Pre-amendment: withdraw is also blocked - this is the bug + // that fixCleanup3_2_0 fixes. + env(vault.withdraw({.depositor = depositor, .id = keylet.key, .amount = asset(100)}), + Ter{tecNO_AUTH}); + env.close(); + } + + { + testcase("MPT non-transferable: vault shares inherit restriction"); + + Env env{*this, testableAmendments()}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(10'000), issuer, owner, alice, bob); + env.close(); + Vault const vault{env}; + + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create( + {.flags = tfMPTCanTransfer | tfMPTCanLock, + .mutableFlags = tmfMPTCanMutateCanTransfer}); + PrettyAsset const asset = mptt.issuanceID(); + mptt.authorize({.account = owner}); + mptt.authorize({.account = alice}); + mptt.authorize({.account = bob}); + env(pay(issuer, alice, asset(1'000))); + env(pay(issuer, bob, asset(1'000))); + env.close(); + + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(500)})); + // Bob also deposits so he has a share MPToken to receive into. + env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(500)})); + env.close(); + + auto const shares = [&]() -> PrettyAsset { + auto const sle = env.le(keylet); + BEAST_EXPECT(sle != nullptr); + return MPTIssue(sle->at(sfShareMPTID)); + }(); + + // Sanity: while CanTransfer is set on the underlying, peer-to-peer + // share transfers are allowed. + env(pay(alice, bob, shares(1))); + env.close(); + + // Issuer governance: clear CanTransfer on the underlying. + mptt.set({.mutableFlags = tmfMPTClearCanTransfer}); + env.close(); + + // Vault shares inherit the restriction: third-party share-to-share + // payments are blocked. + env(pay(alice, bob, shares(1)), Ter{tecNO_AUTH}); + env.close(); + + // Recovery path: existing share holders can still redeem shares + // for the underlying asset via VaultWithdraw. + env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = shares(1)})); + env.close(); + } + + { + testcase("MPT non-transferable: pre-fixCleanup3_2_0 share transfer succeeds"); + + // Regression: before fixCleanup3_2_0 a peer-to-peer share Payment + // succeeded even when the underlying asset's lsfMPTCanTransfer + // was cleared. Verify that the legacy (non-inheriting) behavior + // is preserved when the amendment is disabled. + Env env{*this, testableAmendments() - fixCleanup3_2_0}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(10'000), issuer, owner, alice, bob); + env.close(); + Vault const vault{env}; + + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create( + {.flags = tfMPTCanTransfer | tfMPTCanLock, + .mutableFlags = tmfMPTCanMutateCanTransfer}); + PrettyAsset const asset = mptt.issuanceID(); + mptt.authorize({.account = owner}); + mptt.authorize({.account = alice}); + mptt.authorize({.account = bob}); + env(pay(issuer, alice, asset(1'000))); + env(pay(issuer, bob, asset(1'000))); + env.close(); + + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(500)})); + env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(500)})); + env.close(); + + auto const shares = [&]() -> PrettyAsset { + auto const sle = env.le(keylet); + BEAST_EXPECT(sle != nullptr); + return MPTIssue(sle->at(sfShareMPTID)); + }(); + + mptt.set({.mutableFlags = tmfMPTClearCanTransfer}); + env.close(); + + // Pre-amendment: share transfer leaks past underlying restriction. + env(pay(alice, bob, shares(1))); + env.close(); + } + + { + testcase("MPT CanTrade governance: share inherits underlying on DEX and AMM"); + + Env env{*this, testableAmendments()}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100'000), issuer, owner, alice, bob); + env.close(); + Vault const vault{env}; + + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create( + {.flags = tfMPTCanTransfer | tfMPTCanTrade | tfMPTCanLock, + .mutableFlags = tmfMPTCanMutateCanTrade}); + PrettyAsset const asset = mptt.issuanceID(); + mptt.authorize({.account = owner}); + mptt.authorize({.account = alice}); + mptt.authorize({.account = bob}); + env(pay(issuer, alice, asset(10'000))); + env(pay(issuer, bob, asset(10'000))); + env.close(); + + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + // Seed shares so we can later place them on trading venues. + env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(5'000)})); + env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(5'000)})); + env.close(); + + auto const shares = [&]() -> PrettyAsset { + auto const sle = env.le(keylet); + BEAST_EXPECT(sle != nullptr); + return MPTIssue(sle->at(sfShareMPTID)); + }(); + + // Sanity: while CanTrade is set on the underlying, both the asset + // and the vault share can be placed on the DEX. + env(offer(alice, XRP(1), asset(10))); + env(offer(alice, XRP(1), shares(1))); + env.close(); + + // Issuer governance: clear CanTrade on the underlying. + mptt.set({.mutableFlags = tmfMPTClearCanTrade}); + env.close(); + + // Control: clearing CanTrade on the underlying is observable on + // the DEX path for that asset. + env(offer(alice, XRP(1), asset(10)), Ter{tecNO_PERMISSION}); + env.close(); + + // Control: clearing CanTrade on the underlying is also observable + // on the AMM path for that asset. + AMM const ammUnderlyingFails( + env, alice, XRP(1'000), asset(1'000), Ter{tecNO_PERMISSION}); + + // Post-fixCleanup3_2_0: vault shares inherit the underlying's + // CanTrade restriction on the DEX path (canTrade reads the + // share's sfReferenceHolding and dispatches to the underlying). + env(offer(bob, XRP(1), shares(1)), Ter{tecNO_PERMISSION}); + env.close(); + + // checkMPTAllowed mirrors the inheritance for AMM/Offer- + // crossing/Check paths, so a share AMM also cannot be created + // when the underlying CanTrade is cleared. + AMM const ammShares(env, alice, XRP(1'000), shares(100), Ter{tecNO_PERMISSION}); + + // Deposit still works (canAddHolding does not consult the field). + env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(100)})); + env.close(); + + // Peer-to-peer share transfers still work (CanTransfer is set on + // both layers). + env(pay(alice, bob, shares(1))); + env.close(); + + // Withdraw still works. + env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = asset(100)})); + env.close(); + } + { testcase("MPT OutstandingAmount > MaximumAmount"); @@ -2744,16 +2981,17 @@ class Vault_test : public beast::unit_test::Suite env(tx); env.close(); } - env(pay(owner, charlie, shares(100))); - env.close(); - - // Charlie cannot withdraw - auto tx3 = vault.withdraw( - {.depositor = charlie, .id = keylet.key, .amount = shares(100)}); - env(tx3, Ter{terNO_RIPPLE}); - env.close(); - - env(pay(charlie, owner, shares(100))); + // Behavioural shift introduced by share inheritance: + // before fixCleanup3_2_0 this share Payment succeeded + // and the underlying IOU's NoRipple restriction surfaced + // only later on Charlie's withdrawal (terNO_RIPPLE). + // Post-amendment, canTransfer reads the share's + // sfReferenceHolding and dispatches to the underlying IOU; + // rippling is disabled between owner and charlie so the + // share payment itself is now blocked. tecPATH_DRY is + // the path-find layer's translation of the underlying + // terNO_RIPPLE under featureMPTokensV2. + env(pay(owner, charlie, shares(100)), Ter{tecPATH_DRY}); env.close(); } @@ -6140,6 +6378,406 @@ class Vault_test : public beast::unit_test::Suite runTest(amendments); } + void + testReferenceHolding() + { + using namespace test::jtx; + + auto readReferenceHolding = [&](Env const& env, + Keylet const& vaultKeylet) -> std::optional { + auto const sleVault = env.le(vaultKeylet); + if (!sleVault) + return std::nullopt; + auto const sleIssuance = env.le(keylet::mptIssuance(sleVault->at(sfShareMPTID))); + if (!sleIssuance || !sleIssuance->isFieldPresent(sfReferenceHolding)) + return std::nullopt; + return sleIssuance->getFieldH256(sfReferenceHolding); + }; + + // Post-fixCleanup3_2_0: vault share carries sfReferenceHolding + // pointing to the vault pseudo's MPToken (for MPT-backed vaults) + // or RippleState (for IOU-backed vaults). + { + testcase("sfReferenceHolding: MPT-backed vault, post-amendment"); + Env env{*this, testableAmendments()}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + env.fund(XRP(10'000), issuer, owner); + env.close(); + + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock}); + PrettyAsset const asset = mptt.issuanceID(); + mptt.authorize({.account = owner}); + + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + auto const sleVault = env.le(keylet); + BEAST_EXPECT(sleVault != nullptr); + auto const pseudoId = sleVault->at(sfAccount); + auto const expected = keylet::mptoken(mptt.issuanceID(), pseudoId).key; + + auto const stored = readReferenceHolding(env, keylet); + BEAST_EXPECT(stored.has_value()); + BEAST_EXPECT(stored && *stored == expected); + // The pointed-to MPToken must actually exist. + BEAST_EXPECT(env.le(keylet::mptoken(mptt.issuanceID(), pseudoId)) != nullptr); + } + + { + testcase("sfReferenceHolding: IOU-backed vault, post-amendment"); + Env env{*this, testableAmendments()}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + env.fund(XRP(10'000), issuer, owner); + env(fset(issuer, asfDefaultRipple)); + env.close(); + + PrettyAsset const asset = issuer["IOU"]; + env.trust(asset(1'000'000), owner); + env.close(); + + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + auto const sleVault = env.le(keylet); + BEAST_EXPECT(sleVault != nullptr); + auto const pseudoId = sleVault->at(sfAccount); + auto const expected = keylet::line(pseudoId, asset.raw().get()).key; + + auto const stored = readReferenceHolding(env, keylet); + BEAST_EXPECT(stored.has_value()); + BEAST_EXPECT(stored && *stored == expected); + // The pointed-to RippleState must actually exist. + BEAST_EXPECT(env.le(keylet::line(pseudoId, asset.raw().get())) != nullptr); + } + + // XRP-backed vaults leave the field absent: XRP has no separate + // holding ledger entry and no transferability concept to inherit. + { + testcase("sfReferenceHolding: XRP-backed vault, field absent"); + Env env{*this, testableAmendments()}; + Account const owner{"owner"}; + env.fund(XRP(10'000), owner); + env.close(); + + PrettyAsset const asset{xrpIssue(), 1'000'000}; + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + BEAST_EXPECT(!readReferenceHolding(env, keylet).has_value()); + } + + // Pre-fixCleanup3_2_0: vault share has the field absent regardless + // of underlying type. + { + testcase("sfReferenceHolding: vault share, pre-amendment"); + Env env{*this, testableAmendments() - fixCleanup3_2_0}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + env.fund(XRP(10'000), issuer, owner); + env.close(); + + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock}); + PrettyAsset const asset = mptt.issuanceID(); + mptt.authorize({.account = owner}); + + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + BEAST_EXPECT(!readReferenceHolding(env, keylet).has_value()); + } + + // Plain MPTokenIssuanceCreate (not a vault share) must never + // populate the field. Only the post-amendment case is + // interesting; pre-amendment nothing writes the field at all. + { + testcase("sfReferenceHolding: plain MPT issuance never set"); + Env env{*this, testableAmendments()}; + Account const issuer{"issuer"}; + env.fund(XRP(10'000), issuer); + env.close(); + + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock}); + env.close(); + + auto const sleIssuance = env.le(keylet::mptIssuance(mptt.issuanceID())); + if (BEAST_EXPECT(sleIssuance)) + BEAST_EXPECT(!sleIssuance->isFieldPresent(sfReferenceHolding)); + } + } + + // Probe every transactor surface that might delete the vault pseudo- + // account's underlying holding (the MPToken or RippleState pointed to + // by sfReferenceHolding). Each scenario asserts either that the + // existing pseudo-account guards stop the deletion at preclaim, or + // that the ledger leaves the holding intact afterwards. This is a + // regression guard: if any of these guards regresses, the share's + // sfReferenceHolding pointer would dangle and the new ValidMPTIssuance + // invariant would catch it - but we want to fail much earlier, at + // the transactor's preclaim / doApply, not at invariant time. + void + testHoldingDeletionBlocked() + { + using namespace test::jtx; + + // Helper: read the share's referenced holding and confirm the + // pointed-to SLE still exists after the probe. + auto referencedHoldingExists = [&](Env const& env, Keylet const& vaultKeylet) -> bool { + auto const sleVault = env.le(vaultKeylet); + if (!sleVault) + return false; + auto const sleIssuance = env.le(keylet::mptIssuance(sleVault->at(sfShareMPTID))); + if (!sleIssuance || !sleIssuance->isFieldPresent(sfReferenceHolding)) + return false; + auto const holdingKey = sleIssuance->getFieldH256(sfReferenceHolding); + return env.le(keylet::unchecked(holdingKey)) != nullptr; + }; + + // ---- MPT-backed vault ---------------------------------------- + { + testcase("vault pseudo MPToken: Clawback blocked by tecPSEUDO_ACCOUNT"); + Env env{*this, testableAmendments()}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + Account const depositor{"depositor"}; + env.fund(XRP(10'000), issuer, owner, depositor); + env.close(); + + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanClawback}); + PrettyAsset const asset = mptt.issuanceID(); + mptt.authorize({.account = owner}); + mptt.authorize({.account = depositor}); + env(pay(issuer, depositor, asset(1'000))); + env.close(); + + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + env(vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(500)})); + env.close(); + + BEAST_EXPECT(referencedHoldingExists(env, keylet)); + + Account const pseudoAccount{"vault-pseudo", env.le(keylet)->at(sfAccount)}; + // Issuer attempts to claw back the FULL underlying balance + // (500) directly from the vault pseudo-account. With the + // full amount, the doApply path would drain the pseudo's + // MPToken to zero and removeEmptyHolding would erase it - + // if doApply ever ran. SAV's pseudo-account guard at + // Clawback.cpp:201 refuses at preclaim with + // tecPSEUDO_ACCOUNT before any state change. + env(claw(issuer, asset(500), pseudoAccount), Ter{tecPSEUDO_ACCOUNT}); + env.close(); + BEAST_EXPECT(referencedHoldingExists(env, keylet)); + // Sanity: pseudo's full balance is intact. + BEAST_EXPECT(env.balance(pseudoAccount, asset).number() == 500); + } + + { + testcase("vault pseudo MPToken: Issuer cannot Unauthorize pseudo"); + Env env{*this, testableAmendments()}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + env.fund(XRP(10'000), issuer, owner); + env.close(); + + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTRequireAuth}); + PrettyAsset const asset = mptt.issuanceID(); + mptt.authorize({.account = owner}); + mptt.authorize({.account = issuer, .holder = owner}); + env.close(); + + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + BEAST_EXPECT(referencedHoldingExists(env, keylet)); + + auto const pseudoId = env.le(keylet)->at(sfAccount); + // Issuer attempts MPTokenAuthorize against the pseudo with + // tfMPTUnauthorize. MPTokenAuthorize.cpp blocks pseudo + // accounts via isPseudoAccount; the pseudo's MPToken is + // preserved. Construct the tx manually since the pseudo + // lacks a signing key, and the issuer-driven flavour is + // expressed via sfHolder. + json::Value jv; + jv[sfAccount] = issuer.human(); + jv[sfHolder] = toBase58(pseudoId); + jv[sfMPTokenIssuanceID] = to_string(mptt.issuanceID()); + jv[sfFlags] = tfMPTUnauthorize; + jv[sfTransactionType] = jss::MPTokenAuthorize; + env(jv, Ter{tecNO_PERMISSION}); + env.close(); + BEAST_EXPECT(referencedHoldingExists(env, keylet)); + } + + { + testcase("vault pseudo MPToken: MPTokenIssuanceDestroy blocked while vault holds"); + Env env{*this, testableAmendments()}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + Account const depositor{"depositor"}; + env.fund(XRP(10'000), issuer, owner, depositor); + env.close(); + + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock}); + PrettyAsset const asset = mptt.issuanceID(); + mptt.authorize({.account = owner}); + mptt.authorize({.account = depositor}); + env(pay(issuer, depositor, asset(1'000))); + env.close(); + + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + env(vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(500)})); + env.close(); + + BEAST_EXPECT(referencedHoldingExists(env, keylet)); + + // While the vault holds outstanding underlying, the issuer + // cannot destroy the issuance. tecHAS_OBLIGATIONS confirms + // the protection - and as a side effect, the share's + // sfReferenceHolding pointer cannot be left pointing at a + // ghost issuance. + mptt.destroy({.id = mptt.issuanceID(), .err = tecHAS_OBLIGATIONS}); + env.close(); + BEAST_EXPECT(referencedHoldingExists(env, keylet)); + } + + // ---- IOU-backed vault ---------------------------------------- + { + testcase("vault pseudo trust line: Clawback blocked by tecPSEUDO_ACCOUNT"); + Env env{*this, testableAmendments()}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + env.fund(XRP(10'000), issuer, owner); + env(fset(issuer, asfAllowTrustLineClawback)); + env.close(); + + PrettyAsset const asset = issuer["IOU"]; + env.trust(asset(1'000'000), owner); + env(pay(issuer, owner, asset(1'000))); + env.close(); + + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + env(vault.deposit({.depositor = owner, .id = keylet.key, .amount = asset(500)})); + env.close(); + + BEAST_EXPECT(referencedHoldingExists(env, keylet)); + + Account const pseudoAccount{"vault-pseudo", env.le(keylet)->at(sfAccount)}; + // Issuer attempts to claw back the FULL IOU balance (500) + // directly from the vault pseudo. With the full amount, the + // doApply path would drain the trust line to zero and (if + // both reserve flags clear) trustDelete would erase it - if + // doApply ever ran. The same SAV pseudo-account guard + // refuses at preclaim with tecPSEUDO_ACCOUNT. The amount's + // STAmount issuer field is the holder, per IOU clawback + // convention. + env(claw(issuer, pseudoAccount["IOU"](500)), Ter{tecPSEUDO_ACCOUNT}); + env.close(); + BEAST_EXPECT(referencedHoldingExists(env, keylet)); + // Sanity: pseudo's full balance is intact. + BEAST_EXPECT(env.balance(pseudoAccount, asset).number() == 500); + } + + { + testcase("vault pseudo trust line: TrustSet limit=0 from issuer preserves line"); + Env env{*this, testableAmendments()}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + env.fund(XRP(10'000), issuer, owner); + env(fset(issuer, asfDefaultRipple)); + env.close(); + + PrettyAsset const asset = issuer["IOU"]; + env.trust(asset(1'000'000), owner); + env(pay(issuer, owner, asset(1'000))); + env.close(); + + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + env(vault.deposit({.depositor = owner, .id = keylet.key, .amount = asset(500)})); + env.close(); + + BEAST_EXPECT(referencedHoldingExists(env, keylet)); + + // Issuer submits TrustSet with limit=0 against the vault + // pseudo. The pseudo's side of the line still has the + // original (non-zero) limit and a non-zero balance, so the + // line is preserved - even though the issuer cleared its + // own side. trustDelete only fires when both limits clear + // and the balance is zero. + Account const pseudoAccount{"vault-pseudo", env.le(keylet)->at(sfAccount)}; + env(trust(issuer, pseudoAccount["IOU"](0))); + env.close(); + BEAST_EXPECT(referencedHoldingExists(env, keylet)); + } + + // ---- Positive control: VaultDelete is the only legitimate path + { + testcase("vault pseudo holding: VaultDelete is the legitimate cleanup path"); + Env env{*this, testableAmendments()}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + env.fund(XRP(10'000), issuer, owner); + env.close(); + + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock}); + PrettyAsset const asset = mptt.issuanceID(); + mptt.authorize({.account = owner}); + + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + BEAST_EXPECT(referencedHoldingExists(env, keylet)); + auto const pseudoId = env.le(keylet)->at(sfAccount); + auto const sharedMptId = env.le(keylet)->at(sfShareMPTID); + auto const holdingKeylet = keylet::mptoken(mptt.issuanceID(), pseudoId); + + // VaultDelete tears down the vault pseudo's holding, the + // share issuance, and the pseudo-account itself. Invariant + // permits this because the tx is ttVAULT_DELETE. + env(vault.del({.owner = owner, .id = keylet.key})); + env.close(); + + BEAST_EXPECT(env.le(keylet) == nullptr); + BEAST_EXPECT(env.le(holdingKeylet) == nullptr); + BEAST_EXPECT(env.le(keylet::mptIssuance(sharedMptId)) == nullptr); + } + } + // VaultDeposit::preclaim uses accountHolds(..., SpendableHandling:: // shFULL_BALANCE), which for an IOU asset adds the counterparty's // LowLimit/HighLimit to the depositor's raw balance (TokenHelpers.cpp: @@ -6243,6 +6881,8 @@ public: testAssetsMaximum(); testBug6LimitBypassWithShares(); testRemoveEmptyHoldingLockedAmount(); + testReferenceHolding(); + testHoldingDeletionBlocked(); } }; diff --git a/src/tests/libxrpl/protocol_autogen/ledger_entries/MPTokenIssuanceTests.cpp b/src/tests/libxrpl/protocol_autogen/ledger_entries/MPTokenIssuanceTests.cpp index e74af94a5f..7479f0c63c 100644 --- a/src/tests/libxrpl/protocol_autogen/ledger_entries/MPTokenIssuanceTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/ledger_entries/MPTokenIssuanceTests.cpp @@ -33,6 +33,7 @@ TEST(MPTokenIssuanceTests, BuilderSettersRoundTrip) auto const previousTxnLgrSeqValue = canonical_UINT32(); auto const domainIDValue = canonical_UINT256(); auto const mutableFlagsValue = canonical_UINT32(); + auto const referenceHoldingValue = canonical_UINT256(); MPTokenIssuanceBuilder builder{ issuerValue, @@ -50,6 +51,7 @@ TEST(MPTokenIssuanceTests, BuilderSettersRoundTrip) builder.setMPTokenMetadata(mPTokenMetadataValue); builder.setDomainID(domainIDValue); builder.setMutableFlags(mutableFlagsValue); + builder.setReferenceHolding(referenceHoldingValue); builder.setLedgerIndex(index); builder.setFlags(0x1u); @@ -152,6 +154,14 @@ TEST(MPTokenIssuanceTests, BuilderSettersRoundTrip) EXPECT_TRUE(entry.hasMutableFlags()); } + { + auto const& expected = referenceHoldingValue; + auto const actualOpt = entry.getReferenceHolding(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfReferenceHolding"); + EXPECT_TRUE(entry.hasReferenceHolding()); + } + EXPECT_TRUE(entry.hasLedgerIndex()); auto const ledgerIndex = entry.getLedgerIndex(); ASSERT_TRUE(ledgerIndex.has_value()); @@ -178,6 +188,7 @@ TEST(MPTokenIssuanceTests, BuilderFromSleRoundTrip) auto const previousTxnLgrSeqValue = canonical_UINT32(); auto const domainIDValue = canonical_UINT256(); auto const mutableFlagsValue = canonical_UINT32(); + auto const referenceHoldingValue = canonical_UINT256(); auto sle = std::make_shared(MPTokenIssuance::entryType, index); @@ -194,6 +205,7 @@ TEST(MPTokenIssuanceTests, BuilderFromSleRoundTrip) sle->at(sfPreviousTxnLgrSeq) = previousTxnLgrSeqValue; sle->at(sfDomainID) = domainIDValue; sle->at(sfMutableFlags) = mutableFlagsValue; + sle->at(sfReferenceHolding) = referenceHoldingValue; MPTokenIssuanceBuilder builderFromSle{sle}; EXPECT_TRUE(builderFromSle.validate()); @@ -355,6 +367,19 @@ TEST(MPTokenIssuanceTests, BuilderFromSleRoundTrip) expectEqualField(expected, *fromBuilderOpt, "sfMutableFlags"); } + { + auto const& expected = referenceHoldingValue; + + auto const fromSleOpt = entryFromSle.getReferenceHolding(); + auto const fromBuilderOpt = entryFromBuilder.getReferenceHolding(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfReferenceHolding"); + expectEqualField(expected, *fromBuilderOpt, "sfReferenceHolding"); + } + EXPECT_EQ(entryFromSle.getKey(), index); EXPECT_EQ(entryFromBuilder.getKey(), index); } @@ -433,5 +458,7 @@ TEST(MPTokenIssuanceTests, OptionalFieldsReturnNullopt) EXPECT_FALSE(entry.getDomainID().has_value()); EXPECT_FALSE(entry.hasMutableFlags()); EXPECT_FALSE(entry.getMutableFlags().has_value()); + EXPECT_FALSE(entry.hasReferenceHolding()); + EXPECT_FALSE(entry.getReferenceHolding().has_value()); } } From a5d238e7d4fa6ef2b539b759d58744d0a1c33c0c Mon Sep 17 00:00:00 2001 From: box4wangjing Date: Thu, 21 May 2026 04:46:45 +0900 Subject: [PATCH 002/135] docs: Fix some comments to improve readability (#7122) Signed-off-by: box4wangjing Co-authored-by: Mayukha Vadari --- include/xrpl/json/Writer.h | 2 +- include/xrpl/protocol/detail/ledger_entries.macro | 2 +- include/xrpl/shamap/README.md | 2 +- src/libxrpl/protocol/STAmount.cpp | 2 +- src/test/app/NFTokenBurn_test.cpp | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/xrpl/json/Writer.h b/include/xrpl/json/Writer.h index 664a2a75c1..87e3e99c7e 100644 --- a/include/xrpl/json/Writer.h +++ b/include/xrpl/json/Writer.h @@ -161,7 +161,7 @@ public: * While the JSON spec doesn't explicitly disallow this, you should avoid * calling this method twice with the same tag for the same object. * - * If CHECK_JSON_WRITER is defined, this function throws an exception if if + * If CHECK_JSON_WRITER is defined, this function throws an exception if * the tag you use has already been used in this object. */ template diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 19b8b44fe6..632038a9c5 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -592,7 +592,7 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({ // LoanBroker.ManagementFeeRate // The unrounded true total fee still owed to the broker. // - // Note the the "True" values may differ significantly from the tracked + // Note the "True" values may differ significantly from the tracked // rounded values. {sfPaymentRemaining, SoeDefault}, {sfPeriodicPayment, SoeRequired}, diff --git a/include/xrpl/shamap/README.md b/include/xrpl/shamap/README.md index ae37d23ac3..8931a1b50c 100644 --- a/include/xrpl/shamap/README.md +++ b/include/xrpl/shamap/README.md @@ -231,7 +231,7 @@ The `fetchNodeNT()` method goes through three phases: will be 0. 2. If the node is not in the TreeNodeCache, we attempt to locate the node - in the historic data stored by the data base. The call to to + in the historic data stored by the data base. The call to `fetchNodeFromDB(hash)` does that work for us. 3. Finally if a filter exists, we check if it can supply the node. This is diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index 3cbf03575b..2282488fb6 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -1505,7 +1505,7 @@ roundToScale(STAmount const& value, std::int32_t scale, Number::RoundingMode rou STAmount const referenceValue{value.asset(), STAmount::kMinValue, scale, value.negative()}; NumberRoundModeGuard const mg(rounding); - // With an IOU, the the result of addition will be truncated to the + // With an IOU, the result of addition will be truncated to the // precision of the larger value, which in this case is referenceValue. Then // remove the reference value via subtraction, and we're left with the // rounded value. diff --git a/src/test/app/NFTokenBurn_test.cpp b/src/test/app/NFTokenBurn_test.cpp index f08793da72..482d275d51 100644 --- a/src/test/app/NFTokenBurn_test.cpp +++ b/src/test/app/NFTokenBurn_test.cpp @@ -515,7 +515,7 @@ class NFTokenBurn_test : public beast::unit_test::Suite { // Removing the last token from the last page deletes the // _previous_ page because we need to preserve that last - // page an an anchor. The contents of the next-to-last page + // page as an anchor. The contents of the next-to-last page // are moved into the last page. lastNFTokenPage = env.le(keylet::nftpageMax(alice)); BEAST_EXPECT(lastNFTokenPage); @@ -694,7 +694,7 @@ class NFTokenBurn_test : public beast::unit_test::Suite { // Removing the last token from the last page deletes the // _previous_ page because we need to preserve that last - // page an an anchor. The contents of the next-to-last page + // page as an anchor. The contents of the next-to-last page // are moved into the last page. lastNFTokenPage = env.le(keylet::nftpageMax(alice)); BEAST_EXPECT(lastNFTokenPage); From 242ce3e9e4280aa7f9e6292062117c8ccd8861c8 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Wed, 20 May 2026 15:47:59 -0400 Subject: [PATCH 003/135] refactor: Fix `sfGeneric` and `sfInvalid` field names (#7300) --- include/xrpl/protocol/SField.h | 4 +-- include/xrpl/protocol/STBlob.h | 2 +- src/libxrpl/protocol/SField.cpp | 8 ++--- src/libxrpl/protocol/STAmount.cpp | 2 +- src/libxrpl/protocol/STBase.cpp | 2 +- src/libxrpl/protocol/STParsedJSON.cpp | 12 +++---- src/libxrpl/protocol/XChainAttestations.cpp | 4 +-- src/libxrpl/server/Manifest.cpp | 10 +++--- src/test/app/Manifest_test.cpp | 18 +++++------ src/test/app/Path_test.cpp | 6 ++-- src/test/app/ValidatorList_test.cpp | 4 +-- src/test/jtx/TrustedPublisherServer.h | 2 +- src/test/jtx/impl/TestHelpers.cpp | 6 ++-- src/test/overlay/compression_test.cpp | 6 ++-- src/test/protocol/Hooks_test.cpp | 6 ++-- src/test/protocol/STAmount_test.cpp | 2 +- src/test/protocol/STObject_test.cpp | 32 +++++++++---------- src/test/protocol/STTx_test.cpp | 2 +- src/test/rpc/Simulate_test.cpp | 4 +-- .../libxrpl/protocol_autogen/TestHelpers.h | 2 +- .../rpc/handlers/transaction/Simulate.cpp | 2 +- 21 files changed, 68 insertions(+), 68 deletions(-) diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index 1d4e4e69a0..34fb66ce00 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -365,8 +365,8 @@ using SF_XCHAIN_BRIDGE = TypedField; #define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) extern SField const sfName; #define TYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) extern SF_##stiSuffix const sfName; -extern SField const kSfInvalid; -extern SField const kSfGeneric; +extern SField const sfInvalid; // NOLINT(readability-identifier-naming) +extern SField const sfGeneric; // NOLINT(readability-identifier-naming) #include diff --git a/include/xrpl/protocol/STBlob.h b/include/xrpl/protocol/STBlob.h index c9110a367e..0667c54e30 100644 --- a/include/xrpl/protocol/STBlob.h +++ b/include/xrpl/protocol/STBlob.h @@ -24,7 +24,7 @@ public: STBlob(SField const& f, void const* data, std::size_t size); STBlob(SField const& f, Buffer&& b); STBlob(SField const& n); - STBlob(SerialIter&, SField const& name = kSfGeneric); + STBlob(SerialIter&, SField const& name = sfGeneric); [[nodiscard]] std::size_t size() const; diff --git a/src/libxrpl/protocol/SField.cpp b/src/libxrpl/protocol/SField.cpp index f2a523db49..a2d4903c6a 100644 --- a/src/libxrpl/protocol/SField.cpp +++ b/src/libxrpl/protocol/SField.cpp @@ -53,8 +53,8 @@ TypedField::TypedField(PrivateAccessTagT pat, Args&&... args) ##__VA_ARGS__); // SFields which, for historical reasons, do not follow naming conventions. -SField const kSfInvalid(access, -1, ""); -SField const kSfGeneric(access, 0, "Generic"); +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 kSfHash(access, STI_UINT256, 257, "hash"); @@ -121,7 +121,7 @@ SField::getField(int code) { return *(it->second); } - return kSfInvalid; + return sfInvalid; } int @@ -149,7 +149,7 @@ SField::getField(std::string const& fieldName) { return *(it->second); } - return kSfInvalid; + return sfInvalid; } } // namespace xrpl diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index 2282488fb6..2d051722bf 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -1151,7 +1151,7 @@ amountFromJsonNoThrow(STAmount& result, json::Value const& jvSource) { try { - result = amountFromJson(kSfGeneric, jvSource); + result = amountFromJson(sfGeneric, jvSource); return true; } catch (std::exception const& e) diff --git a/src/libxrpl/protocol/STBase.cpp b/src/libxrpl/protocol/STBase.cpp index d1ae1e5aa8..f029f10e75 100644 --- a/src/libxrpl/protocol/STBase.cpp +++ b/src/libxrpl/protocol/STBase.cpp @@ -12,7 +12,7 @@ namespace xrpl { -STBase::STBase() : fName_(&kSfGeneric) +STBase::STBase() : fName_(&sfGeneric) { } diff --git a/src/libxrpl/protocol/STParsedJSON.cpp b/src/libxrpl/protocol/STParsedJSON.cpp index 7b9989afac..64c4dfd1be 100644 --- a/src/libxrpl/protocol/STParsedJSON.cpp +++ b/src/libxrpl/protocol/STParsedJSON.cpp @@ -258,7 +258,7 @@ parseUInt16( safeCast(static_cast( TxFormats::getInstance().findTypeByName(strValue)))); - if (*name == kSfGeneric) + if (*name == sfGeneric) name = &sfTransaction; } else if (field == sfLedgerEntryType) @@ -268,7 +268,7 @@ parseUInt16( safeCast(static_cast( LedgerFormats::getInstance().findTypeByName(strValue)))); - if (*name == kSfGeneric) + if (*name == sfGeneric) name = &sfLedgerEntry; } else @@ -361,7 +361,7 @@ parseLeaf( auto const& field = SField::getField(fieldName); // checked in parseObject - if (field == kSfInvalid) + if (field == sfInvalid) { // LCOV_EXCL_START error = unknownField(jsonName, fieldName); @@ -1013,7 +1013,7 @@ parseObject( json::Value const& value = json[fieldName]; auto const& field = SField::getField(fieldName); - if (field == kSfInvalid) + if (field == sfInvalid) { error = unknownField(jsonName, fieldName); return std::nullopt; @@ -1144,7 +1144,7 @@ parseArray( std::string const memberName(json[i].getMemberNames()[0]); auto const& nameField(SField::getField(memberName)); - if (nameField == kSfInvalid) + if (nameField == sfInvalid) { error = unknownField(jsonName, memberName); return std::nullopt; @@ -1189,7 +1189,7 @@ parseArray( STParsedJSONObject::STParsedJSONObject(std::string const& name, json::Value const& json) { using namespace STParsedJSONDetail; - object = parseObject(name, json, kSfGeneric, 0, error); + object = parseObject(name, json, sfGeneric, 0, error); } } // namespace xrpl diff --git a/src/libxrpl/protocol/XChainAttestations.cpp b/src/libxrpl/protocol/XChainAttestations.cpp index e41e5f3d61..805d08c097 100644 --- a/src/libxrpl/protocol/XChainAttestations.cpp +++ b/src/libxrpl/protocol/XChainAttestations.cpp @@ -195,7 +195,7 @@ AttestationClaim::message( std::uint64_t claimID, std::optional const& dst) { - STObject o{kSfGeneric}; + STObject o{sfGeneric}; // Serialize in SField order to make python serializers easier to write o[sfXChainClaimID] = claimID; o[sfAmount] = sendingAmount; @@ -332,7 +332,7 @@ AttestationCreateAccount::message( std::uint64_t createCount, AccountID const& dst) { - STObject o{kSfGeneric}; + STObject o{sfGeneric}; // Serialize in SField order to make python serializers easier to write o[sfXChainAccountCreateCount] = createCount; o[sfAmount] = sendingAmount; diff --git a/src/libxrpl/server/Manifest.cpp b/src/libxrpl/server/Manifest.cpp index 1b0493dd9d..b26c67e531 100644 --- a/src/libxrpl/server/Manifest.cpp +++ b/src/libxrpl/server/Manifest.cpp @@ -90,7 +90,7 @@ deserializeManifest(Slice s, beast::Journal journal) try { SerialIter sit{s}; - STObject st{sit, kSfGeneric}; + STObject st{sit, sfGeneric}; st.applyTemplate(kManifestFormat); @@ -193,7 +193,7 @@ logMftAct( bool Manifest::verify() const { - STObject st(kSfGeneric); + STObject st(sfGeneric); SerialIter sit(serialized.data(), serialized.size()); st.set(sit); @@ -213,7 +213,7 @@ Manifest::verify() const uint256 Manifest::hash() const { - STObject st(kSfGeneric); + STObject st(sfGeneric); SerialIter sit(serialized.data(), serialized.size()); st.set(sit); return st.getHash(HashPrefix::Manifest); @@ -240,7 +240,7 @@ Manifest::revoked(std::uint32_t sequence) std::optional Manifest::getSignature() const { - STObject st(kSfGeneric); + STObject st(sfGeneric); SerialIter sit(serialized.data(), serialized.size()); st.set(sit); if (!get(st, sfSignature)) @@ -251,7 +251,7 @@ Manifest::getSignature() const Blob Manifest::getMasterSignature() const { - STObject st(kSfGeneric); + STObject st(sfGeneric); SerialIter sit(serialized.data(), serialized.size()); st.set(sit); return st.getFieldVL(sfMasterSignature); diff --git a/src/test/app/Manifest_test.cpp b/src/test/app/Manifest_test.cpp index 55aa93bf79..d559ecd7b5 100644 --- a/src/test/app/Manifest_test.cpp +++ b/src/test/app/Manifest_test.cpp @@ -115,7 +115,7 @@ public: SecretKey const& ssk, int seq) { - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfSequence] = seq; st[sfPublicKey] = pk; st[sfSigningPubKey] = spk; @@ -137,7 +137,7 @@ public: { auto const pk = derivePublicKey(type, sk); - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfSequence] = std::numeric_limits::max(); st[sfPublicKey] = pk; @@ -156,7 +156,7 @@ public: { auto const pk = derivePublicKey(type, sk); - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfSequence] = std::numeric_limits::max(); st[sfPublicKey] = pk; @@ -186,7 +186,7 @@ public: auto const pk = derivePublicKey(type, sk); auto const spk = derivePublicKey(stype, ssk); - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfSequence] = seq; st[sfPublicKey] = pk; st[sfSigningPubKey] = spk; @@ -363,7 +363,7 @@ public: auto const kp = randomKeyPair(KeyType::Secp256k1); auto const m = makeManifest(sk, KeyType::Ed25519, kp.second, KeyType::Secp256k1, 0); - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfSequence] = 0; st[sfPublicKey] = pk; st[sfSigningPubKey] = kp.first; @@ -495,7 +495,7 @@ public: auto const spk = derivePublicKey(KeyType::Secp256k1, ssk); auto buildManifestObject = [&](std::uint16_t version) { - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfSequence] = 3; st[sfPublicKey] = pk; st[sfSigningPubKey] = spk; @@ -573,7 +573,7 @@ public: std::optional domain, bool noSigningPublic = false, bool noSignature = false) { - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfSequence] = seq; st[sfPublicKey] = pk; @@ -724,7 +724,7 @@ public: } { // reject matching master & ephemeral keys - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfSequence] = 314159; st[sfPublicKey] = pk; st[sfSigningPubKey] = pk; @@ -797,7 +797,7 @@ public: auto const pk2 = derivePublicKey(KeyType::Secp256k1, sk2); auto test = [&](std::string domain) { - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfSequence] = 7; st[sfPublicKey] = pk1; st[sfDomain] = makeSlice(domain); diff --git a/src/test/app/Path_test.cpp b/src/test/app/Path_test.cpp index 22044a2572..1cf8131206 100644 --- a/src/test/app/Path_test.cpp +++ b/src/test/app/Path_test.cpp @@ -216,7 +216,7 @@ public: STAmount da; if (result.isMember(jss::destination_amount)) - da = amountFromJson(kSfGeneric, result[jss::destination_amount]); + da = amountFromJson(sfGeneric, result[jss::destination_amount]); STAmount sa; STPathSet paths; @@ -228,10 +228,10 @@ public: auto const& path = alts[0u]; if (path.isMember(jss::source_amount)) - sa = amountFromJson(kSfGeneric, path[jss::source_amount]); + sa = amountFromJson(sfGeneric, path[jss::source_amount]); if (path.isMember(jss::destination_amount)) - da = amountFromJson(kSfGeneric, path[jss::destination_amount]); + da = amountFromJson(sfGeneric, path[jss::destination_amount]); if (path.isMember(jss::paths_computed)) { diff --git a/src/test/app/ValidatorList_test.cpp b/src/test/app/ValidatorList_test.cpp index 15bd1bc586..1b66dd305d 100644 --- a/src/test/app/ValidatorList_test.cpp +++ b/src/test/app/ValidatorList_test.cpp @@ -81,7 +81,7 @@ private: SecretKey const& ssk, int seq) { - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfSequence] = seq; st[sfPublicKey] = pk; @@ -104,7 +104,7 @@ private: static std::string makeRevocationString(PublicKey const& pk, SecretKey const& sk) { - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfSequence] = std::numeric_limits::max(); st[sfPublicKey] = pk; diff --git a/src/test/jtx/TrustedPublisherServer.h b/src/test/jtx/TrustedPublisherServer.h index 4c7528ccc3..9a53a32481 100644 --- a/src/test/jtx/TrustedPublisherServer.h +++ b/src/test/jtx/TrustedPublisherServer.h @@ -101,7 +101,7 @@ public: SecretKey const& ssk, int seq) { - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfSequence] = seq; st[sfPublicKey] = pk; st[sfSigningPubKey] = spk; diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index 3e65410825..e9ea48fb39 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -262,7 +262,7 @@ findPaths( STAmount da; if (result.isMember(jss::destination_amount)) - da = amountFromJson(kSfGeneric, result[jss::destination_amount]); + da = amountFromJson(sfGeneric, result[jss::destination_amount]); STAmount sa; STPathSet paths; @@ -274,10 +274,10 @@ findPaths( auto const& path = alts[0u]; if (path.isMember(jss::source_amount)) - sa = amountFromJson(kSfGeneric, path[jss::source_amount]); + sa = amountFromJson(sfGeneric, path[jss::source_amount]); if (path.isMember(jss::destination_amount)) - da = amountFromJson(kSfGeneric, path[jss::destination_amount]); + da = amountFromJson(sfGeneric, path[jss::destination_amount]); if (path.isMember(jss::paths_computed)) { diff --git a/src/test/overlay/compression_test.cpp b/src/test/overlay/compression_test.cpp index adb3b1b27b..d98ffa9b33 100644 --- a/src/test/overlay/compression_test.cpp +++ b/src/test/overlay/compression_test.cpp @@ -142,7 +142,7 @@ public: { auto master = randomKeyPair(KeyType::Ed25519); auto signing = randomKeyPair(KeyType::Ed25519); - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfSequence] = i; st[sfPublicKey] = std::get<0>(master); st[sfSigningPubKey] = std::get<0>(signing); @@ -299,7 +299,7 @@ public: auto master = randomKeyPair(KeyType::Ed25519); auto signing = randomKeyPair(KeyType::Ed25519); - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfSequence] = 0; st[sfPublicKey] = std::get<0>(master); st[sfSigningPubKey] = std::get<0>(signing); @@ -326,7 +326,7 @@ public: auto master = randomKeyPair(KeyType::Ed25519); auto signing = randomKeyPair(KeyType::Ed25519); - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfSequence] = 0; st[sfPublicKey] = std::get<0>(master); st[sfSigningPubKey] = std::get<0>(signing); diff --git a/src/test/protocol/Hooks_test.cpp b/src/test/protocol/Hooks_test.cpp index 20fe205579..082507aca7 100644 --- a/src/test/protocol/Hooks_test.cpp +++ b/src/test/protocol/Hooks_test.cpp @@ -73,7 +73,7 @@ class Hooks_test : public beast::unit_test::Suite { SField const& f = rf.get(); - STObject dummy{kSfGeneric}; + STObject dummy{sfGeneric}; BEAST_EXPECT(!dummy.isFieldPresent(f)); @@ -161,8 +161,8 @@ class Hooks_test : public beast::unit_test::Suite case STI_ARRAY: { STArray dummy2{f, 2}; - dummy2.pushBack(STObject{kSfGeneric}); - dummy2.pushBack(STObject{kSfGeneric}); + dummy2.pushBack(STObject{sfGeneric}); + dummy2.pushBack(STObject{sfGeneric}); dummy.setFieldArray(f, dummy2); BEAST_EXPECT(dummy.getFieldArray(f) == dummy2); BEAST_EXPECT(dummy.isFieldPresent(f)); diff --git a/src/test/protocol/STAmount_test.cpp b/src/test/protocol/STAmount_test.cpp index b387c361cb..322d51f84d 100644 --- a/src/test/protocol/STAmount_test.cpp +++ b/src/test/protocol/STAmount_test.cpp @@ -37,7 +37,7 @@ public: s.add(ser); SerialIter sit(ser.slice()); - return STAmount(sit, kSfGeneric); + return STAmount(sit, sfGeneric); } //-------------------------------------------------------------------------- diff --git a/src/test/protocol/STObject_test.cpp b/src/test/protocol/STObject_test.cpp index 59b78d213d..801eebbe63 100644 --- a/src/test/protocol/STObject_test.cpp +++ b/src/test/protocol/STObject_test.cpp @@ -36,19 +36,19 @@ public: { testcase("serialization"); - unexpected(kSfGeneric.isUseful(), "sfGeneric must not be useful"); + unexpected(sfGeneric.isUseful(), "sfGeneric must not be useful"); { // Try to put sfGeneric in an SOTemplate. except( - [&]() { SOTemplate const elements{{kSfGeneric, SoeRequired}}; }); + [&]() { SOTemplate const elements{{sfGeneric, SoeRequired}}; }); } - unexpected(kSfInvalid.isUseful(), "sfInvalid must not be useful"); + unexpected(sfInvalid.isUseful(), "sfInvalid must not be useful"); { // Test return of sfInvalid. auto testInvalid = [this](SerializedTypeID tid, int fv) { SField const& shouldBeInvalid{SField::getField(tid, fv)}; - BEAST_EXPECT(shouldBeInvalid == kSfInvalid); + BEAST_EXPECT(shouldBeInvalid == sfInvalid); }; testInvalid(STI_VL, 255); testInvalid(STI_UINT256, 255); @@ -59,7 +59,7 @@ public: { // Try to put sfInvalid in an SOTemplate. except( - [&]() { SOTemplate const elements{{kSfInvalid, SoeRequired}}; }); + [&]() { SOTemplate const elements{{sfInvalid, SoeRequired}}; }); } { // Try to put the same SField into an SOTemplate twice. @@ -188,7 +188,7 @@ public: { auto const st = [&]() { - STObject s(kSfGeneric); + STObject s(sfGeneric); s.setFieldU32(sf1Outer, 1); s.setFieldU32(sf2Outer, 2); return s; @@ -219,7 +219,7 @@ public: { auto const st = [&]() { - STObject s(sotOuter, kSfGeneric); + STObject s(sotOuter, sfGeneric); s.setFieldU32(sf1Outer, 1); s.setFieldU32(sf2Outer, 2); return s; @@ -239,7 +239,7 @@ public: // write free object { - STObject st(kSfGeneric); + STObject st(sfGeneric); unexcept([&]() { st[sf1Outer]; }); except([&]() { return st[sf1Outer] == 0; }); BEAST_EXPECT(st[~sf1Outer] == std::nullopt); @@ -295,7 +295,7 @@ public: // Write templated object { - STObject st(sotOuter, kSfGeneric); + STObject st(sotOuter, sfGeneric); BEAST_EXPECT(!!st[~sf1Outer]); BEAST_EXPECT(st[~sf1Outer] != std::nullopt); BEAST_EXPECT(st[sf1Outer] == 0); @@ -353,7 +353,7 @@ public: // coercion operator to std::optional { - STObject st(kSfGeneric); + STObject st(sfGeneric); auto const v = ~st[~sf1Outer]; static_assert( std::is_same_v, std::optional>, ""); @@ -362,7 +362,7 @@ public: // UDT scalar fields { - STObject st(kSfGeneric); + STObject st(sfGeneric); st[sfAmount] = STAmount{}; st[sfAccount] = AccountID{}; st[sfDigest] = uint256{}; @@ -375,7 +375,7 @@ public: { { - STObject st(kSfGeneric); + STObject st(sfGeneric); Buffer b(1); BEAST_EXPECT(!b.empty()); st[sf4] = std::move(b); @@ -392,7 +392,7 @@ public: BEAST_EXPECT(Slice(st[sf5]).size() == 2); } { - STObject st(sotOuter, kSfGeneric); + STObject st(sotOuter, sfGeneric); BEAST_EXPECT(st[sf5] == Slice{}); BEAST_EXPECT(!!st[~sf5]); BEAST_EXPECT(!!~st[~sf5]); @@ -408,7 +408,7 @@ public: // UDT blobs { - STObject st(kSfGeneric); + STObject st(sfGeneric); BEAST_EXPECT(!st[~sf5]); auto const kp = generateKeyPair(KeyType::Secp256k1, generateSeed("masterpassphrase")); st[sf5] = kp.first; @@ -419,7 +419,7 @@ public: { auto const& sf = sfIndexes; - STObject st(kSfGeneric); + STObject st(sfGeneric); std::vector v; v.emplace_back(1); v.emplace_back(2); @@ -446,7 +446,7 @@ public: {sf3, SoeDefault}, }; - STObject st(sot, kSfGeneric); + STObject st(sot, sfGeneric); auto const& cst(st); BEAST_EXPECT(cst[sf1].empty()); BEAST_EXPECT(!cst[~sf2]); diff --git a/src/test/protocol/STTx_test.cpp b/src/test/protocol/STTx_test.cpp index 42cb2bb9a7..21518d1406 100644 --- a/src/test/protocol/STTx_test.cpp +++ b/src/test/protocol/STTx_test.cpp @@ -1390,7 +1390,7 @@ public: // Lambda that returns a Payment STObject. auto getPayment = [kp1, id1, id2]() { // Account id1 pays account id2 10,000 XRP. - STObject payment(kSfGeneric); + STObject payment(sfGeneric); payment.setFieldU16(sfTransactionType, ttPAYMENT); payment.setAccountID(sfAccount, id1); payment.setAccountID(sfDestination, id2); diff --git a/src/test/rpc/Simulate_test.cpp b/src/test/rpc/Simulate_test.cpp index b66b3dfe9f..60452e386a 100644 --- a/src/test/rpc/Simulate_test.cpp +++ b/src/test/rpc/Simulate_test.cpp @@ -66,7 +66,7 @@ class Simulate_test : public beast::unit_test::Suite { auto const unHexed = strUnHex(result[jss::tx_blob].asString()); SerialIter sitTrans(makeSlice(*unHexed)); // NOLINT(bugprone-unchecked-optional-access) - txJson = STObject(std::ref(sitTrans), kSfGeneric).getJson(JsonOptions::Values::None); + txJson = STObject(std::ref(sitTrans), sfGeneric).getJson(JsonOptions::Values::None); } BEAST_EXPECT(txJson[jss::TransactionType] == tx[jss::TransactionType]); BEAST_EXPECT(txJson[jss::Account] == tx[jss::Account]); @@ -162,7 +162,7 @@ class Simulate_test : public beast::unit_test::Suite { auto unHexed = strUnHex(txResult[jss::meta_blob].asString()); SerialIter sitTrans(makeSlice(*unHexed)); // NOLINT(bugprone-unchecked-optional-access) - return STObject(std::ref(sitTrans), kSfGeneric).getJson(JsonOptions::Values::None); + return STObject(std::ref(sitTrans), sfGeneric).getJson(JsonOptions::Values::None); } return txResult[jss::meta]; diff --git a/src/tests/libxrpl/protocol_autogen/TestHelpers.h b/src/tests/libxrpl/protocol_autogen/TestHelpers.h index b8449f2bca..a32ab5e20c 100644 --- a/src/tests/libxrpl/protocol_autogen/TestHelpers.h +++ b/src/tests/libxrpl/protocol_autogen/TestHelpers.h @@ -159,7 +159,7 @@ canonical_ARRAY() inline STObject canonical_OBJECT() { - return STObject{kSfGeneric}; + return STObject{sfGeneric}; } inline STPathSet diff --git a/src/xrpld/rpc/handlers/transaction/Simulate.cpp b/src/xrpld/rpc/handlers/transaction/Simulate.cpp index ba108eb5b2..7a11b728ce 100644 --- a/src/xrpld/rpc/handlers/transaction/Simulate.cpp +++ b/src/xrpld/rpc/handlers/transaction/Simulate.cpp @@ -194,7 +194,7 @@ getTxJsonFromParams(json::Value const& params) try { SerialIter sitTrans(makeSlice(*unHexed)); - txJson = STObject(std::ref(sitTrans), kSfGeneric).getJson(JsonOptions::Values::None); + txJson = STObject(std::ref(sitTrans), sfGeneric).getJson(JsonOptions::Values::None); } catch (std::runtime_error const&) { From 9cb07406735a6bb967015495c33668d904d9ddee Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Wed, 20 May 2026 16:24:09 -0400 Subject: [PATCH 004/135] fix: Fix multisign and signfor to check for delegate (#7064) --- src/libxrpl/protocol/STTx.cpp | 4 +- src/test/app/Delegate_test.cpp | 146 +++++++++++++++++++++++ src/xrpld/rpc/detail/TransactionSign.cpp | 8 +- 3 files changed, 155 insertions(+), 3 deletions(-) diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 878aa296dd..8636c99284 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -535,8 +535,10 @@ STTx::checkMultiSign(Rules const& rules, STObject const& sigObject) const { // Used inside the loop in multiSignHelper to enforce that // the account owner may not multisign for themselves. + // For delegated transactions sfDelegate is the account whose signer list is checked, + // the delegate account itself can not be among the signers. auto const txnAccountID = - &sigObject != this ? std::nullopt : std::optional(getAccountID(sfAccount)); + &sigObject != this ? std::nullopt : std::optional(getFeePayer()); // We can ease the computational load inside the loop a bit by // pre-constructing part of the data that we hash. Fill a Serializer diff --git a/src/test/app/Delegate_test.cpp b/src/test/app/Delegate_test.cpp index 06884e9931..588aeee634 100644 --- a/src/test/app/Delegate_test.cpp +++ b/src/test/app/Delegate_test.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -1879,6 +1880,149 @@ class Delegate_test : public beast::unit_test::Suite BEAST_EXPECT(env.balance(edward) == edwardBalance); } + void + testMultiSignDelegatorAsSigner() + { + // checkMultiSign disallows the owner of the account to + // be part of the multisigner list. When it is a delegated transaction, + // the delegate account should not be part of the multisigner list. + testcase("test delegator as multisigner in delegate's signer list"); + using namespace jtx; + + Env env(*this); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + Account const daria{"daria"}; + env.fund(XRP(100000), alice, bob, carol, daria); + env.close(); + + env(delegate::set(alice, bob, {"Payment"})); + env.close(); + + // bob's signer list includes the delegator alice and daria + env(signers(bob, 2, {{alice, 1}, {daria, 1}})); + env.close(); + + auto aliceBalance = env.balance(alice); + auto bobBalance = env.balance(bob); + auto carolBalance = env.balance(carol); + auto const amt = 100; + + // alice can sign as a multisigner for bob + env(pay(alice, carol, XRP(100)), Fee(XRP(10)), delegate::As(bob), Msig(alice, daria)); + env.close(); + + BEAST_EXPECT(env.balance(alice) == aliceBalance - XRP(amt)); + BEAST_EXPECT(env.balance(bob) == bobBalance - XRP(10)); + BEAST_EXPECT(env.balance(carol) == carolBalance + XRP(amt)); + + // alice can not sign as a multisigner if she sent the transaction by herself. + env(pay(alice, carol, XRP(100)), Fee(XRP(10)), Msig(alice, daria), Ter(telENV_RPC_FAILED)); + env.close(); + + // Get new balances + aliceBalance = env.balance(alice); + bobBalance = env.balance(bob); + carolBalance = env.balance(carol); + + // bob (the delegate) should not appear as a multisigner in his transaction sent on behalf + // of alice. STTx::checkMultiSign catches this at the local-check stage, so the jtx + // framework returns telENV_RPC_FAILED. + env(pay(alice, carol, XRP(50)), + Fee(XRP(10)), + delegate::As(bob), + Msig(alice, bob), + Ter(telENV_RPC_FAILED)); + env.close(); + BEAST_EXPECT(env.balance(alice) == aliceBalance); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(carol) == carolBalance); + } + + void + testSignForDelegated() + { + // In sortAndValidateSigners, if it is a delegated transaction, the delegate account is + // the forbidden account from appearing in its own Signers array. + testcase("test sign_for with delegated transaction"); + using namespace jtx; + + Env env(*this); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + Account const daria{"daria"}; + env.fund(XRP(100000), alice, bob, carol, daria); + env.close(); + + env(delegate::set(alice, bob, {"Payment"})); + env.close(); + + // bob's signer list includes the delegator alice and daria + env(signers(bob, 2, {{alice, 1}, {daria, 1}})); + env.close(); + + auto const baseFee = env.current()->fees().base; + + auto const sendAmt = 1'000'000; + auto makeDelegateTx = [&]() -> json::Value { + json::Value jv; + jv[jss::tx_json][jss::Account] = alice.human(); + jv[jss::tx_json][sfDelegate.jsonName] = bob.human(); + jv[jss::tx_json][jss::TransactionType] = jss::Payment; + jv[jss::tx_json][jss::Destination] = carol.human(); + jv[jss::tx_json][jss::Amount] = sendAmt; + jv[jss::tx_json][jss::Fee] = std::to_string((10 * baseFee).drops()); + jv[jss::tx_json][jss::Sequence] = env.seq(alice); + jv[jss::tx_json][jss::SigningPubKey] = ""; + return jv; + }; + + // The delegator alice and daria both sign via sign_for, which is valid + { + auto const aliceBalance = env.balance(alice); + auto const bobBalance = env.balance(bob); + auto const dariaBalance = env.balance(daria); + auto const carolBalance = env.balance(carol); + + json::Value jv = makeDelegateTx(); + jv[jss::account] = alice.human(); + jv[jss::secret] = alice.name(); + auto jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result]; + BEAST_EXPECT(jrr[jss::status] == "success"); + + json::Value jv2; + jv2[jss::tx_json] = jrr[jss::tx_json]; + jv2[jss::account] = daria.human(); + jv2[jss::secret] = daria.name(); + jrr = env.rpc("json", "sign_for", to_string(jv2))[jss::result]; + BEAST_EXPECT(jrr[jss::status] == "success"); + + json::Value jvSubmit; + jvSubmit[jss::tx_json] = jrr[jss::tx_json]; + jrr = env.rpc("json", "submit_multisigned", to_string(jvSubmit))[jss::result]; + BEAST_EXPECT(jrr[jss::status] == "success"); + env.close(); + BEAST_EXPECT(env.balance(alice) == aliceBalance - XRPAmount(sendAmt)); + BEAST_EXPECT(env.balance(bob) == bobBalance - (10 * baseFee)); + BEAST_EXPECT(env.balance(daria) == dariaBalance); + BEAST_EXPECT(env.balance(carol) == carolBalance + XRPAmount(sendAmt)); + } + + // The delegated account bob attempts sign_for, will be rejected. + { + json::Value jv = makeDelegateTx(); + jv[jss::account] = bob.human(); + jv[jss::secret] = bob.name(); + auto jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result]; + BEAST_EXPECT(jrr[jss::status] == "error"); + BEAST_EXPECT( + jrr[jss::error_message].asString().find( + "A Signer may not be the transaction's Account") != std::string::npos); + } + } + void testPermissionValue(FeatureBitset features) { @@ -2086,6 +2230,8 @@ class Delegate_test : public beast::unit_test::Suite testSingleSignBadSecret(); testMultiSign(); testMultiSignQuorumNotMet(); + testMultiSignDelegatorAsSigner(); + testSignForDelegated(); testPermissionValue(all); testTxRequireFeatures(all); testTxDelegableCount(); diff --git a/src/xrpld/rpc/detail/TransactionSign.cpp b/src/xrpld/rpc/detail/TransactionSign.cpp index 4c47914d66..9faa82dfe8 100644 --- a/src/xrpld/rpc/detail/TransactionSign.cpp +++ b/src/xrpld/rpc/detail/TransactionSign.cpp @@ -1255,7 +1255,9 @@ transactionSignFor( signers.emplaceBack(std::move(signer)); // The array must be sorted and validated. - auto err = sortAndValidateSigners(signers, (*sttx)[sfAccount]); + // For delegated transactions, the delegate account is + // the one forbidden from appearing in its own Signers array. + auto err = sortAndValidateSigners(signers, sttx->getFeePayer()); if (RPC::containsError(err)) return err; } @@ -1422,7 +1424,9 @@ transactionSubmitMultiSigned( } // The array must be sorted and validated. - auto err = sortAndValidateSigners(signers, srcAddressID); + // For delegated transactions, getFeePayer() returns sfDelegate, + // that account is the one forbidden from appearing in its own Signers array. + auto err = sortAndValidateSigners(signers, stTx->getFeePayer()); if (RPC::containsError(err)) return err; From 8c0080020f2a9c2d4b4dc7f6e059bf49b82ee867 Mon Sep 17 00:00:00 2001 From: Shawn Xie <35279399+shawnxie999@users.noreply.github.com> Date: Wed, 20 May 2026 17:10:04 -0400 Subject: [PATCH 005/135] fix: Update pDEX invariant firing under a valid offer deletion (#7118) Co-authored-by: Peter Chen --- .../tx/invariants/PermissionedDEXInvariant.h | 7 ++- .../invariants/PermissionedDEXInvariant.cpp | 10 +++- src/test/app/PermissionedDEX_test.cpp | 55 +++++++++++++++++++ 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/include/xrpl/tx/invariants/PermissionedDEXInvariant.h b/include/xrpl/tx/invariants/PermissionedDEXInvariant.h index 3784a32840..654ed3e009 100644 --- a/include/xrpl/tx/invariants/PermissionedDEXInvariant.h +++ b/include/xrpl/tx/invariants/PermissionedDEXInvariant.h @@ -10,9 +10,10 @@ namespace xrpl { class ValidPermissionedDEX { - bool regularOffers_ = false; - bool badHybridsOld_ = false; // pre-fixCleanup3_1_3: missing field/domain or size > 1 - bool badHybrids_ = false; // post-fixCleanup3_1_3: also catches size == 0 (size != 1) + bool regularOffersOld_ = false; // pre-fixCleanup3_2_0: also flags deleted offers + bool regularOffers_ = false; // post-fixCleanup3_2_0: excludes deleted offers + bool badHybridsOld_ = false; // pre-fixCleanup3_1_3: missing field/domain or size > 1 + bool badHybrids_ = false; // post-fixCleanup3_1_3: also catches size == 0 (size != 1) hash_set domains_; public: diff --git a/src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp b/src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp index b480b90a31..282df85302 100644 --- a/src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp +++ b/src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp @@ -20,7 +20,7 @@ namespace xrpl { void ValidPermissionedDEX::visitEntry( - bool, + bool isDelete, std::shared_ptr const& before, std::shared_ptr const& after) { @@ -38,7 +38,9 @@ ValidPermissionedDEX::visitEntry( } else { - regularOffers_ = true; + regularOffersOld_ = true; + if (!isDelete) + regularOffers_ = true; } // pre-fixCleanup3_1_3: hybrid offer missing domain, missing @@ -100,7 +102,9 @@ ValidPermissionedDEX::finalize( } } - if (regularOffers_) + bool const hasRegularOffers = + view.rules().enabled(fixCleanup3_2_0) ? regularOffers_ : regularOffersOld_; + if (hasRegularOffers) { JLOG(j.fatal()) << "Invariant failed: domain transaction" " affected regular offers"; diff --git a/src/test/app/PermissionedDEX_test.cpp b/src/test/app/PermissionedDEX_test.cpp index be377c0c1d..3a95fb2f92 100644 --- a/src/test/app/PermissionedDEX_test.cpp +++ b/src/test/app/PermissionedDEX_test.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include // IWYU pragma: keep #include @@ -34,6 +35,7 @@ #include #include #include +#include #include #include @@ -1455,6 +1457,54 @@ class PermissionedDEX_test : public beast::unit_test::Suite } } + void + testCancelRegularOfferWithDomainCreate(FeatureBitset features) + { + bool const fixEnabled = features[fixCleanup3_2_0]; + + testcase << "Cancel regular offer via domain OfferCreate" + << (fixEnabled ? " (fixCleanup3_2_0 enabled)" : " (fixCleanup3_2_0 disabled)"); + + // An OfferCreate with sfDomainID and sfOfferSequence pointing to + // the user's own non-domain offer should atomically cancel the + // regular offer and place the new domain offer. + // + // Pre-fixCleanup3_2_0: ValidPermissionedDEX flagged the deleted + // regular offer, so the transaction failed with tecINVARIANT_FAILED. + // Post-fixCleanup3_2_0: the invariant ignores deletions and the + // transaction succeeds. + + Env env(*this, features); + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = + PermissionedDEX(env); + + auto const regularSeq = env.seq(bob); + env(offer(bob, XRP(10), USD(10))); + env.close(); + BEAST_EXPECT(checkOffer(env, bob, regularSeq, XRP(10), USD(10), 0, false)); + + auto const domainSeq = env.seq(bob); + if (fixEnabled) + { + env(offer(bob, XRP(20), USD(20)), + Domain(domainID), + Json(jss::OfferSequence, regularSeq)); + env.close(); + BEAST_EXPECT(!offerExists(env, bob, regularSeq)); + BEAST_EXPECT(checkOffer(env, bob, domainSeq, XRP(20), USD(20), 0, true)); + } + else + { + env(offer(bob, XRP(20), USD(20)), + Domain(domainID), + Json(jss::OfferSequence, regularSeq), + Ter(tecINVARIANT_FAILED)); + env.close(); + BEAST_EXPECT(offerExists(env, bob, regularSeq)); + BEAST_EXPECT(!offerExists(env, bob, domainSeq)); + } + } + public: void run() override @@ -1478,6 +1528,11 @@ public: testHybridOfferDirectories(all); testHybridMalformedOffer(all); testHybridMalformedOffer(all - fixCleanup3_1_3); + + // Cancelling a regular offer in a domain OfferCreate is allowed + // only after fixCleanup3_2_0. + testCancelRegularOfferWithDomainCreate(all); + testCancelRegularOfferWithDomainCreate(all - fixCleanup3_2_0); } }; From a830ab10efed8d3e59ef2fc15d66efdf9c6bb0d8 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 20 May 2026 22:31:15 +0100 Subject: [PATCH 006/135] style: More clang-tidy identifier renaming (#7290) --- .clang-tidy | 3 + include/xrpl/basics/TaggedCache.h | 22 +- include/xrpl/basics/TaggedCache.ipp | 44 +-- include/xrpl/basics/scope.h | 58 ++-- include/xrpl/beast/unit_test/reporter.h | 22 +- include/xrpl/core/Coro.ipp | 10 +- include/xrpl/core/Job.h | 2 +- include/xrpl/core/JobQueue.h | 4 +- include/xrpl/ledger/BookDirs.h | 8 +- include/xrpl/ledger/OpenView.h | 2 +- include/xrpl/ledger/detail/RawStateTable.h | 10 +- include/xrpl/net/HTTPClientSSLContext.h | 12 +- include/xrpl/protocol/Book.h | 18 +- include/xrpl/protocol/ErrorCodes.h | 8 +- include/xrpl/protocol/STPathSet.h | 28 +- include/xrpl/protocol/STTx.h | 4 +- include/xrpl/resource/detail/Entry.h | 10 +- include/xrpl/resource/detail/Key.h | 4 +- include/xrpl/resource/detail/Logic.h | 32 +- include/xrpl/server/Handoff.h | 2 +- include/xrpl/server/Port.h | 48 +-- include/xrpl/server/detail/BaseHTTPPeer.h | 30 +- include/xrpl/server/detail/BasePeer.h | 4 +- include/xrpl/server/detail/BaseWSPeer.h | 40 +-- include/xrpl/server/detail/Door.h | 36 +-- include/xrpl/server/detail/PlainHTTPPeer.h | 12 +- include/xrpl/server/detail/SSLHTTPPeer.h | 22 +- include/xrpl/server/detail/SSLWSPeer.h | 6 +- include/xrpl/server/detail/ServerImpl.h | 12 +- src/libxrpl/basics/ResolverAsio.cpp | 30 +- src/libxrpl/beast/insight/StatsDCollector.cpp | 24 +- src/libxrpl/core/detail/Job.cpp | 4 +- src/libxrpl/core/detail/JobQueue.cpp | 4 +- src/libxrpl/ledger/BookDirs.cpp | 18 +- src/libxrpl/ledger/OpenView.cpp | 12 +- src/libxrpl/protocol/ErrorCodes.cpp | 2 +- src/libxrpl/protocol/STTx.cpp | 14 +- src/libxrpl/server/Port.cpp | 49 ++-- src/test/app/AMMMPT_test.cpp | 50 ++-- src/test/app/AMM_test.cpp | 6 +- src/test/app/FeeVote_test.cpp | 82 +++--- src/test/app/LedgerHistory_test.cpp | 2 +- src/test/app/LedgerLoad_test.cpp | 6 +- src/test/app/LedgerMaster_test.cpp | 4 +- src/test/app/LedgerReplay_test.cpp | 8 +- src/test/app/LoadFeeTrack_test.cpp | 6 +- src/test/app/MPToken_test.cpp | 2 +- src/test/app/NetworkID_test.cpp | 2 +- src/test/app/PathMPT_test.cpp | 6 +- src/test/app/Path_test.cpp | 6 +- src/test/app/RCLValidations_test.cpp | 4 +- src/test/app/Regression_test.cpp | 4 +- src/test/app/SHAMapStore_test.cpp | 2 +- src/test/app/Transaction_ordering_test.cpp | 4 +- src/test/app/TxQ_test.cpp | 8 +- src/test/app/ValidatorList_test.cpp | 4 +- src/test/app/XChain_test.cpp | 230 ++++++++------- src/test/consensus/Consensus_test.cpp | 4 +- src/test/consensus/NegativeUNL_test.cpp | 152 +++++----- src/test/consensus/Validations_test.cpp | 24 +- src/test/core/Config_test.cpp | 114 ++++---- src/test/core/Coroutine_test.cpp | 4 +- src/test/csf/Scheduler.h | 14 +- src/test/jtx/impl/AMMTest.cpp | 6 +- src/test/jtx/impl/Env.cpp | 2 +- src/test/jtx/impl/JSONRPCClient.cpp | 8 +- src/test/jtx/impl/TestHelpers.cpp | 6 +- src/test/jtx/impl/WSClient.cpp | 8 +- src/test/jtx/impl/envconfig.cpp | 12 +- src/test/jtx/impl/permissioned_dex.cpp | 6 +- src/test/jtx/impl/xchain_bridge.cpp | 23 +- src/test/jtx/permissioned_dex.h | 2 +- src/test/jtx/xchain_bridge.h | 18 +- src/test/ledger/SkipList_test.cpp | 2 +- src/test/ledger/View_test.cpp | 8 +- src/test/nodestore/Database_test.cpp | 6 +- src/test/nodestore/Timing_test.cpp | 24 +- src/test/overlay/compression_test.cpp | 21 +- src/test/overlay/reduce_relay_test.cpp | 44 ++- src/test/overlay/short_read_test.cpp | 22 +- src/test/overlay/tx_reduce_relay_test.cpp | 16 +- src/test/peerfinder/PeerFinder_test.cpp | 4 +- src/test/rpc/AccountTx_test.cpp | 4 +- src/test/rpc/Book_test.cpp | 4 +- src/test/rpc/DeliveredAmount_test.cpp | 4 +- src/test/rpc/JSONRPC_test.cpp | 4 +- src/test/rpc/KeyGeneration_test.cpp | 104 +++---- src/test/rpc/LedgerClosed_test.cpp | 2 +- src/test/rpc/LedgerEntry_test.cpp | 6 +- src/test/rpc/LedgerRPC_test.cpp | 4 +- src/test/rpc/LedgerRequest_test.cpp | 6 +- src/test/rpc/NoRippleCheck_test.cpp | 2 +- src/test/rpc/RPCCall_test.cpp | 2 +- src/test/rpc/Simulate_test.cpp | 6 +- src/test/rpc/Subscribe_test.cpp | 4 +- src/test/rpc/TransactionEntry_test.cpp | 4 +- src/test/rpc/Transaction_test.cpp | 6 +- src/test/rpc/Version_test.cpp | 12 +- src/test/server/ServerStatus_test.cpp | 6 +- src/test/server/Server_test.cpp | 8 +- src/test/unit_test/multi_runner.cpp | 114 ++++---- src/test/unit_test/multi_runner.h | 40 +-- src/xrpld/app/ledger/LedgerHistory.cpp | 43 ++- src/xrpld/app/ledger/LedgerHistory.h | 12 +- src/xrpld/app/ledger/LedgerMaster.h | 12 +- src/xrpld/app/ledger/OpenLedger.h | 4 +- src/xrpld/app/ledger/detail/LedgerCleaner.cpp | 2 +- src/xrpld/app/ledger/detail/LedgerMaster.cpp | 41 ++- src/xrpld/app/ledger/detail/OpenLedger.cpp | 12 +- src/xrpld/app/main/Application.cpp | 64 ++-- src/xrpld/app/main/BasicApp.cpp | 4 +- src/xrpld/app/main/BasicApp.h | 4 +- src/xrpld/app/main/Main.cpp | 34 +-- src/xrpld/app/misc/FeeVoteImpl.cpp | 21 +- src/xrpld/app/misc/NetworkOPs.cpp | 99 ++++--- src/xrpld/app/misc/SHAMapStoreImp.cpp | 24 +- src/xrpld/app/misc/SHAMapStoreImp.h | 6 +- src/xrpld/app/misc/ValidatorSite.h | 8 +- src/xrpld/app/misc/detail/ValidatorSite.cpp | 32 +- src/xrpld/app/misc/detail/WorkSSL.cpp | 6 +- src/xrpld/consensus/Consensus.cpp | 26 +- src/xrpld/consensus/Consensus.h | 22 +- src/xrpld/consensus/ConsensusParms.h | 28 +- src/xrpld/consensus/DisputedTx.h | 12 +- src/xrpld/consensus/Validations.h | 24 +- src/xrpld/core/Config.h | 130 ++++----- src/xrpld/core/detail/Config.cpp | 276 +++++++++--------- src/xrpld/overlay/Message.h | 2 +- src/xrpld/overlay/Slot.h | 4 +- src/xrpld/overlay/detail/ConnectAttempt.cpp | 8 +- src/xrpld/overlay/detail/Handshake.cpp | 12 +- src/xrpld/overlay/detail/Message.cpp | 2 +- src/xrpld/overlay/detail/OverlayImpl.cpp | 48 +-- src/xrpld/overlay/detail/OverlayImpl.h | 6 +- src/xrpld/overlay/detail/PeerImp.cpp | 32 +- src/xrpld/overlay/detail/PeerImp.h | 20 +- src/xrpld/overlay/detail/ProtocolMessage.h | 66 ++--- src/xrpld/overlay/detail/TxMetrics.cpp | 6 +- src/xrpld/overlay/detail/TxMetrics.h | 2 +- src/xrpld/peerfinder/detail/Counts.h | 54 ++-- .../peerfinder/detail/PeerfinderConfig.cpp | 14 +- src/xrpld/peerfinder/detail/SlotImp.cpp | 10 +- src/xrpld/peerfinder/detail/SlotImp.h | 24 +- src/xrpld/rpc/ServerHandler.h | 10 +- src/xrpld/rpc/detail/AssetCache.h | 8 +- src/xrpld/rpc/detail/PathRequest.cpp | 56 ++-- src/xrpld/rpc/detail/PathRequest.h | 6 +- src/xrpld/rpc/detail/Pathfinder.cpp | 12 +- src/xrpld/rpc/detail/Pathfinder.h | 2 +- src/xrpld/rpc/detail/RPCCall.cpp | 14 +- src/xrpld/rpc/detail/RPCHandler.cpp | 2 +- src/xrpld/rpc/detail/RPCSub.cpp | 6 +- src/xrpld/rpc/detail/Role.cpp | 12 +- src/xrpld/rpc/detail/ServerHandler.cpp | 56 ++-- src/xrpld/rpc/detail/TransactionSign.cpp | 18 +- src/xrpld/rpc/detail/WSInfoSub.h | 4 +- src/xrpld/rpc/handlers/orderbook/PathFind.cpp | 2 +- .../rpc/handlers/orderbook/RipplePathFind.cpp | 2 +- src/xrpld/rpc/handlers/server_info/Version.h | 2 +- src/xrpld/rpc/json_body.h | 6 +- 160 files changed, 1726 insertions(+), 1746 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index a444957a0e..ee6bde6eba 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -191,8 +191,11 @@ CheckOptions: readability-identifier-naming.ParameterCase: camelBack readability-identifier-naming.FunctionCase: camelBack readability-identifier-naming.MemberCase: camelBack + readability-identifier-naming.PrivateMemberCase: camelBack readability-identifier-naming.PrivateMemberSuffix: _ + readability-identifier-naming.ProtectedMemberCase: camelBack readability-identifier-naming.ProtectedMemberSuffix: _ + readability-identifier-naming.PublicMemberCase: camelBack readability-identifier-naming.PublicMemberSuffix: "" readability-identifier-naming.GlobalFunctionIgnoredRegexp: "^(to_string|hash_append|tuple_hash)$" diff --git a/include/xrpl/basics/TaggedCache.h b/include/xrpl/basics/TaggedCache.h index 52b866cdad..fca9eabde2 100644 --- a/include/xrpl/basics/TaggedCache.h +++ b/include/xrpl/basics/TaggedCache.h @@ -181,14 +181,14 @@ private: beast::insight::Collector::ptr const& collector) : hook(collector->makeHook(handler)) , size(collector->makeGauge(prefix, "size")) - , hit_rate(collector->makeGauge(prefix, "hit_rate")) + , hitRate(collector->makeGauge(prefix, "hit_rate")) { } beast::insight::Hook hook; beast::insight::Gauge size; - beast::insight::Gauge hit_rate; + beast::insight::Gauge hitRate; std::size_t hits{0}; std::size_t misses{0}; @@ -197,16 +197,16 @@ private: class KeyOnlyEntry { public: - clock_type::time_point last_access; + clock_type::time_point lastAccess; - explicit KeyOnlyEntry(clock_type::time_point const& lastAccess) : last_access(lastAccess) + explicit KeyOnlyEntry(clock_type::time_point const& lastAccess) : lastAccess(lastAccess) { } void touch(clock_type::time_point const& now) { - last_access = now; + lastAccess = now; } }; @@ -214,10 +214,10 @@ private: { public: shared_weak_combo_pointer_type ptr; - clock_type::time_point last_access; + clock_type::time_point lastAccess; ValueEntry(clock_type::time_point const& lastAccess, shared_pointer_type const& ptr) - : ptr(ptr), last_access(lastAccess) + : ptr(ptr), lastAccess(lastAccess) { } @@ -246,7 +246,7 @@ private: void touch(clock_type::time_point const& now) { - last_access = now; + lastAccess = now; } }; @@ -286,13 +286,13 @@ private: std::string name_; // Desired number of cache entries (0 = ignore) - int const target_size_; + int const targetSize_; // Desired maximum cache age - clock_type::duration const target_age_; + clock_type::duration const targetAge_; // Number of items cached - int cache_count_{0}; + int cacheCount_{0}; cache_type cache_; // Hold strong reference to recent objects std::uint64_t hits_{0}; std::uint64_t misses_{0}; diff --git a/include/xrpl/basics/TaggedCache.ipp b/include/xrpl/basics/TaggedCache.ipp index 8ef9e16570..cee02749c6 100644 --- a/include/xrpl/basics/TaggedCache.ipp +++ b/include/xrpl/basics/TaggedCache.ipp @@ -34,8 +34,8 @@ inline TaggedCache< , clock_(clock) , stats_(name, std::bind(&TaggedCache::collectMetrics, this), collector) , name_(name) - , target_size_(size) - , target_age_(expiration) + , targetSize_(size) + , targetAge_(expiration) { } @@ -86,7 +86,7 @@ TaggedCache(cache_.size()) <= target_size_)) + if (targetSize_ == 0 || (static_cast(cache_.size()) <= targetSize_)) { - whenExpire = now - target_age_; + whenExpire = now - targetAge_; } else { - whenExpire = now - (target_age_ * target_size_ / cache_.size()); + whenExpire = now - (targetAge_ * targetSize_ / cache_.size()); clock_type::duration const minimumAge(std::chrono::seconds(1)); if (whenExpire > (now - minimumAge)) whenExpire = now - minimumAge; JLOG(journal_.trace()) - << name_ << " is growing fast " << cache_.size() << " of " << target_size_ - << " aging at " << (now - whenExpire).count() << " of " << target_age_.count(); + << name_ << " is growing fast " << cache_.size() << " of " << targetSize_ + << " aging at " << (now - whenExpire).count() << " of " << targetAge_.count(); } std::vector workers; @@ -242,7 +242,7 @@ TaggedCachesecond.last_access = now; + it->second.lastAccess = now; return inserted; } @@ -626,7 +626,7 @@ TaggedCachesecond.last_access <= whenExpire) + else if (cit->second.lastAccess <= whenExpire) { // strong, expired ++cacheRemovals; @@ -773,12 +773,12 @@ TaggedCachesecond.last_access > now) + if (cit->second.lastAccess > now) { - cit->second.last_access = now; + cit->second.lastAccess = now; ++cit; } - else if (cit->second.last_access <= whenExpire) + else if (cit->second.lastAccess <= whenExpire) { cit = partition.erase(cit); } diff --git a/include/xrpl/basics/scope.h b/include/xrpl/basics/scope.h index cd2a5299b2..cfd21e6e30 100644 --- a/include/xrpl/basics/scope.h +++ b/include/xrpl/basics/scope.h @@ -24,20 +24,20 @@ namespace xrpl { template class ScopeExit { - EF exit_function_; - bool execute_on_destruction_{true}; + EF exitFunction_; + bool executeOnDestruction_{true}; public: ~ScopeExit() { - if (execute_on_destruction_) - exit_function_(); + if (executeOnDestruction_) + exitFunction_(); } ScopeExit(ScopeExit&& rhs) noexcept( std::is_nothrow_move_constructible_v || std::is_nothrow_copy_constructible_v) - : exit_function_{std::forward(rhs.exit_function_)} - , execute_on_destruction_{rhs.execute_on_destruction_} + : exitFunction_{std::forward(rhs.exitFunction_)} + , executeOnDestruction_{rhs.executeOnDestruction_} { rhs.release(); } @@ -51,7 +51,7 @@ public: std::enable_if_t< !std::is_same_v, ScopeExit> && std::is_constructible_v>* = 0) noexcept - : exit_function_{std::forward(f)} + : exitFunction_{std::forward(f)} { static_assert(std::is_nothrow_constructible_v(f))>); } @@ -59,7 +59,7 @@ public: void release() noexcept { - execute_on_destruction_ = false; + executeOnDestruction_ = false; } }; @@ -69,22 +69,22 @@ ScopeExit(EF) -> ScopeExit; template class ScopeFail { - EF exit_function_; - bool execute_on_destruction_{true}; - int uncaught_on_creation_{std::uncaught_exceptions()}; + EF exitFunction_; + bool executeOnDestruction_{true}; + int uncaughtOnCreation_{std::uncaught_exceptions()}; public: ~ScopeFail() { - if (execute_on_destruction_ && std::uncaught_exceptions() > uncaught_on_creation_) - exit_function_(); + if (executeOnDestruction_ && std::uncaught_exceptions() > uncaughtOnCreation_) + exitFunction_(); } ScopeFail(ScopeFail&& rhs) noexcept( std::is_nothrow_move_constructible_v || std::is_nothrow_copy_constructible_v) - : exit_function_{std::forward(rhs.exit_function_)} - , execute_on_destruction_{rhs.execute_on_destruction_} - , uncaught_on_creation_{rhs.uncaught_on_creation_} + : exitFunction_{std::forward(rhs.exitFunction_)} + , executeOnDestruction_{rhs.executeOnDestruction_} + , uncaughtOnCreation_{rhs.uncaughtOnCreation_} { rhs.release(); } @@ -98,7 +98,7 @@ public: std::enable_if_t< !std::is_same_v, ScopeFail> && std::is_constructible_v>* = 0) noexcept - : exit_function_{std::forward(f)} + : exitFunction_{std::forward(f)} { static_assert(std::is_nothrow_constructible_v(f))>); } @@ -106,7 +106,7 @@ public: void release() noexcept { - execute_on_destruction_ = false; + executeOnDestruction_ = false; } }; @@ -116,22 +116,22 @@ ScopeFail(EF) -> ScopeFail; template class ScopeSuccess { - EF exit_function_; - bool execute_on_destruction_{true}; - int uncaught_on_creation_{std::uncaught_exceptions()}; + EF exitFunction_; + bool executeOnDestruction_{true}; + int uncaughtOnCreation_{std::uncaught_exceptions()}; public: - ~ScopeSuccess() noexcept(noexcept(exit_function_())) + ~ScopeSuccess() noexcept(noexcept(exitFunction_())) { - if (execute_on_destruction_ && std::uncaught_exceptions() <= uncaught_on_creation_) - exit_function_(); + if (executeOnDestruction_ && std::uncaught_exceptions() <= uncaughtOnCreation_) + exitFunction_(); } ScopeSuccess(ScopeSuccess&& rhs) noexcept( std::is_nothrow_move_constructible_v || std::is_nothrow_copy_constructible_v) - : exit_function_{std::forward(rhs.exit_function_)} - , execute_on_destruction_{rhs.execute_on_destruction_} - , uncaught_on_creation_{rhs.uncaught_on_creation_} + : exitFunction_{std::forward(rhs.exitFunction_)} + , executeOnDestruction_{rhs.executeOnDestruction_} + , uncaughtOnCreation_{rhs.uncaughtOnCreation_} { rhs.release(); } @@ -146,14 +146,14 @@ public: !std::is_same_v, ScopeSuccess> && std::is_constructible_v>* = 0) noexcept(std::is_nothrow_constructible_v || std::is_nothrow_constructible_v) - : exit_function_{std::forward(f)} + : exitFunction_{std::forward(f)} { } void release() noexcept { - execute_on_destruction_ = false; + executeOnDestruction_ = false; } }; diff --git a/include/xrpl/beast/unit_test/reporter.h b/include/xrpl/beast/unit_test/reporter.h index b4b6c69fa4..1fdbb451a6 100644 --- a/include/xrpl/beast/unit_test/reporter.h +++ b/include/xrpl/beast/unit_test/reporter.h @@ -77,8 +77,8 @@ private: std::ostream& os_; Results results_; - SuiteResults suite_results_; - CaseResults case_results_; + SuiteResults suiteResults_; + CaseResults caseResults_; public: Reporter(Reporter const&) = delete; @@ -196,22 +196,22 @@ template void Reporter::onSuiteBegin(SuiteInfo const& info) { - suite_results_ = SuiteResults{info.fullName()}; + suiteResults_ = SuiteResults{info.fullName()}; } template void Reporter::onSuiteEnd() { - results_.add(suite_results_); + results_.add(suiteResults_); } template void Reporter::onCaseBegin(std::string const& name) { - case_results_ = CaseResults(name); - os_ << suite_results_.name << (case_results_.name.empty() ? "" : (" " + case_results_.name)) + caseResults_ = CaseResults(name); + os_ << suiteResults_.name << (caseResults_.name.empty() ? "" : (" " + caseResults_.name)) << std::endl; } @@ -219,23 +219,23 @@ template void Reporter::onCaseEnd() { - suite_results_.add(case_results_); + suiteResults_.add(caseResults_); } template void Reporter::onPass() { - ++case_results_.total; + ++caseResults_.total; } template void Reporter::onFail(std::string const& reason) { - ++case_results_.failed; - ++case_results_.total; - os_ << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ") << reason + ++caseResults_.failed; + ++caseResults_.total; + os_ << "#" << caseResults_.total << " failed" << (reason.empty() ? "" : ": ") << reason << std::endl; } diff --git a/include/xrpl/core/Coro.ipp b/include/xrpl/core/Coro.ipp index 90fa965038..133caf37a9 100644 --- a/include/xrpl/core/Coro.ipp +++ b/include/xrpl/core/Coro.ipp @@ -47,7 +47,7 @@ inline bool JobQueue::Coro::post() { { - std::scoped_lock const lk(mutex_run_); + std::scoped_lock const lk(mutexRun_); running_ = true; } @@ -58,7 +58,7 @@ JobQueue::Coro::post() } // The coroutine will not run. Clean up running_. - std::scoped_lock const lk(mutex_run_); + std::scoped_lock const lk(mutexRun_); running_ = false; cv_.notify_all(); return false; @@ -68,7 +68,7 @@ inline void JobQueue::Coro::resume() { { - std::scoped_lock const lk(mutex_run_); + std::scoped_lock const lk(mutexRun_); running_ = true; } { @@ -92,7 +92,7 @@ JobQueue::Coro::resume() } detail::getLocalValues().release(); detail::getLocalValues().reset(saved); - std::scoped_lock const lk(mutex_run_); + std::scoped_lock const lk(mutexRun_); running_ = false; cv_.notify_all(); } @@ -127,7 +127,7 @@ JobQueue::Coro::expectEarlyExit() inline void JobQueue::Coro::join() { - std::unique_lock lk(mutex_run_); + std::unique_lock lk(mutexRun_); cv_.wait(lk, [this]() { return !running_; }); } diff --git a/include/xrpl/core/Job.h b/include/xrpl/core/Job.h index b62488dec6..6af32eb2d8 100644 --- a/include/xrpl/core/Job.h +++ b/include/xrpl/core/Job.h @@ -127,7 +127,7 @@ private: std::function job_; std::shared_ptr loadEvent_; std::string name_; - clock_type::time_point queue_time_; + clock_type::time_point queueTime_; }; using JobCounter = ClosureCounter; diff --git a/include/xrpl/core/JobQueue.h b/include/xrpl/core/JobQueue.h index 2a96ea45fa..fc15e9a064 100644 --- a/include/xrpl/core/JobQueue.h +++ b/include/xrpl/core/JobQueue.h @@ -52,7 +52,7 @@ public: std::string name_; bool running_{false}; std::mutex mutex_; - std::mutex mutex_run_; + std::mutex mutexRun_; std::condition_variable cv_; boost::coroutines2::coroutine::push_type* yield_{}; boost::coroutines2::coroutine::pull_type coro_; @@ -246,7 +246,7 @@ private: // Statistics tracking perf::PerfLog& perfLog_; beast::insight::Collector::ptr collector_; - beast::insight::Gauge job_count_; + beast::insight::Gauge jobCount_; beast::insight::Hook hook_; std::condition_variable cv_; diff --git a/include/xrpl/ledger/BookDirs.h b/include/xrpl/ledger/BookDirs.h index afde6ee7b3..7eaede14ec 100644 --- a/include/xrpl/ledger/BookDirs.h +++ b/include/xrpl/ledger/BookDirs.h @@ -9,7 +9,7 @@ class BookDirs private: ReadView const* view_ = nullptr; uint256 const root_; - uint256 const next_quality_; + uint256 const nextQuality_; uint256 const key_; std::shared_ptr sle_ = nullptr; unsigned int entry_ = 0; @@ -67,15 +67,15 @@ private: friend class BookDirs; const_iterator(ReadView const& view, uint256 const& root, uint256 const& dirKey) - : view_(&view), root_(root), key_(dirKey), cur_key_(dirKey) + : view_(&view), root_(root), key_(dirKey), curKey_(dirKey) { } ReadView const* view_ = nullptr; uint256 root_; - uint256 next_quality_; + uint256 nextQuality_; uint256 key_; - uint256 cur_key_; + uint256 curKey_; std::shared_ptr sle_; unsigned int entry_ = 0; uint256 index_; diff --git a/include/xrpl/ledger/OpenView.h b/include/xrpl/ledger/OpenView.h index c7f80ee321..18d1a9399c 100644 --- a/include/xrpl/ledger/OpenView.h +++ b/include/xrpl/ledger/OpenView.h @@ -76,7 +76,7 @@ private: // monotonic_resource_ must outlive `items_`. Make a pointer so it may be // easily moved. - std::unique_ptr monotonic_resource_; + std::unique_ptr monotonicResource_; txs_map txs_; Rules rules_; LedgerHeader header_; diff --git a/include/xrpl/ledger/detail/RawStateTable.h b/include/xrpl/ledger/detail/RawStateTable.h index 53532ee5ec..e4329bf6fc 100644 --- a/include/xrpl/ledger/detail/RawStateTable.h +++ b/include/xrpl/ledger/detail/RawStateTable.h @@ -22,14 +22,14 @@ public: static constexpr size_t kInitialBufferSize = kilobytes(256); RawStateTable() - : monotonic_resource_{std::make_unique( + : monotonicResource_{std::make_unique( kInitialBufferSize)} - , items_{monotonic_resource_.get()} {}; + , items_{monotonicResource_.get()} {}; RawStateTable(RawStateTable const& rhs) - : monotonic_resource_{std::make_unique( + : monotonicResource_{std::make_unique( kInitialBufferSize)} - , items_{rhs.items_, monotonic_resource_.get()} + , items_{rhs.items_, monotonicResource_.get()} , dropsDestroyed_{rhs.dropsDestroyed_} {}; RawStateTable(RawStateTable&&) = default; @@ -101,7 +101,7 @@ private: boost::container::pmr::polymorphic_allocator>>; // monotonic_resource_ must outlive `items_`. Make a pointer so it may be // easily moved. - std::unique_ptr monotonic_resource_; + std::unique_ptr monotonicResource_; items_t items_; XRPAmount dropsDestroyed_{0}; diff --git a/include/xrpl/net/HTTPClientSSLContext.h b/include/xrpl/net/HTTPClientSSLContext.h index d1744bbce4..ca1983f141 100644 --- a/include/xrpl/net/HTTPClientSSLContext.h +++ b/include/xrpl/net/HTTPClientSSLContext.h @@ -21,13 +21,13 @@ public: bool sslVerify, beast::Journal j, boost::asio::ssl::context_base::method method = boost::asio::ssl::context::sslv23) - : ssl_context_{method}, j_(j), verify_{sslVerify} + : sslContext_{method}, j_(j), verify_{sslVerify} { boost::system::error_code ec; if (sslVerifyFile.empty()) { - registerSSLCerts(ssl_context_, ec, j_); + registerSSLCerts(sslContext_, ec, j_); if (ec && sslVerifyDir.empty()) { @@ -37,12 +37,12 @@ public: } else { - ssl_context_.load_verify_file(sslVerifyFile); + sslContext_.load_verify_file(sslVerifyFile); } if (!sslVerifyDir.empty()) { - ssl_context_.add_verify_path(sslVerifyDir, ec); + sslContext_.add_verify_path(sslVerifyDir, ec); if (ec) { @@ -55,7 +55,7 @@ public: boost::asio::ssl::context& context() { - return ssl_context_; + return sslContext_; } [[nodiscard]] bool @@ -153,7 +153,7 @@ public: } private: - boost::asio::ssl::context ssl_context_; + boost::asio::ssl::context sslContext_; beast::Journal const j_; bool const verify_; }; diff --git a/include/xrpl/protocol/Book.h b/include/xrpl/protocol/Book.h index fc36abddc4..01dc40075b 100644 --- a/include/xrpl/protocol/Book.h +++ b/include/xrpl/protocol/Book.h @@ -140,8 +140,8 @@ private: using issue_hasher = std::hash; using mptissue_hasher = std::hash; - issue_hasher m_issue_hasher_; - mptissue_hasher m_mptissue_hasher_; + issue_hasher mIssueHasher_; + mptissue_hasher mMptissueHasher_; public: explicit hash() = default; @@ -151,11 +151,11 @@ public: { return asset.visit( [&](xrpl::Issue const& issue) { - value_type const result(m_issue_hasher_(issue)); + value_type const result(mIssueHasher_(issue)); return result; }, [&](xrpl::MPTIssue const& issue) { - value_type const result(m_mptissue_hasher_(issue)); + value_type const result(mMptissueHasher_(issue)); return result; }); } @@ -170,8 +170,8 @@ private: using asset_hasher = std::hash; using uint256_hasher = xrpl::uint256::hasher; - asset_hasher issue_hasher_; - uint256_hasher uint256_hasher_; + asset_hasher issueHasher_; + uint256_hasher uint256Hasher_; public: hash() = default; @@ -182,11 +182,11 @@ public: value_type operator()(argument_type const& value) const { - value_type result(issue_hasher_(value.in)); - boost::hash_combine(result, issue_hasher_(value.out)); + value_type result(issueHasher_(value.in)); + boost::hash_combine(result, issueHasher_(value.out)); if (value.domain) - boost::hash_combine(result, uint256_hasher_(*value.domain)); + boost::hash_combine(result, uint256Hasher_(*value.domain)); return result; } diff --git a/include/xrpl/protocol/ErrorCodes.h b/include/xrpl/protocol/ErrorCodes.h index a91adfc55a..f5e67fd572 100644 --- a/include/xrpl/protocol/ErrorCodes.h +++ b/include/xrpl/protocol/ErrorCodes.h @@ -172,24 +172,24 @@ struct ErrorInfo { // Default ctor needed to produce an empty std::array during constexpr eval. constexpr ErrorInfo() - : code(RpcUnknown), token("unknown"), message("An unknown error code."), http_status(200) + : code(RpcUnknown), token("unknown"), message("An unknown error code."), httpStatus(200) { } constexpr ErrorInfo(ErrorCodeI code, char const* token, char const* message) - : code(code), token(token), message(message), http_status(200) + : code(code), token(token), message(message), httpStatus(200) { } constexpr ErrorInfo(ErrorCodeI code, char const* token, char const* message, int httpStatus) - : code(code), token(token), message(message), http_status(httpStatus) + : code(code), token(token), message(message), httpStatus(httpStatus) { } ErrorCodeI code; json::StaticString token; json::StaticString message; - int http_status; + int httpStatus; }; /** Returns an ErrorInfo that reflects the error code. */ diff --git a/include/xrpl/protocol/STPathSet.h b/include/xrpl/protocol/STPathSet.h index a1891164f6..1508dcb727 100644 --- a/include/xrpl/protocol/STPathSet.h +++ b/include/xrpl/protocol/STPathSet.h @@ -21,8 +21,8 @@ class STPathElement final : public CountedObject PathAsset assetID_; AccountID issuerID_; - bool is_offer_; - std::size_t hash_value_; + bool isOffer_; + std::size_t hashValue_; public: // Bitwise values (typeCurrency | typeMPT) @@ -235,9 +235,9 @@ private: // ------------ STPathElement ------------ -inline STPathElement::STPathElement() : type_(TypeNone), is_offer_(true) +inline STPathElement::STPathElement() : type_(TypeNone), isOffer_(true) { - hash_value_ = getHash(*this); + hashValue_ = getHash(*this); } inline STPathElement::STPathElement( @@ -248,11 +248,11 @@ inline STPathElement::STPathElement( { if (!account) { - is_offer_ = true; + isOffer_ = true; } else { - is_offer_ = false; + isOffer_ = false; accountID_ = *account; type_ |= TypeAccount; XRPL_ASSERT( @@ -272,7 +272,7 @@ inline STPathElement::STPathElement( XRPL_ASSERT(issuerID_ != noAccount(), "xrpl::STPathElement::STPathElement : issuer is set"); } - hash_value_ = getHash(*this); + hashValue_ = getHash(*this); } inline STPathElement::STPathElement( @@ -284,9 +284,9 @@ inline STPathElement::STPathElement( , accountID_(account) , assetID_(asset) , issuerID_(issuer) - , is_offer_(isXRP(accountID_)) + , isOffer_(isXRP(accountID_)) { - if (!is_offer_) + if (!isOffer_) type_ |= TypeAccount; if (forceAsset || !isXRP(assetID_)) @@ -295,7 +295,7 @@ inline STPathElement::STPathElement( if (!isXRP(issuer)) type_ |= TypeIssuer; - hash_value_ = getHash(*this); + hashValue_ = getHash(*this); } inline STPathElement::STPathElement( @@ -307,12 +307,12 @@ inline STPathElement::STPathElement( , accountID_(account) , assetID_(asset) , issuerID_(issuer) - , is_offer_(isXRP(accountID_)) + , isOffer_(isXRP(accountID_)) { assetID_.visit( [&](Currency const&) { type_ = type_ & (~Type::TypeMpt); }, [&](MPTID const&) { type_ = type_ & (~Type::TypeCurrency); }); - hash_value_ = getHash(*this); + hashValue_ = getHash(*this); } inline auto @@ -324,7 +324,7 @@ STPathElement::getNodeType() const inline bool STPathElement::isOffer() const { - return is_offer_; + return isOffer_; } inline bool @@ -404,7 +404,7 @@ STPathElement::getIssuerID() const inline bool STPathElement::operator==(STPathElement const& t) const { - return (type_ & TypeAccount) == (t.type_ & TypeAccount) && hash_value_ == t.hash_value_ && + return (type_ & TypeAccount) == (t.type_ & TypeAccount) && hashValue_ == t.hashValue_ && accountID_ == t.accountID_ && assetID_ == t.assetID_ && issuerID_ == t.issuerID_; } diff --git a/include/xrpl/protocol/STTx.h b/include/xrpl/protocol/STTx.h index 5aa70d1299..4deedfafb7 100644 --- a/include/xrpl/protocol/STTx.h +++ b/include/xrpl/protocol/STTx.h @@ -27,7 +27,7 @@ enum class TxnSql : char { class STTx final : public STObject, public CountedObject { uint256 tid_; - TxType tx_type_; + TxType txType_; public: static constexpr std::size_t kMinMultiSigners = 1; @@ -187,7 +187,7 @@ inline STTx::STTx(SerialIter&& sit) // NOLINT(cppcoreguidelines-rvalue-referenc inline TxType STTx::getTxnType() const { - return tx_type_; + return txType_; } inline Blob diff --git a/include/xrpl/resource/detail/Entry.h b/include/xrpl/resource/detail/Entry.h index b12bbf36ae..361c8409c8 100644 --- a/include/xrpl/resource/detail/Entry.h +++ b/include/xrpl/resource/detail/Entry.h @@ -21,7 +21,7 @@ struct Entry : public beast::List::Node @param now Construction time of Entry. */ explicit Entry(clock_type::time_point const now) - : refcount(0), local_balance(now), remote_balance(0) + : refcount(0), localBalance(now), remoteBalance(0) { } @@ -46,7 +46,7 @@ struct Entry : public beast::List::Node int balance(clock_type::time_point const now) { - return local_balance.value(now) + remote_balance; + return localBalance.value(now) + remoteBalance; } // Add a charge and return normalized balance @@ -54,7 +54,7 @@ struct Entry : public beast::List::Node int add(int charge, clock_type::time_point const now) { - return local_balance.add(charge, now) + remote_balance; + return localBalance.add(charge, now) + remoteBalance; } // The public key of the peer @@ -67,10 +67,10 @@ struct Entry : public beast::List::Node int refcount; // Exponentially decaying balance of resource consumption - DecayingSample local_balance; + DecayingSample localBalance; // Normalized balance contribution from imports - int remote_balance; + int remoteBalance; // Time of the last warning clock_type::time_point lastWarningTime; diff --git a/include/xrpl/resource/detail/Key.h b/include/xrpl/resource/detail/Key.h index 935d44425e..7d24b33955 100644 --- a/include/xrpl/resource/detail/Key.h +++ b/include/xrpl/resource/detail/Key.h @@ -25,11 +25,11 @@ struct Key std::size_t operator()(Key const& v) const { - return addr_hash_(v.address); + return addrHash_(v.address); } private: - beast::Uhash<> addr_hash_; + beast::Uhash<> addrHash_; }; struct KeyEqual diff --git a/include/xrpl/resource/detail/Logic.h b/include/xrpl/resource/detail/Logic.h index 7e63baca6c..b11ac100f5 100644 --- a/include/xrpl/resource/detail/Logic.h +++ b/include/xrpl/resource/detail/Logic.h @@ -194,34 +194,34 @@ public: for (auto& inboundEntry : inbound_) { - int const localBalance = inboundEntry.local_balance.value(now); - if ((localBalance + inboundEntry.remote_balance) >= threshold) + int const localBalance = inboundEntry.localBalance.value(now); + if ((localBalance + inboundEntry.remoteBalance) >= threshold) { json::Value& entry = (ret[inboundEntry.toString()] = json::ValueType::Object); entry[jss::local] = localBalance; - entry[jss::remote] = inboundEntry.remote_balance; + entry[jss::remote] = inboundEntry.remoteBalance; entry[jss::type] = "inbound"; } } for (auto& outboundEntry : outbound_) { - int const localBalance = outboundEntry.local_balance.value(now); - if ((localBalance + outboundEntry.remote_balance) >= threshold) + int const localBalance = outboundEntry.localBalance.value(now); + if ((localBalance + outboundEntry.remoteBalance) >= threshold) { json::Value& entry = (ret[outboundEntry.toString()] = json::ValueType::Object); entry[jss::local] = localBalance; - entry[jss::remote] = outboundEntry.remote_balance; + entry[jss::remote] = outboundEntry.remoteBalance; entry[jss::type] = "outbound"; } } for (auto& adminEntry : admin_) { - int const localBalance = adminEntry.local_balance.value(now); - if ((localBalance + adminEntry.remote_balance) >= threshold) + int const localBalance = adminEntry.localBalance.value(now); + if ((localBalance + adminEntry.remoteBalance) >= threshold) { json::Value& entry = (ret[adminEntry.toString()] = json::ValueType::Object); entry[jss::local] = localBalance; - entry[jss::remote] = adminEntry.remote_balance; + entry[jss::remote] = adminEntry.remoteBalance; entry[jss::type] = "admin"; } } @@ -242,7 +242,7 @@ public: for (auto& inboundEntry : inbound_) { Gossip::Item item; - item.balance = inboundEntry.local_balance.value(now); + item.balance = inboundEntry.localBalance.value(now); if (item.balance >= kMinimumGossipBalance) { item.address = inboundEntry.key->address; @@ -278,7 +278,7 @@ public: Import::Item item; item.balance = gossipItem.balance; item.consumer = newInboundEndpoint(gossipItem.address); - item.consumer.entry().remote_balance += item.balance; + item.consumer.entry().remoteBalance += item.balance; next.items.push_back(item); } } @@ -295,14 +295,14 @@ public: Import::Item item; item.balance = gossipItem.balance; item.consumer = newInboundEndpoint(gossipItem.address); - item.consumer.entry().remote_balance += item.balance; + item.consumer.entry().remoteBalance += item.balance; next.items.push_back(item); } Import& prev(resultIt->second); for (auto& item : prev.items) { - item.consumer.entry().remote_balance -= item.balance; + item.consumer.entry().remoteBalance -= item.balance; } std::swap(next, prev); @@ -345,7 +345,7 @@ public: for (auto itemIter(import.items.begin()); itemIter != import.items.end(); ++itemIter) { - itemIter->consumer.entry().remote_balance -= itemIter->balance; + itemIter->consumer.entry().remoteBalance -= itemIter->balance; } iter = importTable_.erase(iter); @@ -520,8 +520,8 @@ public: item["count"] = entry.refcount; item["name"] = entry.toString(); item["balance"] = entry.balance(now); - if (entry.remote_balance != 0) - item["remote_balance"] = entry.remote_balance; + if (entry.remoteBalance != 0) + item["remote_balance"] = entry.remoteBalance; } } diff --git a/include/xrpl/server/Handoff.h b/include/xrpl/server/Handoff.h index eba8c6c4de..b80a9c5745 100644 --- a/include/xrpl/server/Handoff.h +++ b/include/xrpl/server/Handoff.h @@ -21,7 +21,7 @@ struct Handoff bool moved = false; // If response is set, this determines the keep alive - bool keep_alive = false; + bool keepAlive = false; // When set, this will be sent back std::shared_ptr response; diff --git a/include/xrpl/server/Port.h b/include/xrpl/server/Port.h index 9207e0552b..ac9b855cf0 100644 --- a/include/xrpl/server/Port.h +++ b/include/xrpl/server/Port.h @@ -30,19 +30,19 @@ struct Port boost::asio::ip::address ip; std::uint16_t port = 0; std::set protocol; - std::vector admin_nets_v4; - std::vector admin_nets_v6; - std::vector secure_gateway_nets_v4; - std::vector secure_gateway_nets_v6; + std::vector adminNetsV4; + std::vector adminNetsV6; + std::vector secureGatewayNetsV4; + std::vector secureGatewayNetsV6; std::string user; std::string password; - std::string admin_user; - std::string admin_password; - std::string ssl_key; - std::string ssl_cert; - std::string ssl_chain; - std::string ssl_ciphers; - boost::beast::websocket::permessage_deflate pmd_options; + std::string adminUser; + std::string adminPassword; + std::string sslKey; + std::string sslCert; + std::string sslChain; + std::string sslCiphers; + boost::beast::websocket::permessage_deflate pmdOptions; std::shared_ptr context; // How many incoming connections are allowed on this @@ -50,7 +50,7 @@ struct Port int limit = 0; // Websocket disconnects if send queue exceeds this limit - std::uint16_t ws_queue_limit{}; + std::uint16_t wsQueueLimit{}; // Returns `true` if any websocket protocols are specified [[nodiscard]] bool @@ -78,22 +78,22 @@ struct ParsedPort std::set protocol; std::string user; std::string password; - std::string admin_user; - std::string admin_password; - std::string ssl_key; - std::string ssl_cert; - std::string ssl_chain; - std::string ssl_ciphers; - boost::beast::websocket::permessage_deflate pmd_options; + std::string adminUser; + std::string adminPassword; + std::string sslKey; + std::string sslCert; + std::string sslChain; + std::string sslCiphers; + boost::beast::websocket::permessage_deflate pmdOptions; int limit = 0; - std::uint16_t ws_queue_limit{}; + std::uint16_t wsQueueLimit{}; std::optional ip; std::optional port; - std::vector admin_nets_v4; - std::vector admin_nets_v6; - std::vector secure_gateway_nets_v4; - std::vector secure_gateway_nets_v6; + std::vector adminNetsV4; + std::vector adminNetsV6; + std::vector secureGatewayNetsV4; + std::vector secureGatewayNetsV6; }; void diff --git a/include/xrpl/server/detail/BaseHTTPPeer.h b/include/xrpl/server/detail/BaseHTTPPeer.h index 04ec4862c6..d26817bc45 100644 --- a/include/xrpl/server/detail/BaseHTTPPeer.h +++ b/include/xrpl/server/detail/BaseHTTPPeer.h @@ -58,13 +58,13 @@ protected: Handler& handler_; boost::asio::executor_work_guard work_; boost::asio::strand strand_; - endpoint_type remote_address_; + endpoint_type remoteAddress_; beast::Journal const journal_; std::string id_; std::size_t nid_; - boost::asio::streambuf read_buf_; + boost::asio::streambuf readBuf_; http_request_type message_; std::vector wq_; std::vector wq2_; @@ -73,9 +73,9 @@ protected: bool complete_ = false; boost::system::error_code ec_; - int request_count_ = 0; - std::size_t bytes_in_ = 0; - std::size_t bytes_out_ = 0; + int requestCount_ = 0; + std::size_t bytesIn_ = 0; + std::size_t bytesOut_ = 0; //-------------------------------------------------------------------------- @@ -151,7 +151,7 @@ protected: beast::IP::Endpoint remoteAddress() override { - return beast::IPAddressConversion::fromAsio(remote_address_); + return beast::IPAddressConversion::fromAsio(remoteAddress_); } http_request_type& @@ -191,23 +191,23 @@ BaseHTTPPeer::BaseHTTPPeer( , handler_(handler) , work_(boost::asio::make_work_guard(executor)) , strand_(boost::asio::make_strand(executor)) - , remote_address_(std::move(remoteAddress)) + , remoteAddress_(std::move(remoteAddress)) , journal_(journal) { - read_buf_.commit( - boost::asio::buffer_copy(read_buf_.prepare(boost::asio::buffer_size(buffers)), buffers)); + readBuf_.commit( + boost::asio::buffer_copy(readBuf_.prepare(boost::asio::buffer_size(buffers)), buffers)); static std::atomic kSid; nid_ = ++kSid; id_ = std::string("#") + std::to_string(nid_) + " "; - JLOG(journal_.trace()) << id_ << "accept: " << remote_address_.address(); + JLOG(journal_.trace()) << id_ << "accept: " << remoteAddress_.address(); } template BaseHTTPPeer::~BaseHTTPPeer() { handler_.onClose(session(), ec_); - JLOG(journal_.trace()) << id_ << "destroyed: " << request_count_ - << ((request_count_ == 1) ? " request" : " requests"); + JLOG(journal_.trace()) << id_ << "destroyed: " << requestCount_ + << ((requestCount_ == 1) ? " request" : " requests"); } template @@ -245,7 +245,7 @@ BaseHTTPPeer::startTimer() boost::beast::get_lowest_layer(impl().stream_) .expires_after( std::chrono::seconds( - remote_address_.address().is_loopback() ? kTimeoutSecondsLocal : kTimeoutSeconds)); + remoteAddress_.address().is_loopback() ? kTimeoutSecondsLocal : kTimeoutSeconds)); } // Convenience for discarding the error code @@ -274,7 +274,7 @@ BaseHTTPPeer::doRead(yield_context doYield) complete_ = false; error_code ec; startTimer(); - boost::beast::http::async_read(impl().stream_, read_buf_, message_, doYield[ec]); + boost::beast::http::async_read(impl().stream_, readBuf_, message_, doYield[ec]); cancelTimer(); if (ec == boost::beast::http::error::end_of_stream) return doClose(); @@ -296,7 +296,7 @@ BaseHTTPPeer::onWrite(error_code const& ec, std::size_t bytesTran return onTimer(); if (ec) return fail(ec, "write"); - bytes_out_ += bytesTransferred; + bytesOut_ += bytesTransferred; { std::scoped_lock const lock(mutex_); wq2_.clear(); diff --git a/include/xrpl/server/detail/BasePeer.h b/include/xrpl/server/detail/BasePeer.h index 3705ef448e..edde28981c 100644 --- a/include/xrpl/server/detail/BasePeer.h +++ b/include/xrpl/server/detail/BasePeer.h @@ -27,7 +27,7 @@ protected: Port const& port_; Handler& handler_; - endpoint_type remote_address_; + endpoint_type remoteAddress_; beast::WrappedSink sink_; beast::Journal const j_; @@ -65,7 +65,7 @@ BasePeer::BasePeer( beast::Journal journal) : port_(port) , handler_(handler) - , remote_address_(std::move(remoteAddress)) + , remoteAddress_(std::move(remoteAddress)) , sink_( journal.sink(), [] { diff --git a/include/xrpl/server/detail/BaseWSPeer.h b/include/xrpl/server/detail/BaseWSPeer.h index 181cf41f81..13225dcba1 100644 --- a/include/xrpl/server/detail/BaseWSPeer.h +++ b/include/xrpl/server/detail/BaseWSPeer.h @@ -42,15 +42,15 @@ private: /// The socket has been closed, or will close after the next write /// finishes. Do not do any more writes, and don't try to close /// again. - bool do_close_ = false; + bool doClose_ = false; boost::beast::websocket::close_reason cr_; waitable_timer timer_; - bool close_on_timer_ = false; - bool ping_active_ = false; + bool closeOnTimer_ = false; + bool pingActive_ = false; boost::beast::websocket::ping_data payload_; error_code ec_; std::function - control_callback_; + controlCallback_; public: template @@ -85,7 +85,7 @@ public: [[nodiscard]] boost::asio::ip::tcp::endpoint const& remoteEndpoint() const override { - return this->remote_address_; + return this->remoteAddress_; } void @@ -173,14 +173,14 @@ BaseWSPeer::run() { if (!strand_.running_in_this_thread()) return post(strand_, std::bind(&BaseWSPeer::run, impl().shared_from_this())); - impl().ws_.set_option(port().pmd_options); + impl().ws_.set_option(port().pmdOptions); // Must manage the control callback memory outside of the `control_callback` // function - control_callback_ = + controlCallback_ = std::bind(&BaseWSPeer::onPingPong, this, std::placeholders::_1, std::placeholders::_2); - impl().ws_.control_callback(control_callback_); + impl().ws_.control_callback(controlCallback_); startTimer(); - close_on_timer_ = true; + closeOnTimer_ = true; impl().ws_.set_option(boost::beast::websocket::stream_base::decorator([](auto& res) { res.set(boost::beast::http::field::server, BuildInfo::getFullVersionString()); })); @@ -198,9 +198,9 @@ BaseWSPeer::send(std::shared_ptr w) { if (!strand_.running_in_this_thread()) return post(strand_, std::bind(&BaseWSPeer::send, impl().shared_from_this(), std::move(w))); - if (do_close_) + if (doClose_) return; - if (wq_.size() > port().ws_queue_limit) + if (wq_.size() > port().wsQueueLimit) { cr_.code = safeCast(boost::beast::websocket::close_code::policy_error); cr_.reason = "Policy error: client is too slow."; @@ -227,9 +227,9 @@ BaseWSPeer::close(boost::beast::websocket::close_reason const& re { if (!strand_.running_in_this_thread()) return post(strand_, [self = impl().shared_from_this(), reason] { self->close(reason); }); - if (do_close_) + if (doClose_) return; - do_close_ = true; + doClose_ = true; if (wq_.empty()) { impl().ws_.async_close( @@ -260,7 +260,7 @@ BaseWSPeer::onWsHandshake(error_code const& ec) { if (ec) return fail(ec, "on_ws_handshake"); - close_on_timer_ = false; + closeOnTimer_ = false; doRead(); } @@ -313,7 +313,7 @@ BaseWSPeer::onWriteFin(error_code const& ec) if (ec) return fail(ec, "write_fin"); wq_.pop_front(); - if (do_close_) + if (doClose_) { impl().ws_.async_close( cr_, @@ -409,7 +409,7 @@ BaseWSPeer::onPing(error_code const& ec) { if (ec == boost::asio::error::operation_aborted) return; - ping_active_ = false; + pingActive_ = false; if (!ec) return; fail(ec, "on_ping"); @@ -426,7 +426,7 @@ BaseWSPeer::onPingPong( boost::beast::string_view const p(payload_.begin()); if (payload == p) { - close_on_timer_ = false; + closeOnTimer_ = false; JLOG(this->j_.trace()) << "got matching pong"; } else @@ -444,11 +444,11 @@ BaseWSPeer::onTimer(error_code ec) return; if (!ec) { - if (!close_on_timer_ || !ping_active_) + if (!closeOnTimer_ || !pingActive_) { startTimer(); - close_on_timer_ = true; - ping_active_ = true; + closeOnTimer_ = true; + pingActive_ = true; // cryptographic is probably overkill.. beast::rngfill(payload_.begin(), payload_.size(), cryptoPrng()); impl().ws_.async_ping( diff --git a/include/xrpl/server/detail/Door.h b/include/xrpl/server/detail/Door.h index d3fff8b476..e273ca791f 100644 --- a/include/xrpl/server/detail/Door.h +++ b/include/xrpl/server/detail/Door.h @@ -61,7 +61,7 @@ private: boost::asio::io_context& ioc_; stream_type stream_; socket_type& socket_; - endpoint_type remote_address_; + endpoint_type remoteAddress_; boost::asio::strand strand_; beast::Journal const j_; @@ -97,8 +97,8 @@ private: (port_.protocol.count("ws2") != 0u)}; static constexpr std::chrono::milliseconds kInitialAcceptDelay{50}; static constexpr std::chrono::milliseconds kMaxAcceptDelay{2000}; - std::chrono::milliseconds accept_delay_{kInitialAcceptDelay}; - boost::asio::steady_timer backoff_timer_; + std::chrono::milliseconds acceptDelay_{kInitialAcceptDelay}; + boost::asio::steady_timer backoffTimer_; static constexpr double kFreeFdThreshold = 0.70; struct FDStats @@ -164,7 +164,7 @@ Door::Detector::Detector( , ioc_(ioc) , stream_(std::move(stream)) , socket_(stream_.socket()) - , remote_address_(std::move(remoteAddress)) + , remoteAddress_(std::move(remoteAddress)) , strand_(boost::asio::make_strand(ioc_)) , j_(j) { @@ -199,18 +199,18 @@ Door::Detector::doDetect(boost::asio::yield_context doYield) if (ssl) { if (auto sp = ios().template emplace>( - port_, handler_, ioc_, j_, remote_address_, buf.data(), std::move(stream_))) + port_, handler_, ioc_, j_, remoteAddress_, buf.data(), std::move(stream_))) sp->run(); return; } if (auto sp = ios().template emplace>( - port_, handler_, ioc_, j_, remote_address_, buf.data(), std::move(stream_))) + port_, handler_, ioc_, j_, remoteAddress_, buf.data(), std::move(stream_))) sp->run(); return; } if (ec != boost::asio::error::operation_aborted) { - JLOG(j_.trace()) << "Error detecting ssl: " << ec.message() << " from " << remote_address_; + JLOG(j_.trace()) << "Error detecting ssl: " << ec.message() << " from " << remoteAddress_; } } @@ -279,7 +279,7 @@ Door::Door( , ioc_(ioContext) , acceptor_(ioContext) , strand_(boost::asio::make_strand(ioContext)) - , backoff_timer_(ioContext) + , backoffTimer_(ioContext) { reOpen(); } @@ -302,7 +302,7 @@ Door::close() return boost::asio::post( strand_, std::bind(&Door::close, this->shared_from_this())); } - backoff_timer_.cancel(); + backoffTimer_.cancel(); error_code ec; acceptor_.close(ec); } @@ -338,11 +338,11 @@ Door::doAccept(boost::asio::yield_context doYield) { if (shouldThrottleForFds()) { - backoff_timer_.expires_after(accept_delay_); + backoffTimer_.expires_after(acceptDelay_); boost::system::error_code tec; - backoff_timer_.async_wait(doYield[tec]); - accept_delay_ = std::min(accept_delay_ * 2, kMaxAcceptDelay); - JLOG(j_.warn()) << "Throttling do_accept for " << accept_delay_.count() << "ms."; + backoffTimer_.async_wait(doYield[tec]); + acceptDelay_ = std::min(acceptDelay_ * 2, kMaxAcceptDelay); + JLOG(j_.warn()) << "Throttling do_accept for " << acceptDelay_.count() << "ms."; continue; } @@ -360,13 +360,13 @@ Door::doAccept(boost::asio::yield_context doYield) ec == boost::asio::error::no_buffer_space) { JLOG(j_.warn()) << "accept: Too many open files. Pausing for " - << accept_delay_.count() << "ms."; + << acceptDelay_.count() << "ms."; - backoff_timer_.expires_after(accept_delay_); + backoffTimer_.expires_after(acceptDelay_); boost::system::error_code tec; - backoff_timer_.async_wait(doYield[tec]); + backoffTimer_.async_wait(doYield[tec]); - accept_delay_ = std::min(accept_delay_ * 2, kMaxAcceptDelay); + acceptDelay_ = std::min(acceptDelay_ * 2, kMaxAcceptDelay); } else { @@ -375,7 +375,7 @@ Door::doAccept(boost::asio::yield_context doYield) continue; } - accept_delay_ = kInitialAcceptDelay; + acceptDelay_ = kInitialAcceptDelay; if (ssl_ && plain_) { diff --git a/include/xrpl/server/detail/PlainHTTPPeer.h b/include/xrpl/server/detail/PlainHTTPPeer.h index 40f7fe2571..791ab91acf 100644 --- a/include/xrpl/server/detail/PlainHTTPPeer.h +++ b/include/xrpl/server/detail/PlainHTTPPeer.h @@ -82,7 +82,7 @@ template void PlainHTTPPeer::run() { - if (!this->handler_.onAccept(this->session(), this->remote_address_)) + if (!this->handler_.onAccept(this->session(), this->remoteAddress_)) { util::spawn(this->strand_, std::bind(&PlainHTTPPeer::doClose, this->shared_from_this())); return; @@ -103,7 +103,7 @@ PlainHTTPPeer::websocketUpgrade() auto ws = this->ios().template emplace>( this->port_, this->handler_, - this->remote_address_, + this->remoteAddress_, std::move(this->message_), std::move(stream_), this->journal_); @@ -114,20 +114,20 @@ template void PlainHTTPPeer::doRequest() { - ++this->request_count_; + ++this->requestCount_; auto const what = - this->handler_.onHandoff(this->session(), std::move(this->message_), this->remote_address_); + this->handler_.onHandoff(this->session(), std::move(this->message_), this->remoteAddress_); if (what.moved) return; boost::system::error_code ec; if (what.response) { // half-close on Connection: close - if (!what.keep_alive) + if (!what.keepAlive) socket_.shutdown(socket_type::shutdown_receive, ec); if (ec) return this->fail(ec, "request"); - return this->write(what.response, what.keep_alive); + return this->write(what.response, what.keepAlive); } // Perform half-close when Connection: close and not SSL diff --git a/include/xrpl/server/detail/SSLHTTPPeer.h b/include/xrpl/server/detail/SSLHTTPPeer.h index 2eec8ed60f..f3b047150f 100644 --- a/include/xrpl/server/detail/SSLHTTPPeer.h +++ b/include/xrpl/server/detail/SSLHTTPPeer.h @@ -26,7 +26,7 @@ private: using yield_context = boost::asio::yield_context; using error_code = boost::system::error_code; - std::unique_ptr stream_ptr_; + std::unique_ptr streamPtr_; stream_type& stream_; socket_type& socket_; @@ -80,8 +80,8 @@ SSLHTTPPeer::SSLHTTPPeer( journal, remoteAddress, buffers) - , stream_ptr_(std::make_unique(middle_type(std::move(stream)), *port.context)) - , stream_(*stream_ptr_) + , streamPtr_(std::make_unique(middle_type(std::move(stream)), *port.context)) + , stream_(*streamPtr_) , socket_(stream_.next_layer().socket()) { } @@ -91,7 +91,7 @@ template void SSLHTTPPeer::run() { - if (!this->handler_.onAccept(this->session(), this->remote_address_)) + if (!this->handler_.onAccept(this->session(), this->remoteAddress_)) { util::spawn(this->strand_, std::bind(&SSLHTTPPeer::doClose, this->shared_from_this())); return; @@ -110,9 +110,9 @@ SSLHTTPPeer::websocketUpgrade() auto ws = this->ios().template emplace>( this->port_, this->handler_, - this->remote_address_, + this->remoteAddress_, std::move(this->message_), - std::move(this->stream_ptr_), + std::move(this->streamPtr_), this->journal_); return ws; } @@ -124,8 +124,8 @@ SSLHTTPPeer::doHandshake(yield_context doYield) boost::system::error_code ec; stream_.set_verify_mode(boost::asio::ssl::verify_none); this->startTimer(); - this->read_buf_.consume( - stream_.async_handshake(stream_type::server, this->read_buf_.data(), doYield[ec])); + this->readBuf_.consume( + stream_.async_handshake(stream_type::server, this->readBuf_.data(), doYield[ec])); this->cancelTimer(); if (ec == boost::beast::error::timeout) return this->onTimer(); @@ -148,13 +148,13 @@ template void SSLHTTPPeer::doRequest() { - ++this->request_count_; + ++this->requestCount_; auto const what = this->handler_.onHandoff( - this->session(), std::move(stream_ptr_), std::move(this->message_), this->remote_address_); + this->session(), std::move(streamPtr_), std::move(this->message_), this->remoteAddress_); if (what.moved) return; if (what.response) - return this->write(what.response, what.keep_alive); + return this->write(what.response, what.keepAlive); // legacy this->handler_.onRequest(this->session()); } diff --git a/include/xrpl/server/detail/SSLWSPeer.h b/include/xrpl/server/detail/SSLWSPeer.h index 19b77ea932..cb03d5a796 100644 --- a/include/xrpl/server/detail/SSLWSPeer.h +++ b/include/xrpl/server/detail/SSLWSPeer.h @@ -28,7 +28,7 @@ class SSLWSPeer : public BaseWSPeer>, using stream_type = boost::beast::ssl_stream; using waitable_timer = boost::asio::basic_waitable_timer; - std::unique_ptr stream_ptr_; + std::unique_ptr streamPtr_; boost::beast::websocket::stream ws_; public: @@ -61,8 +61,8 @@ SSLWSPeer::SSLWSPeer( remoteEndpoint, std::move(request), journal) - , stream_ptr_(std::move(streamPtr)) - , ws_(*stream_ptr_) + , streamPtr_(std::move(streamPtr)) + , ws_(*streamPtr_) { } diff --git a/include/xrpl/server/detail/ServerImpl.h b/include/xrpl/server/detail/ServerImpl.h index df9a2dd3e8..48ffa26cfe 100644 --- a/include/xrpl/server/detail/ServerImpl.h +++ b/include/xrpl/server/detail/ServerImpl.h @@ -66,7 +66,7 @@ private: Handler& handler_; beast::Journal const j_; - boost::asio::io_context& io_context_; + boost::asio::io_context& ioContext_; boost::asio::strand strand_; std::optional> work_; @@ -104,7 +104,7 @@ public: boost::asio::io_context& getIoContext() { - return io_context_; + return ioContext_; } bool @@ -122,9 +122,9 @@ ServerImpl::ServerImpl( beast::Journal journal) : handler_(handler) , j_(journal) - , io_context_(ioContext) - , strand_(boost::asio::make_strand(io_context_)) - , work_(std::in_place, boost::asio::make_work_guard(io_context_)) + , ioContext_(ioContext) + , strand_(boost::asio::make_strand(ioContext_)) + , work_(std::in_place, boost::asio::make_work_guard(ioContext_)) { } @@ -150,7 +150,7 @@ ServerImpl::ports(std::vector const& ports) { ports_.push_back(port); auto& internalPort = ports_.back(); - if (auto sp = ios_.emplace>(handler_, io_context_, internalPort, j_)) + if (auto sp = ios_.emplace>(handler_, ioContext_, internalPort, j_)) { list_.push_back(sp); diff --git a/src/libxrpl/basics/ResolverAsio.cpp b/src/libxrpl/basics/ResolverAsio.cpp index 4a5ceb3d8d..ddf006b681 100644 --- a/src/libxrpl/basics/ResolverAsio.cpp +++ b/src/libxrpl/basics/ResolverAsio.cpp @@ -108,7 +108,7 @@ public: beast::Journal journal; - boost::asio::io_context& io_context; + boost::asio::io_context& ioContext; boost::asio::strand strand; boost::asio::ip::tcp::resolver resolver; @@ -116,7 +116,7 @@ public: std::mutex mut; bool asyncHandlersCompleted{true}; - std::atomic stop_called; + std::atomic stopCalled; std::atomic stopped; // Represents a unit of work for the resolver to do @@ -138,10 +138,10 @@ public: ResolverAsioImpl(boost::asio::io_context& ioContext, beast::Journal journal) : journal(journal) - , io_context(ioContext) + , ioContext(ioContext) , strand(boost::asio::make_strand(ioContext)) , resolver(ioContext) - , stop_called(false) + , stopCalled(false) , stopped(true) { } @@ -172,7 +172,7 @@ public: start() override { XRPL_ASSERT(stopped == true, "xrpl::ResolverAsioImpl::start : stopped"); - XRPL_ASSERT(stop_called == false, "xrpl::ResolverAsioImpl::start : not stopping"); + XRPL_ASSERT(stopCalled == false, "xrpl::ResolverAsioImpl::start : not stopping"); if (stopped.exchange(false)) { @@ -187,10 +187,10 @@ public: void stopAsync() override { - if (!stop_called.exchange(true)) + if (!stopCalled.exchange(true)) { boost::asio::dispatch( - io_context, + ioContext, boost::asio::bind_executor( strand, std::bind(&ResolverAsioImpl::doStop, this, CompletionCounter(this)))); @@ -213,13 +213,13 @@ public: void resolve(std::vector const& names, HandlerType const& handler) override { - XRPL_ASSERT(stop_called == false, "xrpl::ResolverAsioImpl::resolve : not stopping"); + XRPL_ASSERT(stopCalled == false, "xrpl::ResolverAsioImpl::resolve : not stopping"); XRPL_ASSERT(!names.empty(), "xrpl::ResolverAsioImpl::resolve : names non-empty"); // TODO NIKB use rvalue references to construct and move // reducing cost. boost::asio::dispatch( - io_context, + ioContext, boost::asio::bind_executor( strand, std::bind( @@ -231,7 +231,7 @@ public: void doStop(CompletionCounter) { - XRPL_ASSERT(stop_called == true, "xrpl::ResolverAsioImpl::doStop : stopping"); + XRPL_ASSERT(stopCalled == true, "xrpl::ResolverAsioImpl::doStop : stopping"); if (!stopped.exchange(true)) { @@ -270,7 +270,7 @@ public: handler(name, addresses); boost::asio::post( - io_context, + ioContext, boost::asio::bind_executor( strand, std::bind(&ResolverAsioImpl::doWork, this, CompletionCounter(this)))); } @@ -324,7 +324,7 @@ public: void doWork(CompletionCounter) { - if (stop_called) + if (stopCalled) return; // We don't have any work to do at this time @@ -346,7 +346,7 @@ public: JLOG(journal.error()) << "Unable to parse '" << name << "'"; boost::asio::post( - io_context, + ioContext, boost::asio::bind_executor( strand, std::bind(&ResolverAsioImpl::doWork, this, CompletionCounter(this)))); @@ -371,7 +371,7 @@ public: { XRPL_ASSERT(!names.empty(), "xrpl::ResolverAsioImpl::doResolve : names non-empty"); - if (!stop_called) + if (!stopCalled) { work.emplace_back(names, handler); @@ -381,7 +381,7 @@ public: if (!work.empty()) { boost::asio::post( - io_context, + ioContext, boost::asio::bind_executor( strand, std::bind(&ResolverAsioImpl::doWork, this, CompletionCounter(this)))); diff --git a/src/libxrpl/beast/insight/StatsDCollector.cpp b/src/libxrpl/beast/insight/StatsDCollector.cpp index 7d460d54ef..88f52c26de 100644 --- a/src/libxrpl/beast/insight/StatsDCollector.cpp +++ b/src/libxrpl/beast/insight/StatsDCollector.cpp @@ -164,7 +164,7 @@ public: private: std::shared_ptr impl_; std::string name_; - GaugeImpl::value_type last_value_{0}; + GaugeImpl::value_type lastValue_{0}; GaugeImpl::value_type value_{0}; bool dirty_{false}; }; @@ -209,7 +209,7 @@ private: Journal journal_; IP::Endpoint address_; std::string prefix_; - boost::asio::io_context io_context_; + boost::asio::io_context ioContext_; std::optional> work_; boost::asio::strand strand_; boost::asio::basic_waitable_timer timer_; @@ -232,10 +232,10 @@ public: : journal_(journal) , address_(std::move(address)) , prefix_(std::move(prefix)) - , work_(boost::asio::make_work_guard(io_context_)) - , strand_(boost::asio::make_strand(io_context_)) - , timer_(io_context_) - , socket_(io_context_) + , work_(boost::asio::make_work_guard(ioContext_)) + , strand_(boost::asio::make_strand(ioContext_)) + , timer_(ioContext_) + , socket_(ioContext_) , thread_(&StatsDCollectorImp::run, this) { } @@ -306,7 +306,7 @@ public: boost::asio::io_context& getIoContext() { - return io_context_; + return ioContext_; } std::string const& @@ -325,7 +325,7 @@ public: postBuffer(std::string&& buffer) { boost::asio::dispatch( - io_context_, + ioContext_, boost::asio::bind_executor( strand_, std::bind(&StatsDCollectorImp::doPostBuffer, this, std::move(buffer)))); } @@ -465,14 +465,14 @@ public: setTimer(); - io_context_.run(); + ioContext_.run(); // NOLINTNEXTLINE(bugprone-unused-return-value) socket_.shutdown(boost::asio::ip::udp::socket::shutdown_send, ec); socket_.close(); - io_context_.poll(); + ioContext_.poll(); } }; @@ -628,9 +628,9 @@ StatsDGaugeImpl::doSet(GaugeImpl::value_type value) { value_ = value; - if (value_ != last_value_) + if (value_ != lastValue_) { - last_value_ = value_; + lastValue_ = value_; dirty_ = true; } } diff --git a/src/libxrpl/core/detail/Job.cpp b/src/libxrpl/core/detail/Job.cpp index a1b88864d7..89fe42db05 100644 --- a/src/libxrpl/core/detail/Job.cpp +++ b/src/libxrpl/core/detail/Job.cpp @@ -25,7 +25,7 @@ Job::Job( std::uint64_t index, LoadMonitor& lm, std::function const& job) - : type_(type), jobIndex_(index), job_(job), name_(name), queue_time_(clock_type::now()) + : type_(type), jobIndex_(index), job_(job), name_(name), queueTime_(clock_type::now()) { loadEvent_ = std::make_shared(std::ref(lm), name, false); } @@ -39,7 +39,7 @@ Job::getType() const Job::clock_type::time_point const& Job::queueTime() const { - return queue_time_; + return queueTime_; } void diff --git a/src/libxrpl/core/detail/JobQueue.cpp b/src/libxrpl/core/detail/JobQueue.cpp index 50f95dcdbf..5c07bf68cc 100644 --- a/src/libxrpl/core/detail/JobQueue.cpp +++ b/src/libxrpl/core/detail/JobQueue.cpp @@ -36,7 +36,7 @@ JobQueue::JobQueue( JLOG(journal_.info()) << "Using " << threadCount << " threads"; hook_ = collector_->makeHook(std::bind(&JobQueue::collect, this)); - job_count_ = collector_->makeGauge("job_count"); + jobCount_ = collector_->makeGauge("job_count"); { std::scoped_lock const lock(mutex_); @@ -66,7 +66,7 @@ void JobQueue::collect() { std::scoped_lock const lock(mutex_); - job_count_ = jobSet_.size(); + jobCount_ = jobSet_.size(); } bool diff --git a/src/libxrpl/ledger/BookDirs.cpp b/src/libxrpl/ledger/BookDirs.cpp index f3e41e4f58..099fec99e1 100644 --- a/src/libxrpl/ledger/BookDirs.cpp +++ b/src/libxrpl/ledger/BookDirs.cpp @@ -14,8 +14,8 @@ namespace xrpl { BookDirs::BookDirs(ReadView const& view, Book const& book) : view_(&view) , root_(keylet::page(getBookBase(book)).key) - , next_quality_(getQualityNext(root_)) - , key_(view_->succ(root_, next_quality_).value_or(beast::kZero)) + , nextQuality_(getQualityNext(root_)) + , key_(view_->succ(root_, nextQuality_).value_or(beast::kZero)) { XRPL_ASSERT(root_ != beast::kZero, "xrpl::BookDirs::BookDirs : nonzero root"); if (key_ != beast::kZero) @@ -35,7 +35,7 @@ BookDirs::begin() const -> BookDirs::const_iterator auto it = BookDirs::const_iterator(*view_, root_, key_); if (key_ != beast::kZero) { - it.next_quality_ = next_quality_; + it.nextQuality_ = nextQuality_; it.sle_ = sle_; it.entry_ = entry_; it.index_ = index_; @@ -59,7 +59,7 @@ BookDirs::const_iterator::operator==(BookDirs::const_iterator const& other) cons view_ == other.view_ && root_ == other.root_, "xrpl::BookDirs::const_iterator::operator== : views and roots are " "matching"); - return entry_ == other.entry_ && cur_key_ == other.cur_key_ && index_ == other.index_; + return entry_ == other.entry_ && curKey_ == other.curKey_ && index_ == other.index_; } BookDirs::const_iterator::reference @@ -78,18 +78,18 @@ BookDirs::const_iterator::operator++() using beast::kZero; XRPL_ASSERT(index_ != kZero, "xrpl::BookDirs::const_iterator::operator++ : nonzero index"); - if (!cdirNext(*view_, cur_key_, sle_, entry_, index_)) + if (!cdirNext(*view_, curKey_, sle_, entry_, index_)) { if (index_ == 0) - cur_key_ = view_->succ(++cur_key_, next_quality_).value_or(kZero); + curKey_ = view_->succ(++curKey_, nextQuality_).value_or(kZero); - if (index_ != 0 || cur_key_ == kZero) + if (index_ != 0 || curKey_ == kZero) { - cur_key_ = key_; + curKey_ = key_; entry_ = 0; index_ = kZero; } - else if (!cdirFirst(*view_, cur_key_, sle_, entry_, index_)) + else if (!cdirFirst(*view_, curKey_, sle_, entry_, index_)) { // LCOV_EXCL_START UNREACHABLE("xrpl::BookDirs::const_iterator::operator++ : directory is empty"); diff --git a/src/libxrpl/ledger/OpenView.cpp b/src/libxrpl/ledger/OpenView.cpp index c5bda2d0c8..40b411fcc0 100644 --- a/src/libxrpl/ledger/OpenView.cpp +++ b/src/libxrpl/ledger/OpenView.cpp @@ -78,9 +78,9 @@ public: OpenView::OpenView(OpenView const& rhs) : ReadView(rhs) , TxsRawView(rhs) - , monotonic_resource_{std::make_unique( + , monotonicResource_{std::make_unique( kInitialBufferSize)} - , txs_{rhs.txs_, monotonic_resource_.get()} + , txs_{rhs.txs_, monotonicResource_.get()} , rules_{rhs.rules_} , header_{rhs.header_} , base_{rhs.base_} @@ -89,9 +89,9 @@ OpenView::OpenView(OpenView const& rhs) , open_{rhs.open_} {}; OpenView::OpenView(OpenLedgerT, ReadView const* base, Rules rules, std::shared_ptr hold) - : monotonic_resource_{ + : monotonicResource_{ std::make_unique(kInitialBufferSize)} - , txs_{monotonic_resource_.get()} + , txs_{monotonicResource_.get()} , rules_(std::move(rules)) , header_(base->header()) , base_(base) @@ -105,9 +105,9 @@ OpenView::OpenView(OpenLedgerT, ReadView const* base, Rules rules, std::shared_p } OpenView::OpenView(ReadView const* base, std::shared_ptr hold) - : monotonic_resource_{ + : monotonicResource_{ std::make_unique(kInitialBufferSize)} - , txs_{monotonic_resource_.get()} + , txs_{monotonicResource_.get()} , rules_(base->rules()) , header_(base->header()) , base_(base) diff --git a/src/libxrpl/protocol/ErrorCodes.cpp b/src/libxrpl/protocol/ErrorCodes.cpp index 25c5fbe033..87761ce13e 100644 --- a/src/libxrpl/protocol/ErrorCodes.cpp +++ b/src/libxrpl/protocol/ErrorCodes.cpp @@ -212,7 +212,7 @@ containsError(json::Value const& json) int errorCodeHttpStatus(ErrorCodeI code) { - return getErrorInfo(code).http_status; + return getErrorInfo(code).httpStatus; } } // namespace RPC diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 8636c99284..2777981fd7 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -69,8 +69,8 @@ getTxFormat(TxType type) STTx::STTx(STObject&& object) : STObject(std::move(object)) { - tx_type_ = safeCast(getFieldU16(sfTransactionType)); - applyTemplate(getTxFormat(tx_type_)->getSOTemplate()); // may throw + txType_ = safeCast(getFieldU16(sfTransactionType)); + applyTemplate(getTxFormat(txType_)->getSOTemplate()); // may throw tid_ = getHash(HashPrefix::TransactionId); } @@ -84,9 +84,9 @@ STTx::STTx(SerialIter& sit) : STObject(sfTransaction) if (set(sit)) Throw("Transaction contains an object terminator"); - tx_type_ = safeCast(getFieldU16(sfTransactionType)); + txType_ = safeCast(getFieldU16(sfTransactionType)); - applyTemplate(getTxFormat(tx_type_)->getSOTemplate()); // May throw + applyTemplate(getTxFormat(txType_)->getSOTemplate()); // May throw tid_ = getHash(HashPrefix::TransactionId); } @@ -99,9 +99,9 @@ STTx::STTx(TxType type, std::function assembler) : STObject(sfT assembler(*this); - tx_type_ = safeCast(getFieldU16(sfTransactionType)); + txType_ = safeCast(getFieldU16(sfTransactionType)); - if (tx_type_ != type) + if (txType_ != type) logicError("Transaction type was mutated during assembly"); tid_ = getHash(HashPrefix::TransactionId); @@ -380,7 +380,7 @@ STTx::getMetaSQL( static boost::format const kBfTrans("('%s', '%s', '%s', '%d', '%d', '%c', %s, %s)"); std::string rTxn = sqlBlobLiteral(rawTxn.peekData()); - auto format = TxFormats::getInstance().findByType(tx_type_); + auto format = TxFormats::getInstance().findByType(txType_); XRPL_ASSERT(format, "xrpl::STTx::getMetaSQL : non-null type format"); return str( diff --git a/src/libxrpl/server/Port.cpp b/src/libxrpl/server/Port.cpp index 9a6b6dce35..00c10b2b55 100644 --- a/src/libxrpl/server/Port.cpp +++ b/src/libxrpl/server/Port.cpp @@ -44,30 +44,30 @@ operator<<(std::ostream& os, Port const& p) { os << "'" << p.name << "' (ip=" << p.ip << ":" << p.port << ", "; - if (!p.admin_nets_v4.empty() || !p.admin_nets_v6.empty()) + if (!p.adminNetsV4.empty() || !p.adminNetsV6.empty()) { os << "admin nets:"; - for (auto const& net : p.admin_nets_v4) + for (auto const& net : p.adminNetsV4) { os << net.to_string(); os << ", "; } - for (auto const& net : p.admin_nets_v6) + for (auto const& net : p.adminNetsV6) { os << net.to_string(); os << ", "; } } - if (!p.secure_gateway_nets_v4.empty() || !p.secure_gateway_nets_v6.empty()) + if (!p.secureGatewayNetsV4.empty() || !p.secureGatewayNetsV6.empty()) { os << "secure_gateway nets:"; - for (auto const& net : p.secure_gateway_nets_v4) + for (auto const& net : p.secureGatewayNetsV4) { os << net.to_string(); os << ", "; } - for (auto const& net : p.secure_gateway_nets_v6) + for (auto const& net : p.secureGatewayNetsV6) { os << net.to_string(); os << ", "; @@ -265,10 +265,10 @@ parsePort(ParsedPort& port, Section const& section, std::ostream& log) { try { - port.ws_queue_limit = beast::lexicalCastThrow(*optResult); + port.wsQueueLimit = beast::lexicalCastThrow(*optResult); // Queue must be greater than 0 - if (port.ws_queue_limit == 0) + if (port.wsQueueLimit == 0) Throw(); } catch (std::exception const&) @@ -281,32 +281,31 @@ parsePort(ParsedPort& port, Section const& section, std::ostream& log) else { // Default Websocket send queue size limit - port.ws_queue_limit = 100; + port.wsQueueLimit = 100; } } - populate(section, "admin", log, port.admin_nets_v4, port.admin_nets_v6); - populate( - section, "secure_gateway", log, port.secure_gateway_nets_v4, port.secure_gateway_nets_v6); + populate(section, "admin", log, port.adminNetsV4, port.adminNetsV6); + populate(section, "secure_gateway", log, port.secureGatewayNetsV4, port.secureGatewayNetsV6); set(port.user, "user", section); set(port.password, "password", section); - set(port.admin_user, "admin_user", section); - set(port.admin_password, "admin_password", section); - set(port.ssl_key, "ssl_key", section); - set(port.ssl_cert, "ssl_cert", section); - set(port.ssl_chain, "ssl_chain", section); - set(port.ssl_ciphers, "ssl_ciphers", section); + set(port.adminUser, "admin_user", section); + set(port.adminPassword, "admin_password", section); + set(port.sslKey, "ssl_key", section); + set(port.sslCert, "ssl_cert", section); + set(port.sslChain, "ssl_chain", section); + set(port.sslCiphers, "ssl_ciphers", section); - port.pmd_options.server_enable = section.valueOr("permessage_deflate", true); - port.pmd_options.client_max_window_bits = section.valueOr("client_max_window_bits", 15); - port.pmd_options.server_max_window_bits = section.valueOr("server_max_window_bits", 15); - port.pmd_options.client_no_context_takeover = + port.pmdOptions.server_enable = section.valueOr("permessage_deflate", true); + port.pmdOptions.client_max_window_bits = section.valueOr("client_max_window_bits", 15); + port.pmdOptions.server_max_window_bits = section.valueOr("server_max_window_bits", 15); + port.pmdOptions.client_no_context_takeover = section.valueOr("client_no_context_takeover", false); - port.pmd_options.server_no_context_takeover = + port.pmdOptions.server_no_context_takeover = section.valueOr("server_no_context_takeover", false); - port.pmd_options.compLevel = section.valueOr("compress_level", 8); - port.pmd_options.memLevel = section.valueOr("memory_level", 4); + port.pmdOptions.compLevel = section.valueOr("compress_level", 8); + port.pmdOptions.memLevel = section.valueOr("memory_level", 4); } } // namespace xrpl diff --git a/src/test/app/AMMMPT_test.cpp b/src/test/app/AMMMPT_test.cpp index eba388e5fd..cfb8edc60b 100644 --- a/src/test/app/AMMMPT_test.cpp +++ b/src/test/app/AMMMPT_test.cpp @@ -6299,9 +6299,9 @@ private: struct MPTList { - MPTTester const USD; - MPTTester const ETH; - MPTTester const CAN; + MPTTester const usd; + MPTTester const eth; + MPTTester const can; }; auto prep = [&](Env& env, uint16_t gwTransferFee, uint16_t gw1TransferFee) -> MPTList { @@ -6330,9 +6330,9 @@ private: env.close(); return MPTList{ - .USD = std::move(usd), - .ETH = std::move(eth), - .CAN = std::move(can), + .usd = std::move(usd), + .eth = std::move(eth), + .can = std::move(can), }; }; @@ -6357,9 +6357,9 @@ private: { Env env(*this, features); auto mpts = prep(env, rates.first, rates.second); - auto usd = mpts.USD; - auto eth = mpts.ETH; - auto can = mpts.CAN; + auto usd = mpts.usd; + auto eth = mpts.eth; + auto can = mpts.can; std::optional amm; if (i == 0 || i == 2) @@ -6402,9 +6402,9 @@ private: { Env env(*this, features); auto mpts = prep(env, rates.first, rates.second); - auto usd = mpts.USD; - auto eth = mpts.ETH; - auto can = mpts.CAN; + auto usd = mpts.usd; + auto eth = mpts.eth; + auto can = mpts.can; std::optional amm; if (i == 0 || i == 2) { @@ -6447,9 +6447,9 @@ private: { Env env(*this, features); auto mpts = prep(env, rates.first, rates.second); - auto usd = mpts.USD; - auto eth = mpts.ETH; - auto can = mpts.CAN; + auto usd = mpts.usd; + auto eth = mpts.eth; + auto can = mpts.can; std::optional amm; if (i == 0 || i == 2) { @@ -6513,9 +6513,9 @@ private: { Env env(*this, features); auto mpts = prep(env, rates.first, rates.second); - auto usd = mpts.USD; - auto eth = mpts.ETH; - auto can = mpts.CAN; + auto usd = mpts.usd; + auto eth = mpts.eth; + auto can = mpts.can; std::optional amm; if (i == 0 || i == 2) { @@ -6591,9 +6591,9 @@ private: { Env env(*this, features); auto mpts = prep(env, rates.first, rates.second); - auto usd = mpts.USD; - auto eth = mpts.ETH; - auto can = mpts.CAN; + auto usd = mpts.usd; + auto eth = mpts.eth; + auto can = mpts.can; std::optional amm; if (i == 0 || i == 2) @@ -6929,7 +6929,7 @@ private: Env env( *this, envconfig([](std::unique_ptr cfg) { - cfg->FEES.reference_fee = XRPAmount(1); + cfg->fees.referenceFee = XRPAmount(1); return cfg; }), all); @@ -6983,7 +6983,7 @@ private: Env env( *this, envconfig([](std::unique_ptr cfg) { - cfg->FEES.reference_fee = XRPAmount(1); + cfg->fees.referenceFee = XRPAmount(1); return cfg; }), all); @@ -7018,7 +7018,7 @@ private: Env env( *this, envconfig([](std::unique_ptr cfg) { - cfg->FEES.reference_fee = XRPAmount(1); + cfg->fees.referenceFee = XRPAmount(1); return cfg; }), all); @@ -7042,7 +7042,7 @@ private: Env env( *this, envconfig([](std::unique_ptr cfg) { - cfg->FEES.reference_fee = XRPAmount(1); + cfg->fees.referenceFee = XRPAmount(1); return cfg; }), all); diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index 68ebdbcf60..17f959ae3c 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -5093,7 +5093,7 @@ private: Env env( *this, envconfig([](std::unique_ptr cfg) { - cfg->FEES.reference_fee = XRPAmount(1); + cfg->fees.referenceFee = XRPAmount(1); return cfg; }), all); @@ -5151,7 +5151,7 @@ private: Env env( *this, envconfig([](std::unique_ptr cfg) { - cfg->FEES.reference_fee = XRPAmount(1); + cfg->fees.referenceFee = XRPAmount(1); return cfg; }), all); @@ -7090,7 +7090,7 @@ private: Env env( *this, envconfig([](std::unique_ptr cfg) { - cfg->FEES.reference_fee = XRPAmount(1); + cfg->fees.referenceFee = XRPAmount(1); return cfg; }), features); diff --git a/src/test/app/FeeVote_test.cpp b/src/test/app/FeeVote_test.cpp index 4fb97a0b74..22e8322bb5 100644 --- a/src/test/app/FeeVote_test.cpp +++ b/src/test/app/FeeVote_test.cpp @@ -213,18 +213,18 @@ class FeeVote_test : public beast::unit_test::Suite // defaults Section const config; auto setup = setupFeeVote(config); - BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee); - BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve); - BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve); + BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee); + BEAST_EXPECT(setup.accountReserve == defaultSetup.accountReserve); + BEAST_EXPECT(setup.ownerReserve == defaultSetup.ownerReserve); } { Section config; config.append( {"reference_fee = 50", "account_reserve = 1234567", "owner_reserve = 1234"}); auto setup = setupFeeVote(config); - BEAST_EXPECT(setup.reference_fee == 50); - BEAST_EXPECT(setup.account_reserve == 1234567); - BEAST_EXPECT(setup.owner_reserve == 1234); + BEAST_EXPECT(setup.referenceFee == 50); + BEAST_EXPECT(setup.accountReserve == 1234567); + BEAST_EXPECT(setup.ownerReserve == 1234); } { Section config; @@ -232,9 +232,9 @@ class FeeVote_test : public beast::unit_test::Suite {"reference_fee = blah", "account_reserve = yada", "owner_reserve = foo"}); // Illegal values are ignored, and the defaults left unchanged auto setup = setupFeeVote(config); - BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee); - BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve); - BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve); + BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee); + BEAST_EXPECT(setup.accountReserve == defaultSetup.accountReserve); + BEAST_EXPECT(setup.ownerReserve == defaultSetup.ownerReserve); } { Section config; @@ -242,9 +242,9 @@ class FeeVote_test : public beast::unit_test::Suite {"reference_fee = -50", "account_reserve = -1234567", "owner_reserve = -1234"}); // Illegal values are ignored, and the defaults left unchanged auto setup = setupFeeVote(config); - BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee); - BEAST_EXPECT(setup.account_reserve == static_cast(-1234567)); - BEAST_EXPECT(setup.owner_reserve == static_cast(-1234)); + BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee); + BEAST_EXPECT(setup.accountReserve == static_cast(-1234567)); + BEAST_EXPECT(setup.ownerReserve == static_cast(-1234)); } { auto const big64 = std::to_string( @@ -256,9 +256,9 @@ class FeeVote_test : public beast::unit_test::Suite "owner_reserve = " + big64}); // Illegal values are ignored, and the defaults left unchanged auto setup = setupFeeVote(config); - BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee); - BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve); - BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve); + BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee); + BEAST_EXPECT(setup.accountReserve == defaultSetup.accountReserve); + BEAST_EXPECT(setup.ownerReserve == defaultSetup.ownerReserve); } } @@ -273,7 +273,7 @@ class FeeVote_test : public beast::unit_test::Suite auto ledger = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); @@ -303,7 +303,7 @@ class FeeVote_test : public beast::unit_test::Suite auto ledger = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); @@ -336,7 +336,7 @@ class FeeVote_test : public beast::unit_test::Suite auto ledger = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); @@ -358,7 +358,7 @@ class FeeVote_test : public beast::unit_test::Suite auto ledger = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); @@ -385,7 +385,7 @@ class FeeVote_test : public beast::unit_test::Suite auto ledger = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); @@ -424,7 +424,7 @@ class FeeVote_test : public beast::unit_test::Suite auto ledger = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); @@ -472,7 +472,7 @@ class FeeVote_test : public beast::unit_test::Suite auto ledger = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); @@ -503,7 +503,7 @@ class FeeVote_test : public beast::unit_test::Suite auto ledger = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); @@ -549,7 +549,7 @@ class FeeVote_test : public beast::unit_test::Suite auto ledger = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); @@ -578,9 +578,9 @@ class FeeVote_test : public beast::unit_test::Suite using namespace jtx; FeeSetup setup; - setup.reference_fee = 42; - setup.account_reserve = 1234567; - setup.owner_reserve = 7654321; + setup.referenceFee = 42; + setup.accountReserve = 1234567; + setup.ownerReserve = 7654321; // Test with XRPFees enabled { @@ -590,7 +590,7 @@ class FeeVote_test : public beast::unit_test::Suite auto ledger = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); @@ -609,7 +609,7 @@ class FeeVote_test : public beast::unit_test::Suite feeVote->doValidation(currentFees, ledger->rules(), *val); BEAST_EXPECT(val->isFieldPresent(sfBaseFeeDrops)); - BEAST_EXPECT(val->getFieldAmount(sfBaseFeeDrops) == XRPAmount(setup.reference_fee)); + BEAST_EXPECT(val->getFieldAmount(sfBaseFeeDrops) == XRPAmount(setup.referenceFee)); } // Test with XRPFees disabled (legacy format) @@ -620,7 +620,7 @@ class FeeVote_test : public beast::unit_test::Suite auto ledger = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); @@ -638,7 +638,7 @@ class FeeVote_test : public beast::unit_test::Suite // In legacy mode, should vote using legacy fields BEAST_EXPECT(val->isFieldPresent(sfBaseFee)); - BEAST_EXPECT(val->getFieldU64(sfBaseFee) == setup.reference_fee); + BEAST_EXPECT(val->getFieldU64(sfBaseFee) == setup.referenceFee); } } @@ -650,9 +650,9 @@ class FeeVote_test : public beast::unit_test::Suite using namespace jtx; FeeSetup setup; - setup.reference_fee = 42; - setup.account_reserve = 1234567; - setup.owner_reserve = 7654321; + setup.referenceFee = 42; + setup.accountReserve = 1234567; + setup.ownerReserve = 7654321; Env env(*this, testableAmendments() | featureXRPFees); @@ -665,7 +665,7 @@ class FeeVote_test : public beast::unit_test::Suite auto ledger = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); @@ -689,9 +689,9 @@ class FeeVote_test : public beast::unit_test::Suite env.app().getTimeKeeper().now(), pub, sec, calcNodeID(pub), [&](STValidation& v) { v.setFieldU32(sfLedgerSequence, ledger->seq()); // Vote for different fees than current - v.setFieldAmount(sfBaseFeeDrops, XRPAmount{setup.reference_fee}); - v.setFieldAmount(sfReserveBaseDrops, XRPAmount{setup.account_reserve}); - v.setFieldAmount(sfReserveIncrementDrops, XRPAmount{setup.owner_reserve}); + v.setFieldAmount(sfBaseFeeDrops, XRPAmount{setup.referenceFee}); + v.setFieldAmount(sfReserveBaseDrops, XRPAmount{setup.accountReserve}); + v.setFieldAmount(sfReserveIncrementDrops, XRPAmount{setup.ownerReserve}); }); if ((i % 2) != 0) val->setTrusted(); @@ -723,10 +723,10 @@ class FeeVote_test : public beast::unit_test::Suite BEAST_EXPECT(!feeTx.isFieldPresent(sfReferenceFeeUnits)); // Check the values - BEAST_EXPECT(feeTx.getFieldAmount(sfBaseFeeDrops) == XRPAmount{setup.reference_fee}); - BEAST_EXPECT(feeTx.getFieldAmount(sfReserveBaseDrops) == XRPAmount{setup.account_reserve}); + BEAST_EXPECT(feeTx.getFieldAmount(sfBaseFeeDrops) == XRPAmount{setup.referenceFee}); + BEAST_EXPECT(feeTx.getFieldAmount(sfReserveBaseDrops) == XRPAmount{setup.accountReserve}); BEAST_EXPECT( - feeTx.getFieldAmount(sfReserveIncrementDrops) == XRPAmount{setup.owner_reserve}); + feeTx.getFieldAmount(sfReserveIncrementDrops) == XRPAmount{setup.ownerReserve}); } void diff --git a/src/test/app/LedgerHistory_test.cpp b/src/test/app/LedgerHistory_test.cpp index e464962258..3d0e546678 100644 --- a/src/test/app/LedgerHistory_test.cpp +++ b/src/test/app/LedgerHistory_test.cpp @@ -50,7 +50,7 @@ public: return std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); } diff --git a/src/test/app/LedgerLoad_test.cpp b/src/test/app/LedgerLoad_test.cpp index fa2c8f9047..ee3bfe5192 100644 --- a/src/test/app/LedgerLoad_test.cpp +++ b/src/test/app/LedgerLoad_test.cpp @@ -40,9 +40,9 @@ class LedgerLoad_test : public beast::unit_test::Suite StartUpType type, std::optional trapTxHash) { - cfg->START_LEDGER = ledger; - cfg->START_UP = type; - cfg->TRAP_TX_HASH = trapTxHash; + cfg->startLedger = ledger; + cfg->startUp = type; + cfg->trapTxHash = trapTxHash; assert(!dbPath.empty()); cfg->legacy("database_path", dbPath); return cfg; diff --git a/src/test/app/LedgerMaster_test.cpp b/src/test/app/LedgerMaster_test.cpp index ee14a93d07..3cf9b3a9d9 100644 --- a/src/test/app/LedgerMaster_test.cpp +++ b/src/test/app/LedgerMaster_test.cpp @@ -27,9 +27,9 @@ class LedgerMaster_test : public beast::unit_test::Suite { using namespace jtx; return envconfig([&](std::unique_ptr cfg) { - cfg->NETWORK_ID = networkID; + cfg->networkId = networkID; // This test relies on ledger hash so must lock it to fee 10. - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; return cfg; }); } diff --git a/src/test/app/LedgerReplay_test.cpp b/src/test/app/LedgerReplay_test.cpp index 4b1e4510d6..1978d04fe1 100644 --- a/src/test/app/LedgerReplay_test.cpp +++ b/src/test/app/LedgerReplay_test.cpp @@ -1062,7 +1062,7 @@ struct LedgerReplayer_test : public beast::unit_test::Suite testcase("config test"); { Config const c; - BEAST_EXPECT(c.LEDGER_REPLAY == false); + BEAST_EXPECT(c.ledgerReplay == false); } { @@ -1072,7 +1072,7 @@ struct LedgerReplayer_test : public beast::unit_test::Suite 1 )xrpldConfig"); c.loadFromString(toLoad); - BEAST_EXPECT(c.LEDGER_REPLAY == true); + BEAST_EXPECT(c.ledgerReplay == true); } { @@ -1082,7 +1082,7 @@ struct LedgerReplayer_test : public beast::unit_test::Suite 0 )xrpldConfig"); c.loadFromString(toLoad); - BEAST_EXPECT(c.LEDGER_REPLAY == false); + BEAST_EXPECT(c.ledgerReplay == false); } } @@ -1101,7 +1101,7 @@ struct LedgerReplayer_test : public beast::unit_test::Suite beast::IP::Address const addr = boost::asio::ip::make_address("172.1.1.100"); jtx::Env serverEnv(*this); - serverEnv.app().config().LEDGER_REPLAY = server; + serverEnv.app().config().ledgerReplay = server; auto httpResp = xrpl::makeResponse( true, httpRequest, addr, addr, uint256{1}, 1, {1, 0}, serverEnv.app()); auto const clientResult = peerFeatureEnabled(httpResp, kFeatureLedgerReplay, client); diff --git a/src/test/app/LoadFeeTrack_test.cpp b/src/test/app/LoadFeeTrack_test.cpp index 528e90321b..aba0b35062 100644 --- a/src/test/app/LoadFeeTrack_test.cpp +++ b/src/test/app/LoadFeeTrack_test.cpp @@ -17,7 +17,7 @@ public: { Fees const fees = [&]() { Fees f; - f.base = d.FEES.reference_fee; + f.base = d.fees.referenceFee; f.reserve = 200 * kDropsPerXrp; f.increment = 50 * kDropsPerXrp; return f; @@ -30,7 +30,7 @@ public: { Fees const fees = [&]() { Fees f; - f.base = d.FEES.reference_fee * 10; + f.base = d.fees.referenceFee * 10; f.reserve = 200 * kDropsPerXrp; f.increment = 50 * kDropsPerXrp; return f; @@ -43,7 +43,7 @@ public: { Fees const fees = [&]() { Fees f; - f.base = d.FEES.reference_fee; + f.base = d.fees.referenceFee; f.reserve = 200 * kDropsPerXrp; f.increment = 50 * kDropsPerXrp; return f; diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 0e48db6080..74bdc656df 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -2125,7 +2125,7 @@ class MPToken_test : public beast::unit_test::Suite Account const alice{"alice"}; auto cfg = envconfig(); - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; Env env{*this, std::move(cfg), features}; MPTTester mptAlice(env, alice); diff --git a/src/test/app/NetworkID_test.cpp b/src/test/app/NetworkID_test.cpp index a98fbd6b4f..677d8f166d 100644 --- a/src/test/app/NetworkID_test.cpp +++ b/src/test/app/NetworkID_test.cpp @@ -37,7 +37,7 @@ public: { using namespace jtx; return envconfig([&](std::unique_ptr cfg) { - cfg->NETWORK_ID = networkID; + cfg->networkId = networkID; return cfg; }); } diff --git a/src/test/app/PathMPT_test.cpp b/src/test/app/PathMPT_test.cpp index da5597cf05..3ba67b58a6 100644 --- a/src/test/app/PathMPT_test.cpp +++ b/src/test/app/PathMPT_test.cpp @@ -87,9 +87,9 @@ class PathMPT_test : public beast::unit_test::Suite // with the search parameters that the tests were written for. using namespace jtx; return Env(*this, envconfig([](std::unique_ptr cfg) { - cfg->PATH_SEARCH_OLD = 7; - cfg->PATH_SEARCH = 7; - cfg->PATH_SEARCH_MAX = 10; + cfg->pathSearchOld = 7; + cfg->pathSearch = 7; + cfg->pathSearchMax = 10; return cfg; })); } diff --git a/src/test/app/Path_test.cpp b/src/test/app/Path_test.cpp index 1cf8131206..4cfe938798 100644 --- a/src/test/app/Path_test.cpp +++ b/src/test/app/Path_test.cpp @@ -101,9 +101,9 @@ class Path_test : public beast::unit_test::Suite // with the search parameters that the tests were written for. using namespace jtx; return Env(*this, envconfig([](std::unique_ptr cfg) { - cfg->PATH_SEARCH_OLD = 7; - cfg->PATH_SEARCH = 7; - cfg->PATH_SEARCH_MAX = 10; + cfg->pathSearchOld = 7; + cfg->pathSearch = 7; + cfg->pathSearchMax = 10; return cfg; })); } diff --git a/src/test/app/RCLValidations_test.cpp b/src/test/app/RCLValidations_test.cpp index df8e873c75..c3b0ddea9c 100644 --- a/src/test/app/RCLValidations_test.cpp +++ b/src/test/app/RCLValidations_test.cpp @@ -71,7 +71,7 @@ class RCLValidations_test : public beast::unit_test::Suite auto prev = std::make_shared( kCreateGenesis, Rules{config.features}, - config.FEES.toFees(), + config.fees.toFees(), std::vector{}, env.app().getNodeFamily()); history.push_back(prev); @@ -237,7 +237,7 @@ class RCLValidations_test : public beast::unit_test::Suite auto prev = std::make_shared( kCreateGenesis, Rules{config.features}, - config.FEES.toFees(), + config.fees.toFees(), std::vector{}, env.app().getNodeFamily()); history.push_back(prev); diff --git a/src/test/app/Regression_test.cpp b/src/test/app/Regression_test.cpp index ee3e25e2f4..1c83e97e61 100644 --- a/src/test/app/Regression_test.cpp +++ b/src/test/app/Regression_test.cpp @@ -87,7 +87,7 @@ struct Regression_test : public beast::unit_test::Suite auto closed = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); auto expectedDrops = kInitialXrp; @@ -194,7 +194,7 @@ struct Regression_test : public beast::unit_test::Suite using namespace jtx; Env env(*this, envconfig([](std::unique_ptr cfg) { cfg->section("transaction_queue").set("minimum_txn_in_ledger_standalone", "3"); - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; return cfg; })); EnvSs envs(env); diff --git a/src/test/app/SHAMapStore_test.cpp b/src/test/app/SHAMapStore_test.cpp index f03fabd92c..fc5ef02465 100644 --- a/src/test/app/SHAMapStore_test.cpp +++ b/src/test/app/SHAMapStore_test.cpp @@ -41,7 +41,7 @@ class SHAMapStore_test : public beast::unit_test::Suite static auto onlineDelete(std::unique_ptr cfg) { - cfg->LEDGER_HISTORY = kDeleteInterval; + cfg->ledgerHistory = kDeleteInterval; auto& section = cfg->section(ConfigSection::nodeDatabase()); section.set("online_delete", std::to_string(kDeleteInterval)); return cfg; diff --git a/src/test/app/Transaction_ordering_test.cpp b/src/test/app/Transaction_ordering_test.cpp index 6672b3f834..7c0721cde3 100644 --- a/src/test/app/Transaction_ordering_test.cpp +++ b/src/test/app/Transaction_ordering_test.cpp @@ -64,7 +64,7 @@ struct Transaction_ordering_test : public beast::unit_test::Suite testcase("Incorrect order"); Env env(*this, envconfig([](std::unique_ptr cfg) { - cfg->FORCE_MULTI_THREAD = false; + cfg->forceMultiThread = false; return cfg; })); @@ -102,7 +102,7 @@ struct Transaction_ordering_test : public beast::unit_test::Suite testcase("Incorrect order multiple intermediaries"); Env env(*this, envconfig([](std::unique_ptr cfg) { - cfg->FORCE_MULTI_THREAD = true; + cfg->forceMultiThread = true; return cfg; })); diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index d8882cf346..8333fce3b3 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -1259,7 +1259,7 @@ public: testcase("tie breaking"); auto cfg = makeConfig({{"minimum_txn_in_ledger_standalone", "4"}}); - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; Env env(*this, std::move(cfg)); auto alice = Account("alice"); @@ -2653,7 +2653,7 @@ public: {{"minimum_txn_in_ledger_standalone", "1"}, {"ledgers_in_queue", "10"}, {"maximum_txn_per_account", "11"}}); - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; Env env(*this, std::move(cfg)); auto const baseFee = env.current()->fees().base.drops(); @@ -4069,9 +4069,9 @@ public: {{"account_reserve", "1000"}, {"owner_reserve", "50"}}); auto& votingSection = cfg->section("voting"); - votingSection.set("account_reserve", std::to_string(cfg->FEES.reference_fee.drops() * 100)); + votingSection.set("account_reserve", std::to_string(cfg->fees.referenceFee.drops() * 100)); - votingSection.set("reference_fee", std::to_string(cfg->FEES.reference_fee.drops())); + votingSection.set("reference_fee", std::to_string(cfg->fees.referenceFee.drops())); Env env(*this, std::move(cfg)); diff --git a/src/test/app/ValidatorList_test.cpp b/src/test/app/ValidatorList_test.cpp index 1b66dd305d..20a3557db5 100644 --- a/src/test/app/ValidatorList_test.cpp +++ b/src/test/app/ValidatorList_test.cpp @@ -2257,7 +2257,7 @@ private: auto extractProtocolMessage1 = [this, &extractHeader](Message& message) { auto [header, buffers] = extractHeader(message); if (BEAST_EXPECT(header) && - BEAST_EXPECT(header->message_type == protocol::mtVALIDATOR_LIST)) + BEAST_EXPECT(header->messageType == protocol::mtVALIDATOR_LIST)) { auto const msg = detail::parseMessageContent(*header, buffers.data()); @@ -2269,7 +2269,7 @@ private: auto extractProtocolMessage2 = [this, &extractHeader](Message& message) { auto [header, buffers] = extractHeader(message); if (BEAST_EXPECT(header) && - BEAST_EXPECT(header->message_type == protocol::mtVALIDATOR_LIST_COLLECTION)) + BEAST_EXPECT(header->messageType == protocol::mtVALIDATOR_LIST_COLLECTION)) { auto const msg = detail::parseMessageContent( *header, buffers.data()); diff --git a/src/test/app/XChain_test.cpp b/src/test/app/XChain_test.cpp index 6266d5870d..0198a36e96 100644 --- a/src/test/app/XChain_test.cpp +++ b/src/test/app/XChain_test.cpp @@ -266,8 +266,8 @@ struct BalanceTransfer balance from; balance to; - balance payer; // pays the rewards - std::vector reward_accounts; // receives the reward + balance payer; // pays the rewards + std::vector rewardAccounts; // receives the reward XRPAmount txFees; BalanceTransfer( @@ -281,7 +281,7 @@ struct BalanceTransfer : from(env, fromAcct) , to(env, toAcct) , payer(env, payer) - , reward_accounts([&]() { + , rewardAccounts([&]() { std::vector r; r.reserve(numPayees); for (size_t i = 0; i < numPayees; ++i) @@ -306,7 +306,7 @@ struct BalanceTransfer [[nodiscard]] bool payeesReceived(STAmount const& reward) const { - return std::all_of(reward_accounts.begin(), reward_accounts.end(), [&](balance const& b) { + return std::all_of(rewardAccounts.begin(), rewardAccounts.end(), [&](balance const& b) { return b.diff() == reward; }); } @@ -320,7 +320,7 @@ struct BalanceTransfer bool hasHappened(STAmount const& amt, STAmount const& reward, bool checkPayer = true) { - auto rewardCost = multiply(reward, STAmount(reward_accounts.size()), reward.asset()); + auto rewardCost = multiply(reward, STAmount(rewardAccounts.size()), reward.asset()); return checkMostBalances(amt, reward) && (!checkPayer || payer.diff() == -(rewardCost + txFees)); } @@ -1007,7 +1007,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob)).close(); } - BEAST_EXPECT(transfer.hasHappened(amt, split_reward_quorum)); + BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum)); } // Check that the reward paid from a claim Id was the reward when @@ -1059,7 +1059,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj // make sure the reward accounts indeed received the original // split reward (1 split 5 ways) instead of the updated 2 XRP. - BEAST_EXPECT(transfer.hasHappened(amt, split_reward_quorum)); + BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum)); } // Check that the signatures used to verify attestations and decide @@ -1086,7 +1086,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj // change signers - claim should not be processed is the batch // is signed by original signers - scEnv.tx(jtx::signers(Account::kMaster, quorum, alt_signers)).close(); + scEnv.tx(jtx::signers(Account::kMaster, quorum, altSigners)).close(); BalanceTransfer transfer( scEnv, @@ -1120,7 +1120,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj // submit claim using current signers - should succeed scEnv .multiTx(claimAttestations( - scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, alt_signers)) + scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, altSigners)) .close(); if (withClaim) { @@ -1132,7 +1132,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj // make sure the transfer went through as we sent attestations // using new signers - BEAST_EXPECT(transfer.hasHappened(amt, split_reward_quorum, false)); + BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum, false)); } // coverage test: bridge_modify transaction with incorrect flag @@ -1220,7 +1220,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj // Creating the new object would put the account below the reserve XEnv(*this, true) .tx(createBridge(Account::kMaster, jvb)) - .fund(res1 - xrp_dust, scuAlice) // barely not enough + .fund(res1 - xrpDust, scuAlice) // barely not enough .close() .tx(xchainCreateClaimId(scuAlice, jvb, reward, mcAlice), Ter(tecINSUFFICIENT_RESERVE)) .close(); @@ -1231,7 +1231,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj XEnv(*this, true) .tx(createBridge(Account::kMaster, jvb)) .close() - .tx(xchainCreateClaimId(scAlice, jvb, split_reward_quorum, mcAlice), + .tx(xchainCreateClaimId(scAlice, jvb, splitRewardQuorum, mcAlice), Ter(tecXCHAIN_REWARD_MISMATCH)) .close(); @@ -1273,7 +1273,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj testcase("Commit"); // Commit to a non-existent bridge - XEnv(*this).tx(xchainCommit(mcAlice, jvb, 1, one_xrp, scBob), Ter(tecNO_ENTRY)); + XEnv(*this).tx(xchainCommit(mcAlice, jvb, 1, oneXrp, scBob), Ter(tecNO_ENTRY)); // check that reward not deducted when doing the commit { @@ -1309,17 +1309,17 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj // reserve (if XRP) XEnv(*this) .tx(createBridge(mcDoor, jvb)) - .fund(res0 + one_xrp - xrp_dust, mcuAlice) // barely not enough + .fund(res0 + oneXrp - xrpDust, mcuAlice) // barely not enough .close() - .tx(xchainCommit(mcuAlice, jvb, 1, one_xrp, scBob), Ter(tecUNFUNDED_PAYMENT)); + .tx(xchainCommit(mcuAlice, jvb, 1, oneXrp, scBob), Ter(tecUNFUNDED_PAYMENT)); XEnv(*this) .tx(createBridge(mcDoor, jvb)) .fund( - res0 + one_xrp + xrp_dust, // "xrp_dust" for tx fees - mcuAlice) // exactly enough => should succeed + res0 + oneXrp + xrpDust, // "xrp_dust" for tx fees + mcuAlice) // exactly enough => should succeed .close() - .tx(xchainCommit(mcuAlice, jvb, 1, one_xrp, scBob)); + .tx(xchainCommit(mcuAlice, jvb, 1, oneXrp, scBob)); // Commit an amount above the account's balance (for both XRP and // IOUs) @@ -1327,7 +1327,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj .tx(createBridge(mcDoor, jvb)) .fund(res0, mcuAlice) // barely not enough .close() - .tx(xchainCommit(mcuAlice, jvb, 1, res0 + one_xrp, scBob), Ter(tecUNFUNDED_PAYMENT)); + .tx(xchainCommit(mcuAlice, jvb, 1, res0 + oneXrp, scBob), Ter(tecUNFUNDED_PAYMENT)); auto jvbUsd = bridge(mcDoor, mcUSD, scGw, scUSD); @@ -1377,7 +1377,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj XEnv(*this) .tx(createBridge(mcDoor)) .close() - .tx(xchainCommit(mcAlice, jvb, 1, one_xrp, scBob), + .tx(xchainCommit(mcAlice, jvb, 1, oneXrp, scBob), Txflags(tfFillOrKill), Ter(temINVALID_FLAG)); @@ -1387,7 +1387,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj .tx(createBridge(mcDoor)) .disableFeature(featureXChainBridge) .close() - .tx(xchainCommit(mcAlice, jvb, 1, one_xrp, scBob), Ter(temDISABLED)); + .tx(xchainCommit(mcAlice, jvb, 1, oneXrp, scBob), Ter(temDISABLED)); } void @@ -1467,7 +1467,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj BEAST_EXPECT(scEnv.claimID(jvb) == claimID); } - BEAST_EXPECT(transfer.hasHappened(amt, split_reward_everyone)); + BEAST_EXPECT(transfer.hasHappened(amt, splitRewardEveryone)); } // Test that signature weights are correctly handled. Assign @@ -2208,12 +2208,12 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj std::uint32_t const claimID = 1; for (auto i = 0; i < kUtXchainDefaultNumSigners - 2; ++i) - scEnv.fund(amt, alt_signers[i].account); + scEnv.fund(amt, altSigners[i].account); mcEnv.tx(createBridge(mcDoor, jvb)).close(); scEnv.tx(createBridge(Account::kMaster, jvb)) - .tx(jtx::signers(Account::kMaster, quorum, alt_signers)) + .tx(jtx::signers(Account::kMaster, quorum, altSigners)) .close() .tx(xchainCreateClaimId(scAlice, jvb, reward, mcAlice)) .close(); @@ -2225,24 +2225,24 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj { // G1: master key auto att = claimAttestation( - scAttester, jvb, mcAlice, amt, payees[0], true, claimID, dst, alt_signers[0]); + scAttester, jvb, mcAlice, amt, payees[0], true, claimID, dst, altSigners[0]); scEnv.tx(att).close(); } { // G2: regular key // alt_signers[0] is the regular key of alt_signers[1] // There should be 2 attestations after the transaction - scEnv.tx(jtx::regkey(alt_signers[1].account, alt_signers[0].account)).close(); + scEnv.tx(jtx::regkey(altSigners[1].account, altSigners[0].account)).close(); auto att = claimAttestation( - scAttester, jvb, mcAlice, amt, payees[1], true, claimID, dst, alt_signers[0]); - att[sfAttestationSignerAccount.getJsonName()] = alt_signers[1].account.human(); + scAttester, jvb, mcAlice, amt, payees[1], true, claimID, dst, altSigners[0]); + att[sfAttestationSignerAccount.getJsonName()] = altSigners[1].account.human(); scEnv.tx(att).close(); } { // B3: public key and non-exist (unfunded) account mismatch // G3: public key and non-exist (unfunded) account match - auto const unfundedSigner1 = alt_signers[kUtXchainDefaultNumSigners - 1]; - auto const unfundedSigner2 = alt_signers[kUtXchainDefaultNumSigners - 2]; + auto const unfundedSigner1 = altSigners[kUtXchainDefaultNumSigners - 1]; + auto const unfundedSigner2 = altSigners[kUtXchainDefaultNumSigners - 2]; auto att = claimAttestation( scAttester, jvb, @@ -2261,7 +2261,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj { // B2: single item signer list std::vector tempSignerList = {signers[0]}; - scEnv.tx(jtx::signers(alt_signers[2].account, 1, tempSignerList)); + scEnv.tx(jtx::signers(altSigners[2].account, 1, tempSignerList)); auto att = claimAttestation( scAttester, jvb, @@ -2272,14 +2272,14 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj claimID, dst, tempSignerList.front()); - att[sfAttestationSignerAccount.getJsonName()] = alt_signers[2].account.human(); + att[sfAttestationSignerAccount.getJsonName()] = altSigners[2].account.human(); scEnv.tx(att, Ter(tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR)).close(); } { // B1: disabled master key - scEnv.tx(fset(alt_signers[2].account, asfDisableMaster, 0)).close(); + scEnv.tx(fset(altSigners[2].account, asfDisableMaster, 0)).close(); auto att = claimAttestation( - scAttester, jvb, mcAlice, amt, payees[2], true, claimID, dst, alt_signers[2]); + scAttester, jvb, mcAlice, amt, payees[2], true, claimID, dst, altSigners[2]); scEnv.tx(att, Ter(tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR)).close(); } { @@ -2292,11 +2292,11 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj // --B5: missing sfAttestationSignerAccount field // Then submit the one with the field. Should reach quorum. auto att = claimAttestation( - scAttester, jvb, mcAlice, amt, payees[3], true, claimID, dst, alt_signers[3]); + scAttester, jvb, mcAlice, amt, payees[3], true, claimID, dst, altSigners[3]); att.removeMember(sfAttestationSignerAccount.getJsonName()); scEnv.tx(att, Ter(temMALFORMED)).close(); BEAST_EXPECT(dstStartBalance == scEnv.env.balance(dst)); - att[sfAttestationSignerAccount.getJsonName()] = alt_signers[3].account.human(); + att[sfAttestationSignerAccount.getJsonName()] = altSigners[3].account.human(); scEnv.tx(att).close(); BEAST_EXPECT(dstStartBalance + amt == scEnv.env.balance(dst)); } @@ -2431,7 +2431,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj scEnv.tx(xchainClaim(scAlice, jvb, claimID, amt, scBob)).close(); } - BEAST_EXPECT(transfer.hasHappened(amt, split_reward_quorum)); + BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum)); } // Claim with just one attestation signed by the Master key @@ -2641,7 +2641,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj } else { - BEAST_EXPECT(transfer.hasHappened(amt, split_reward_quorum)); + BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum)); } } @@ -3180,7 +3180,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj // claim wrong amount scEnv - .tx(xchainClaim(scAlice, jvb, claimID, one_xrp, scBob), + .tx(xchainClaim(scAlice, jvb, claimID, oneXrp, scBob), Ter(tecXCHAIN_CLAIM_NO_QUORUM)) .close(); } @@ -3232,7 +3232,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj claimCost += fee; } - BEAST_EXPECT(transfer.hasHappened(amt, split_reward_quorum)); + BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum)); BEAST_EXPECT(scAliceBal.diff() == -claimCost); // because reward % 4 == 0 } @@ -3244,12 +3244,12 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj XEnv mcEnv(*this); XEnv scEnv(*this, true); - mcEnv.tx(createBridge(mcDoor, jvb, tiny_reward)).close(); + mcEnv.tx(createBridge(mcDoor, jvb, tinyReward)).close(); - scEnv.tx(createBridge(Account::kMaster, jvb, tiny_reward)) + scEnv.tx(createBridge(Account::kMaster, jvb, tinyReward)) .tx(jtx::signers(Account::kMaster, quorum, signers)) .close() - .tx(xchainCreateClaimId(scAlice, jvb, tiny_reward, mcAlice)) + .tx(xchainCreateClaimId(scAlice, jvb, tinyReward, mcAlice)) .close(); auto dst(withClaim ? std::nullopt : std::optional{scBob}); @@ -3268,7 +3268,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj test::Balance const scAliceBal(scEnv, scAlice); scEnv.multiTx(claimAttestations( scAttester, jvb, mcAlice, amt, payees, true, claimID, dst, signers)); - STAmount claimCost = tiny_reward; + STAmount claimCost = tinyReward; if (withClaim) { @@ -3279,8 +3279,8 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj claimCost += fee; } - BEAST_EXPECT(transfer.hasHappened(amt, tiny_reward_split)); - BEAST_EXPECT(scAliceBal.diff() == -(claimCost - tiny_reward_remainder)); + BEAST_EXPECT(transfer.hasHappened(amt, tinyRewardSplit)); + BEAST_EXPECT(scAliceBal.diff() == -(claimCost - tinyRewardRemainder)); } // If a reward distribution fails for one of the reward accounts @@ -3331,7 +3331,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj // this also checks that only 3 * split_reward was deducted from // scAlice (the payer account), since we passed alt_payees to // BalanceTransfer - BEAST_EXPECT(transfer.hasHappened(amt, split_reward_quorum)); + BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum)); } for (auto withClaim : {false, true}) @@ -3381,7 +3381,7 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj // this also checks that only 3 * split_reward was deducted from // scAlice (the payer account), since we passed payees.size() - // 1 to BalanceTransfer - BEAST_EXPECT(transfer.hasHappened(amt, split_reward_quorum)); + BEAST_EXPECT(transfer.hasHappened(amt, splitRewardQuorum)); // and make sure the account with deposit auth received nothing BEAST_EXPECT(lastSigner.diff() == STAmount(0)); @@ -3578,17 +3578,17 @@ struct XChain_test : public beast::unit_test::Suite, public jtx::XChainBridgeObj // commit where the fee dips into the reserve, this should succeed XEnv(*this) .tx(createBridge(mcDoor, jvb)) - .fund(res0 + one_xrp + fee - drops(1), mcuAlice) + .fund(res0 + oneXrp + fee - drops(1), mcuAlice) .close() - .tx(xchainCommit(mcuAlice, jvb, 1, one_xrp, scBob), Ter(tesSUCCESS)); + .tx(xchainCommit(mcuAlice, jvb, 1, oneXrp, scBob), Ter(tesSUCCESS)); // commit where the commit amount drips into the reserve, this should // fail XEnv(*this) .tx(createBridge(mcDoor, jvb)) - .fund(res0 + one_xrp - drops(1), mcuAlice) + .fund(res0 + oneXrp - drops(1), mcuAlice) .close() - .tx(xchainCommit(mcuAlice, jvb, 1, one_xrp, scBob), Ter(tecUNFUNDED_PAYMENT)); + .tx(xchainCommit(mcuAlice, jvb, 1, oneXrp, scBob), Ter(tecUNFUNDED_PAYMENT)); auto const minAccountCreate = XRP(20); @@ -3757,8 +3757,8 @@ private: jtx::Account finaldest; STAmount amt; bool a2b; // direction of transfer - WithClaim with_claim{WithClaim::No}; - uint32_t claim_id{0}; + WithClaim withClaim{WithClaim::No}; + uint32_t claimId{0}; std::array attested{}; }; @@ -3769,7 +3769,7 @@ private: STAmount amt; STAmount reward; bool a2b; - uint32_t claim_id{0}; + uint32_t claimId{0}; std::array attested{}; }; @@ -3806,7 +3806,7 @@ private: using CreateClaimVec = jtx::JValueVec; using CreateClaimMap = std::map; - ChainStateTrack(ENV& env) : env(env), tx_fee(env.env.current()->fees().base) + ChainStateTrack(ENV& env) : env(env), txFee(env.env.current()->fees().base) { } @@ -3851,31 +3851,30 @@ private: { callbackCalled = false; // cspell: ignore attns - for (size_t i = 0; i < signers_attns.size(); ++i) + for (size_t i = 0; i < signersAttns.size(); ++i) { - for (auto& [bridge, claims] : signers_attns[i]) + for (auto& [bridge, claims] : signersAttns[i]) { - sendAttestations(i, bridge, claims.xfer_claims); + sendAttestations(i, bridge, claims.xferClaims); auto& c = counters[bridge]; - auto& createClaims = claims.create_claims[c.claim_count]; + auto& createClaims = claims.createClaims[c.claimCount]; auto numAttns = createClaims.size(); if (numAttns != 0u) { - c.num_create_attn_sent += - sendCreateAttestations(i, bridge, createClaims); + c.numCreateAttnSent += sendCreateAttestations(i, bridge, createClaims); } - assert(claims.create_claims[c.claim_count].empty()); + assert(claims.createClaims[c.claimCount].empty()); } } for (auto& [bridge, c] : counters) { - if (c.num_create_attn_sent >= bridge->quorum) + if (c.numCreateAttnSent >= bridge->quorum) { callbackCalled = true; - c.create_callbacks[c.claim_count](c.signers); - ++c.claim_count; - c.num_create_attn_sent = 0; + c.createCallbacks[c.claimCount](c.signers); + ++c.claimCount; + c.numCreateAttnSent = 0; c.signers.clear(); } } @@ -3926,7 +3925,7 @@ private: void spendFee(jtx::Account const& acct, size_t times = 1) { - spend(acct, tx_fee, times); + spend(acct, txFee, times); } [[nodiscard]] bool @@ -3944,20 +3943,20 @@ private: { using complete_cb = std::function const& signers)>; - uint32_t claim_id{0}; - uint32_t create_count{0}; // for account create. First should be 1 - uint32_t claim_count{0}; // for account create. Increments after quorum for - // current create_count (starts at 1) is reached. + uint32_t claimId{0}; + uint32_t createCount{0}; // for account create. First should be 1 + uint32_t claimCount{0}; // for account create. Increments after quorum for + // current createCount (starts at 1) is reached. - uint32_t num_create_attn_sent{0}; // for current claim_count + uint32_t numCreateAttnSent{0}; // for current claimCount std::vector signers; - std::vector create_callbacks; + std::vector createCallbacks; }; struct Claims { - ClaimVec xfer_claims; - CreateClaimMap create_claims; + ClaimVec xferClaims; + CreateClaimMap createClaims; }; using SignerAttns = std::unordered_map; @@ -3965,9 +3964,9 @@ private: ENV& env; std::map accounts; - SignersAttns signers_attns; + SignersAttns signersAttns; std::map counters; - STAmount tx_fee; + STAmount txFee; }; struct ChainStateTracker @@ -4086,7 +4085,7 @@ private: st.transfer(cr_.from, srcdoor, cr_.amt); st.transfer(cr_.from, srcdoor, cr_.reward); - return ++st.counters[&bridge_].create_count; + return ++st.counters[&bridge_].createCount; } void @@ -4105,9 +4104,8 @@ private: // enqueue one attestation for this signer cr_.attested[signerIdx] = true; - st.signers_attns[signerIdx][&bridge_] - .create_claims[cr_.claim_id - 1] - .emplace_back(createAccountAttestation( + st.signersAttns[signerIdx][&bridge_].createClaims[cr_.claimId - 1].emplace_back( + createAccountAttestation( bridge_.signers[signerIdx].account, bridge_.jvb, cr_.from, @@ -4115,7 +4113,7 @@ private: cr_.reward, bridge_.signers[signerIdx].account, cr_.a2b, - cr_.claim_id, + cr_.claimId, cr_.to, bridge_.signers[signerIdx])); break; @@ -4126,15 +4124,15 @@ private: return; // did not attest auto& counters = st.counters[&bridge_]; - if (counters.create_callbacks.size() < cr_.claim_id) - counters.create_callbacks.resize(cr_.claim_id); + if (counters.createCallbacks.size() < cr_.claimId) + counters.createCallbacks.resize(cr_.claimId); auto completeCb = [&](std::vector const& signers) { auto numAttestors = signers.size(); st.env.close(); assert(numAttestors <= std::count(cr_.attested.begin(), cr_.attested.end(), true)); assert(numAttestors >= bridge_.quorum); - assert(cr_.claim_id - 1 == counters.claim_count); + assert(cr_.claimId - 1 == counters.claimCount); auto r = cr_.reward; auto reward = divide(r, STAmount(numAttestors), r.asset()); @@ -4145,20 +4143,20 @@ private: st.spend(dstDoor(), reward, numAttestors); st.transfer(dstDoor(), cr_.to, cr_.amt); st.env.env.memoize(cr_.to); - sm_state_ = SmState::Completed; + smState_ = SmState::Completed; }; - counters.create_callbacks[cr_.claim_id - 1] = std::move(completeCb); + counters.createCallbacks[cr_.claimId - 1] = std::move(completeCb); } SmState advance(uint64_t time, uint32_t rnd) { - switch (sm_state_) + switch (smState_) { case SmState::Initial: - cr_.claim_id = issueAccountCreate(); - sm_state_ = SmState::Attesting; + cr_.claimId = issueAccountCreate(); + smState_ = SmState::Attesting; break; case SmState::Attesting: @@ -4172,11 +4170,11 @@ private: case SmState::Completed: break; // will get this once } - return sm_state_; + return smState_; } private: - SmState sm_state_{SmState::Initial}; + SmState smState_{SmState::Initial}; AccountCreate cr_; }; @@ -4209,7 +4207,7 @@ private: .close(); // needed for claim_id sequence to be // correct' st.spendFee(xfer_.to); - return ++st.counters[&bridge_].claim_id; + return ++st.counters[&bridge_].claimId; } void @@ -4226,10 +4224,10 @@ private: st.env.tx(xchainCommit( xfer_.from, bridge_.jvb, - xfer_.claim_id, + xfer_.claimId, xfer_.amt, - xfer_.with_claim == WithClaim::Yes ? std::nullopt - : std::optional(xfer_.finaldest))); + xfer_.withClaim == WithClaim::Yes ? std::nullopt + : std::optional(xfer_.finaldest))); st.spendFee(xfer_.from); st.transfer(xfer_.from, srcdoor, xfer_.amt); } @@ -4262,15 +4260,15 @@ private: // enqueue one attestation for this signer xfer_.attested[signerIdx] = true; - st.signers_attns[signerIdx][&bridge_].xfer_claims.emplace_back(claimAttestation( + st.signersAttns[signerIdx][&bridge_].xferClaims.emplace_back(claimAttestation( bridge_.signers[signerIdx].account, bridge_.jvb, xfer_.from, xfer_.amt, bridge_.signers[signerIdx].account, xfer_.a2b, - xfer_.claim_id, - xfer_.with_claim == WithClaim::Yes + xfer_.claimId, + xfer_.withClaim == WithClaim::Yes ? std::nullopt : std::optional(xfer_.finaldest), bridge_.signers[signerIdx])); @@ -4281,7 +4279,7 @@ private: // return true if quorum was reached, false otherwise bool const quorum = std::count(xfer_.attested.begin(), xfer_.attested.end(), true) >= bridge_.quorum; - if (quorum && xfer_.with_claim == WithClaim::No) + if (quorum && xfer_.withClaim == WithClaim::No) { distributeReward(st); st.transfer(dstDoor(), xfer_.finaldest, xfer_.amt); @@ -4294,7 +4292,7 @@ private: { ChainStateTrack& st = destState(); st.env.tx( - xchainClaim(xfer_.to, bridge_.jvb, xfer_.claim_id, xfer_.amt, xfer_.finaldest)); + xchainClaim(xfer_.to, bridge_.jvb, xfer_.claimId, xfer_.amt, xfer_.finaldest)); distributeReward(st); st.transfer(dstDoor(), xfer_.finaldest, xfer_.amt); st.spendFee(xfer_.to); @@ -4303,34 +4301,34 @@ private: SmState advance(uint64_t time, uint32_t rnd) { - switch (sm_state_) + switch (smState_) { case SmState::Initial: - xfer_.claim_id = createClaimId(); - sm_state_ = SmState::ClaimIdCreated; + xfer_.claimId = createClaimId(); + smState_ = SmState::ClaimIdCreated; break; case SmState::ClaimIdCreated: commit(); - sm_state_ = SmState::Attesting; + smState_ = SmState::Attesting; break; case SmState::Attesting: if (attest(time, rnd)) { - sm_state_ = xfer_.with_claim == WithClaim::Yes ? SmState::Attested - : SmState::Completed; + smState_ = xfer_.withClaim == WithClaim::Yes ? SmState::Attested + : SmState::Completed; } else { - sm_state_ = SmState::Attesting; + smState_ = SmState::Attesting; } break; case SmState::Attested: - assert(xfer_.with_claim == WithClaim::Yes); + assert(xfer_.withClaim == WithClaim::Yes); claim(); - sm_state_ = SmState::Completed; + smState_ = SmState::Completed; break; default: @@ -4338,12 +4336,12 @@ private: assert(0); // should have been removed break; } - return sm_state_; + return smState_; } private: Transfer xfer_; - SmState sm_state_{SmState::Initial}; + SmState smState_{SmState::Initial}; }; // -------------------------------------------------- @@ -4573,7 +4571,7 @@ public: .finaldest = a[1], .amt = XRP(6), .a2b = true, - .with_claim = WithClaim::No}); + .withClaim = WithClaim::No}); xfer( 1, st, @@ -4583,7 +4581,7 @@ public: .finaldest = a[1], .amt = XRP(8), .a2b = false, - .with_claim = WithClaim::No}); + .withClaim = WithClaim::No}); xfer( 1, st, xrpB, {.from = a[1], .to = a[1], .finaldest = a[1], .amt = XRP(1), .a2b = true}); xfer( @@ -4605,7 +4603,7 @@ public: .finaldest = a[1], .amt = XRP(7), .a2b = false, - .with_claim = WithClaim::No}); + .withClaim = WithClaim::No}); xfer( 2, st, xrpB, {.from = a[1], .to = a[1], .finaldest = a[1], .amt = XRP(9), .a2b = true}); runSimulation(st); diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index b8fed1d981..6cc8d4fde1 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -453,7 +453,7 @@ public: // Vary the time it takes to process validations to exercise detecting // the wrong LCL at different phases of consensus - for (auto validationDelay : {0ms, parms.ledgerMIN_CLOSE}) + for (auto validationDelay : {0ms, parms.ledgerMinClose}) { // Consider 10 peers: // 0 1 2 3 4 5 6 7 8 9 @@ -492,7 +492,7 @@ public: CollectByNode jumps; sim.collectors.add(jumps); - BEAST_EXPECT(sim.trustGraph.canFork(parms.minCONSENSUS_PCT / 100.)); + BEAST_EXPECT(sim.trustGraph.canFork(parms.minConsensusPct / 100.)); // initial round to set prior state sim.run(1); diff --git a/src/test/consensus/NegativeUNL_test.cpp b/src/test/consensus/NegativeUNL_test.cpp index 05dc3f0e2f..142077a42f 100644 --- a/src/test/consensus/NegativeUNL_test.cpp +++ b/src/test/consensus/NegativeUNL_test.cpp @@ -239,7 +239,7 @@ class NegativeUNL_test : public beast::unit_test::Suite auto l = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); @@ -554,12 +554,12 @@ struct NetworkHistory createNodes() { assert(param.numNodes <= 256); - UNLKeys = createPublicKeys(param.numNodes); + unlKeys = createPublicKeys(param.numNodes); for (int i = 0; i < param.numNodes; ++i) { - UNLKeySet.insert(UNLKeys[i]); - UNLNodeIDs.push_back(calcNodeID(UNLKeys[i])); - UNLNodeIDSet.insert(UNLNodeIDs.back()); + unlKeySet.insert(unlKeys[i]); + unlNodeIDs.push_back(calcNodeID(unlKeys[i])); + unlNodeIdSet.insert(unlNodeIDs.back()); } } @@ -574,7 +574,7 @@ struct NetworkHistory auto l = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{kFakeAmendment++}, env.app().getNodeFamily()); history.push_back(l); @@ -593,7 +593,7 @@ struct NetworkHistory OpenView accum(&*l); if (l->negativeUNL().size() < param.negUNLSize) { - auto tx = createTx(true, l->seq(), UNLKeys[nidx]); + auto tx = createTx(true, l->seq(), unlKeys[nidx]); if (!applyAndTestResult(env, accum, tx, true)) break; ++nidx; @@ -602,14 +602,14 @@ struct NetworkHistory { if (param.hasToDisable) { - auto tx = createTx(true, l->seq(), UNLKeys[nidx]); + auto tx = createTx(true, l->seq(), unlKeys[nidx]); if (!applyAndTestResult(env, accum, tx, true)) break; ++nidx; } if (param.hasToReEnable) { - auto tx = createTx(false, l->seq(), UNLKeys[0]); + auto tx = createTx(false, l->seq(), unlKeys[0]); if (!applyAndTestResult(env, accum, tx, true)) break; } @@ -665,9 +665,9 @@ struct NetworkHistory { if (needVal(history[curr], i)) { - RCLValidation v(createSTVal(history[curr], UNLNodeIDs[i])); + RCLValidation v(createSTVal(history[curr], unlNodeIDs[i])); v.setTrusted(); - validations.add(UNLNodeIDs[i], v); + validations.add(unlNodeIDs[i], v); } } } @@ -682,10 +682,10 @@ struct NetworkHistory jtx::Env env; Parameter param; RCLValidations& validations; - std::vector UNLKeys; - hash_set UNLKeySet; - std::vector UNLNodeIDs; - hash_set UNLNodeIDSet; + std::vector unlKeys; + hash_set unlKeySet; + std::vector unlNodeIDs; + hash_set unlNodeIdSet; LedgerHistory history; bool goodHistory; }; @@ -714,7 +714,7 @@ voteAndCheck( pre(vote); auto txSet = std::make_shared(SHAMapType::TRANSACTION, history.env.app().getNodeFamily()); - vote.doVoting(history.lastLedger(), history.UNLKeySet, history.validations, txSet); + vote.doVoting(history.lastLedger(), history.unlKeySet, history.validations, txSet); return countTx(txSet) == expect; } @@ -795,9 +795,9 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::Suite BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { - NegativeUNLVote vote(history.UNLNodeIDs[3], history.env.journal); + NegativeUNLVote vote(history.unlNodeIDs[3], history.env.journal); BEAST_EXPECT(!vote.buildScoreTable( - history.lastLedger(), history.UNLNodeIDSet, history.validations)); + history.lastLedger(), history.unlNodeIdSet, history.validations)); } } @@ -813,9 +813,9 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::Suite BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { - NegativeUNLVote vote(history.UNLNodeIDs[3], history.env.journal); + NegativeUNLVote vote(history.unlNodeIDs[3], history.env.journal); BEAST_EXPECT(!vote.buildScoreTable( - history.lastLedger(), history.UNLNodeIDSet, history.validations)); + history.lastLedger(), history.unlNodeIdSet, history.validations)); } } @@ -831,15 +831,15 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::Suite BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { - NodeID myId = history.UNLNodeIDs[3]; + NodeID myId = history.unlNodeIDs[3]; history.walkHistoryAndAddValidations( [&](std::shared_ptr const& l, std::size_t idx) -> bool { // skip half my validations. - return history.UNLNodeIDs[idx] != myId || l->seq() % 2 != 0; + return history.unlNodeIDs[idx] != myId || l->seq() % 2 != 0; }); NegativeUNLVote vote(myId, history.env.journal); BEAST_EXPECT(!vote.buildScoreTable( - history.lastLedger(), history.UNLNodeIDSet, history.validations)); + history.lastLedger(), history.unlNodeIdSet, history.validations)); } } @@ -864,12 +864,12 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::Suite if (history.goodHistory && wrongChainSuccess) { - NodeID myId = history.UNLNodeIDs[3]; - NodeID const badNode = history.UNLNodeIDs[4]; + NodeID myId = history.unlNodeIDs[3]; + NodeID const badNode = history.unlNodeIDs[4]; history.walkHistoryAndAddValidations( [&](std::shared_ptr const& l, std::size_t idx) -> bool { // everyone but me - return !(history.UNLNodeIDs[idx] == myId); + return !(history.unlNodeIDs[idx] == myId); }); // local node validate wrong chain @@ -887,7 +887,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::Suite // local node still on wrong chain, can build a scoreTable, // but all other nodes' scores are zero auto scoreTable = vote.buildScoreTable( - wrongChain.back(), history.UNLNodeIDSet, history.validations); + wrongChain.back(), history.unlNodeIdSet, history.validations); BEAST_EXPECT(scoreTable); if (scoreTable) { @@ -907,7 +907,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::Suite // if local node switched to right history, but cannot build // scoreTable because not enough local validations BEAST_EXPECT(!vote.buildScoreTable( - history.lastLedger(), history.UNLNodeIDSet, history.validations)); + history.lastLedger(), history.unlNodeIdSet, history.validations)); } } @@ -927,9 +927,9 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::Suite [&](std::shared_ptr const& l, std::size_t idx) -> bool { return true; }); - NegativeUNLVote vote(history.UNLNodeIDs[3], history.env.journal); + NegativeUNLVote vote(history.unlNodeIDs[3], history.env.journal); auto scoreTable = vote.buildScoreTable( - history.lastLedger(), history.UNLNodeIDSet, history.validations); + history.lastLedger(), history.unlNodeIdSet, history.validations); BEAST_EXPECT(scoreTable); if (scoreTable) { @@ -1001,35 +1001,35 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::Suite hash_set negUnl012; for (std::uint32_t i = 0; i < 3; ++i) - negUnl012.insert(history.UNLNodeIDs[i]); + negUnl012.insert(history.unlNodeIDs[i]); // build a good scoreTable to use, or copy and modify hash_map goodScoreTable; - for (auto const& n : history.UNLNodeIDs) + for (auto const& n : history.unlNodeIDs) goodScoreTable[n] = NegativeUNLVote::kNegativeUnlHighWaterMark + 1; - NegativeUNLVote vote(history.UNLNodeIDs[0], history.env.journal); + NegativeUNLVote vote(history.unlNodeIDs[0], history.env.journal); { // all good scores BEAST_EXPECT( - checkCandidateSizes(vote, history.UNLNodeIDSet, negUnl012, goodScoreTable, 0, 3)); + checkCandidateSizes(vote, history.unlNodeIdSet, negUnl012, goodScoreTable, 0, 3)); } { // all bad scores hash_map scoreTable; - for (auto& n : history.UNLNodeIDs) + for (auto& n : history.unlNodeIDs) scoreTable[n] = NegativeUNLVote::kNegativeUnlLowWaterMark - 1; BEAST_EXPECT( - checkCandidateSizes(vote, history.UNLNodeIDSet, negUnl012, scoreTable, 35 - 3, 0)); + checkCandidateSizes(vote, history.unlNodeIdSet, negUnl012, scoreTable, 35 - 3, 0)); } { // all between watermarks hash_map scoreTable; - for (auto& n : history.UNLNodeIDs) + for (auto& n : history.unlNodeIDs) scoreTable[n] = NegativeUNLVote::kNegativeUnlLowWaterMark + 1; BEAST_EXPECT( - checkCandidateSizes(vote, history.UNLNodeIDSet, negUnl012, scoreTable, 0, 0)); + checkCandidateSizes(vote, history.unlNodeIdSet, negUnl012, scoreTable, 0, 0)); } { @@ -1037,36 +1037,36 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::Suite auto scoreTable = goodScoreTable; scoreTable[*negUnl012.begin()] = NegativeUNLVote::kNegativeUnlLowWaterMark + 1; BEAST_EXPECT( - checkCandidateSizes(vote, history.UNLNodeIDSet, negUnl012, scoreTable, 0, 2)); + checkCandidateSizes(vote, history.unlNodeIdSet, negUnl012, scoreTable, 0, 2)); } { // 2 bad scorers not in negUnl auto scoreTable = goodScoreTable; - scoreTable[history.UNLNodeIDs[11]] = NegativeUNLVote::kNegativeUnlLowWaterMark - 1; - scoreTable[history.UNLNodeIDs[12]] = NegativeUNLVote::kNegativeUnlLowWaterMark - 1; + scoreTable[history.unlNodeIDs[11]] = NegativeUNLVote::kNegativeUnlLowWaterMark - 1; + scoreTable[history.unlNodeIDs[12]] = NegativeUNLVote::kNegativeUnlLowWaterMark - 1; BEAST_EXPECT( - checkCandidateSizes(vote, history.UNLNodeIDSet, negUnl012, scoreTable, 2, 3)); + checkCandidateSizes(vote, history.unlNodeIdSet, negUnl012, scoreTable, 2, 3)); } { // 2 in negUnl but not in unl, have a remove candidate from score // table - hash_set unlTemp = history.UNLNodeIDSet; - unlTemp.erase(history.UNLNodeIDs[0]); - unlTemp.erase(history.UNLNodeIDs[1]); + hash_set unlTemp = history.unlNodeIdSet; + unlTemp.erase(history.unlNodeIDs[0]); + unlTemp.erase(history.unlNodeIDs[1]); BEAST_EXPECT(checkCandidateSizes(vote, unlTemp, negUnl012, goodScoreTable, 0, 3)); } { // 2 in negUnl but not in unl, no remove candidate from score table auto scoreTable = goodScoreTable; - scoreTable.erase(history.UNLNodeIDs[0]); - scoreTable.erase(history.UNLNodeIDs[1]); - scoreTable[history.UNLNodeIDs[2]] = NegativeUNLVote::kNegativeUnlLowWaterMark + 1; - hash_set unlTemp = history.UNLNodeIDSet; - unlTemp.erase(history.UNLNodeIDs[0]); - unlTemp.erase(history.UNLNodeIDs[1]); + scoreTable.erase(history.unlNodeIDs[0]); + scoreTable.erase(history.unlNodeIDs[1]); + scoreTable[history.unlNodeIDs[2]] = NegativeUNLVote::kNegativeUnlLowWaterMark + 1; + hash_set unlTemp = history.unlNodeIdSet; + unlTemp.erase(history.unlNodeIDs[0]); + unlTemp.erase(history.unlNodeIDs[1]); BEAST_EXPECT(checkCandidateSizes(vote, unlTemp, negUnl012, scoreTable, 0, 2)); } @@ -1075,7 +1075,7 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::Suite NodeID const new1(0xbead); NodeID const new2(0xbeef); hash_set const nowTrusted = {new1, new2}; - hash_set unlTemp = history.UNLNodeIDSet; + hash_set unlTemp = history.unlNodeIdSet; unlTemp.insert(new1); unlTemp.insert(new2); vote.newValidators(256, nowTrusted); @@ -1381,7 +1381,7 @@ class NegativeUNLVoteScoreTable_test : public beast::unit_test::Suite BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { - NodeID myId = history.UNLNodeIDs[3]; + NodeID myId = history.unlNodeIDs[3]; history.walkHistoryAndAddValidations( [&](std::shared_ptr const& l, std::size_t idx) -> bool { std::size_t k = 0; @@ -1400,19 +1400,19 @@ class NegativeUNLVoteScoreTable_test : public beast::unit_test::Suite bool const add50 = scorePattern[sp][k] == 50 && l->seq() % 2 == 0; bool const add100 = scorePattern[sp][k] == 100; - bool const addMe = history.UNLNodeIDs[idx] == myId; + bool const addMe = history.unlNodeIDs[idx] == myId; return add50 || add100 || addMe; }); NegativeUNLVote vote(myId, history.env.journal); auto scoreTable = vote.buildScoreTable( - history.lastLedger(), history.UNLNodeIDSet, history.validations); + history.lastLedger(), history.unlNodeIdSet, history.validations); BEAST_EXPECT(scoreTable); if (scoreTable) { std::uint32_t i = 0; // looping unl auto checkScores = [&](std::uint32_t score, std::uint32_t k) -> bool { - if (history.UNLNodeIDs[i] == myId) + if (history.unlNodeIDs[i] == myId) return score == 256; if (scorePattern[sp][k] == 0) return score == 0; @@ -1427,15 +1427,15 @@ class NegativeUNLVoteScoreTable_test : public beast::unit_test::Suite }; for (; i < 2; ++i) { - BEAST_EXPECT(checkScores((*scoreTable)[history.UNLNodeIDs[i]], 0)); + BEAST_EXPECT(checkScores((*scoreTable)[history.unlNodeIDs[i]], 0)); } for (; i < 4; ++i) { - BEAST_EXPECT(checkScores((*scoreTable)[history.UNLNodeIDs[i]], 1)); + BEAST_EXPECT(checkScores((*scoreTable)[history.unlNodeIDs[i]], 1)); } for (; i < unlSize; ++i) { - BEAST_EXPECT(checkScores((*scoreTable)[history.UNLNodeIDs[i]], 2)); + BEAST_EXPECT(checkScores((*scoreTable)[history.unlNodeIDs[i]], 2)); } } } @@ -1506,7 +1506,7 @@ class NegativeUNLVoteGoodScore_test : public beast::unit_test::Suite [&](std::shared_ptr const& l, std::size_t idx) -> bool { return true; }); - BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 0)); + BEAST_EXPECT(voteAndCheck(history, history.unlNodeIDs[0], 0)); } } @@ -1527,7 +1527,7 @@ class NegativeUNLVoteGoodScore_test : public beast::unit_test::Suite [&](std::shared_ptr const& l, std::size_t idx) -> bool { return true; }); - BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 1)); + BEAST_EXPECT(voteAndCheck(history, history.unlNodeIDs[0], 1)); } } } @@ -1564,7 +1564,7 @@ class NegativeUNLVoteOffline_test : public beast::unit_test::Suite // skip node 0 and node 1 return idx > 1; }); - BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs.back(), 1)); + BEAST_EXPECT(voteAndCheck(history, history.unlNodeIDs.back(), 1)); } } @@ -1587,9 +1587,9 @@ class NegativeUNLVoteOffline_test : public beast::unit_test::Suite history.walkHistoryAndAddValidations( [&](std::shared_ptr const& l, std::size_t idx) -> bool { // skip node 0 and node 1 - return history.UNLNodeIDs[idx] != n1 && history.UNLNodeIDs[idx] != n2; + return history.unlNodeIDs[idx] != n1 && history.unlNodeIDs[idx] != n2; }); - BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs.back(), 0)); + BEAST_EXPECT(voteAndCheck(history, history.unlNodeIDs.back(), 0)); } } } @@ -1626,7 +1626,7 @@ class NegativeUNLVoteMaxListed_test : public beast::unit_test::Suite // skip node 0 ~ 10 return idx > 10; }); - BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs.back(), 0)); + BEAST_EXPECT(voteAndCheck(history, history.unlNodeIDs.back(), 0)); } } } @@ -1662,7 +1662,7 @@ class NegativeUNLVoteRetiredValidator_test : public beast::unit_test::Suite [&](std::shared_ptr const& l, std::size_t idx) -> bool { return idx > 1; }); - BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 0)); + BEAST_EXPECT(voteAndCheck(history, history.unlNodeIDs[0], 0)); } } @@ -1705,9 +1705,9 @@ class NegativeUNLVoteRetiredValidator_test : public beast::unit_test::Suite return idx > 1; }); BEAST_EXPECT( - voteAndCheck(history, history.UNLNodeIDs.back(), 1, [&](NegativeUNLVote& vote) { - history.UNLKeySet.erase(history.UNLKeys[0]); - history.UNLKeySet.erase(history.UNLKeys[1]); + voteAndCheck(history, history.unlNodeIDs.back(), 1, [&](NegativeUNLVote& vote) { + history.unlKeySet.erase(history.unlKeys[0]); + history.unlKeySet.erase(history.unlKeys[1]); })); } } @@ -1745,11 +1745,11 @@ class NegativeUNLVoteNewValidator_test : public beast::unit_test::Suite return true; }); BEAST_EXPECT( - voteAndCheck(history, history.UNLNodeIDs[0], 0, [&](NegativeUNLVote& vote) { + voteAndCheck(history, history.unlNodeIDs[0], 0, [&](NegativeUNLVote& vote) { auto extraKey1 = randomKeyPair(KeyType::Ed25519).first; auto extraKey2 = randomKeyPair(KeyType::Ed25519).first; - history.UNLKeySet.insert(extraKey1); - history.UNLKeySet.insert(extraKey2); + history.unlKeySet.insert(extraKey1); + history.unlKeySet.insert(extraKey2); hash_set nowTrusted; nowTrusted.insert(calcNodeID(extraKey1)); nowTrusted.insert(calcNodeID(extraKey2)); @@ -1776,11 +1776,11 @@ class NegativeUNLVoteNewValidator_test : public beast::unit_test::Suite return true; }); BEAST_EXPECT( - voteAndCheck(history, history.UNLNodeIDs[0], 1, [&](NegativeUNLVote& vote) { + voteAndCheck(history, history.unlNodeIDs[0], 1, [&](NegativeUNLVote& vote) { auto extraKey1 = randomKeyPair(KeyType::Ed25519).first; auto extraKey2 = randomKeyPair(KeyType::Ed25519).first; - history.UNLKeySet.insert(extraKey1); - history.UNLKeySet.insert(extraKey2); + history.unlKeySet.insert(extraKey1); + history.unlKeySet.insert(extraKey2); hash_set nowTrusted; nowTrusted.insert(calcNodeID(extraKey1)); nowTrusted.insert(calcNodeID(extraKey2)); @@ -1807,7 +1807,7 @@ class NegativeUNLVoteFilterValidations_test : public beast::unit_test::Suite auto l = std::make_shared( kCreateGenesis, Rules{env.app().config().features}, - env.app().config().FEES.toFees(), + env.app().config().fees.toFees(), std::vector{}, env.app().getNodeFamily()); diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index fb453b0791..606c6f2824 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -324,15 +324,15 @@ class Validations_test : public beast::unit_test::Suite BEAST_EXPECT( ValStatus::Stale == - harness.add(n.validate(ledgerA, -harness.parms().validationCURRENT_EARLY, 0s))); + harness.add(n.validate(ledgerA, -harness.parms().validationCurrentEarly, 0s))); BEAST_EXPECT( ValStatus::Stale == - harness.add(n.validate(ledgerA, harness.parms().validationCURRENT_WALL, 0s))); + harness.add(n.validate(ledgerA, harness.parms().validationCurrentWall, 0s))); BEAST_EXPECT( ValStatus::Stale == - harness.add(n.validate(ledgerA, 0s, harness.parms().validationCURRENT_LOCAL))); + harness.add(n.validate(ledgerA, 0s, harness.parms().validationCurrentLocal))); } { @@ -357,7 +357,7 @@ class Validations_test : public beast::unit_test::Suite // If we advance far enough for AB to expire, we can fully // validate or partially validate that sequence number again BEAST_EXPECT(ValStatus::Conflicting == process(ledgerAZ)); - harness.clock().advance(harness.parms().validationSET_EXPIRES + 1ms); + harness.clock().advance(harness.parms().validationSetExpires + 1ms); BEAST_EXPECT(ValStatus::Current == process(ledgerAZ)); } } @@ -392,7 +392,7 @@ class Validations_test : public beast::unit_test::Suite BEAST_EXPECT( harness.vals().getPreferred(genesisLedger_) == std::make_pair(ledgerAB.seq(), ledgerAB.id())); - harness.clock().advance(harness.parms().validationCURRENT_LOCAL); + harness.clock().advance(harness.parms().validationCurrentLocal); // trigger check for stale trigger(harness.vals()); @@ -487,7 +487,7 @@ class Validations_test : public beast::unit_test::Suite BEAST_EXPECT(harness.vals().currentTrusted()[0].seq() == ledgerAC.seq()); // Pass enough time for it to go stale - harness.clock().advance(harness.parms().validationCURRENT_LOCAL); + harness.clock().advance(harness.parms().validationCurrentLocal); BEAST_EXPECT(harness.vals().currentTrusted().empty()); } @@ -528,7 +528,7 @@ class Validations_test : public beast::unit_test::Suite } // Pass enough time for them to go stale - harness.clock().advance(harness.parms().validationCURRENT_LOCAL); + harness.clock().advance(harness.parms().validationCurrentLocal); BEAST_EXPECT(harness.vals().getCurrentNodeIDs().empty()); } @@ -657,7 +657,7 @@ class Validations_test : public beast::unit_test::Suite BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 1); harness.vals().expire(j); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 1); - harness.clock().advance(harness.parms().validationSET_EXPIRES); + harness.clock().advance(harness.parms().validationSetExpires); harness.vals().expire(j); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerA.id()) == 0); @@ -666,14 +666,14 @@ class Validations_test : public beast::unit_test::Suite BEAST_EXPECT(ValStatus::Current == harness.add(a.validate(ledgerB))); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerB.id()) == 1); harness.vals().setSeqToKeep(ledgerB.seq(), ledgerB.seq() + kOne); - harness.clock().advance(harness.parms().validationSET_EXPIRES); + harness.clock().advance(harness.parms().validationSetExpires); harness.vals().expire(j); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerB.id()) == 1); // change toKeep harness.vals().setSeqToKeep(ledgerB.seq() + kOne, ledgerB.seq() + kTwo); // advance clock slowly int const loops = - harness.parms().validationSET_EXPIRES / harness.parms().validationFRESHNESS + 1; + harness.parms().validationSetExpires / harness.parms().validationFRESHNESS + 1; for (int i = 0; i < loops; ++i) { harness.clock().advance(harness.parms().validationFRESHNESS); @@ -686,7 +686,7 @@ class Validations_test : public beast::unit_test::Suite BEAST_EXPECT(ValStatus::Current == harness.add(a.validate(ledgerC))); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerC.id()) == 1); harness.vals().setSeqToKeep(ledgerC.seq() - kOne, ledgerC.seq()); - harness.clock().advance(harness.parms().validationSET_EXPIRES); + harness.clock().advance(harness.parms().validationSetExpires); harness.vals().expire(j); BEAST_EXPECT(harness.vals().numTrustedForLedger(ledgerC.id()) == 0); } @@ -934,7 +934,7 @@ class Validations_test : public beast::unit_test::Suite BEAST_EXPECT(enforcer(clock.now(), Seq{10}, p)); BEAST_EXPECT(!enforcer(clock.now(), Seq{5}, p)); BEAST_EXPECT(!enforcer(clock.now(), Seq{9}, p)); - clock.advance(p.validationSET_EXPIRES - 1ms); + clock.advance(p.validationSetExpires - 1ms); BEAST_EXPECT(!enforcer(clock.now(), Seq{1}, p)); clock.advance(2ms); BEAST_EXPECT(enforcer(clock.now(), Seq{1}, p)); diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index 3e6495bc2f..ce6774827e 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -550,7 +550,7 @@ main } BEAST_EXPECT(error.empty()); - BEAST_EXPECT(c.NETWORK_ID == 0); + BEAST_EXPECT(c.networkId == 0); try { @@ -563,7 +563,7 @@ main } BEAST_EXPECT(error.empty()); - BEAST_EXPECT(c.NETWORK_ID == 0); + BEAST_EXPECT(c.networkId == 0); try { @@ -578,7 +578,7 @@ main } BEAST_EXPECT(error.empty()); - BEAST_EXPECT(c.NETWORK_ID == 255); + BEAST_EXPECT(c.networkId == 255); try { @@ -593,7 +593,7 @@ main } BEAST_EXPECT(error.empty()); - BEAST_EXPECT(c.NETWORK_ID == 10000); + BEAST_EXPECT(c.networkId == 10000); } void @@ -655,7 +655,7 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8 c.loadFromString(toLoad); BEAST_EXPECT(c.legacy("validators_file").empty()); BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 5); - BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == std::nullopt); + BEAST_EXPECT(c.validatorListThreshold == std::nullopt); } { // load validator list sites and keys from config @@ -685,7 +685,7 @@ trust-these-validators.gov "E566"); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values()[0] == "1"); - BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == std::size_t(1)); + BEAST_EXPECT(c.validatorListThreshold == std::size_t(1)); } { // load validator list sites and keys from config @@ -715,7 +715,7 @@ trust-these-validators.gov "E566"); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values()[0] == "0"); - BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == std::nullopt); + BEAST_EXPECT(c.validatorListThreshold == std::nullopt); } { // load should throw if [validator_list_threshold] is greater than @@ -836,7 +836,7 @@ trust-these-validators.gov BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); - BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2); + BEAST_EXPECT(c.validatorListThreshold == 2); } { // load from specified [validators_file] file name @@ -853,7 +853,7 @@ trust-these-validators.gov BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); - BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2); + BEAST_EXPECT(c.validatorListThreshold == 2); } { // load from specified [validators_file] relative path @@ -870,7 +870,7 @@ trust-these-validators.gov BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); - BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2); + BEAST_EXPECT(c.validatorListThreshold == 2); } { // load from validators file in default location @@ -885,7 +885,7 @@ trust-these-validators.gov BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); - BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2); + BEAST_EXPECT(c.validatorListThreshold == 2); } { // load from specified [validators_file] instead @@ -904,7 +904,7 @@ trust-these-validators.gov BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); - BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2); + BEAST_EXPECT(c.validatorListThreshold == 2); } { @@ -940,7 +940,7 @@ trust-these-validators.gov BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 4); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 3); BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); - BEAST_EXPECT(c.VALIDATOR_LIST_THRESHOLD == 2); + BEAST_EXPECT(c.validatorListThreshold == 2); } { // load should throw if [validator_list_threshold] is present both @@ -1017,7 +1017,7 @@ trust-these-validators.gov BEAST_EXPECT(!config.quiet()); BEAST_EXPECT(!config.silent()); BEAST_EXPECT(!config.standalone()); - BEAST_EXPECT(config.LEDGER_HISTORY == 256); + BEAST_EXPECT(config.ledgerHistory == 256); BEAST_EXPECT(!config.legacy("database_path").empty()); } { @@ -1030,7 +1030,7 @@ trust-these-validators.gov BEAST_EXPECT(config.quiet()); BEAST_EXPECT(!config.silent()); BEAST_EXPECT(!config.standalone()); - BEAST_EXPECT(config.LEDGER_HISTORY == 256); + BEAST_EXPECT(config.ledgerHistory == 256); BEAST_EXPECT(!config.legacy("database_path").empty()); } { @@ -1043,7 +1043,7 @@ trust-these-validators.gov BEAST_EXPECT(config.quiet()); BEAST_EXPECT(config.silent()); BEAST_EXPECT(!config.standalone()); - BEAST_EXPECT(config.LEDGER_HISTORY == 256); + BEAST_EXPECT(config.ledgerHistory == 256); BEAST_EXPECT(!config.legacy("database_path").empty()); } { @@ -1056,7 +1056,7 @@ trust-these-validators.gov BEAST_EXPECT(config.quiet()); BEAST_EXPECT(config.silent()); BEAST_EXPECT(!config.standalone()); - BEAST_EXPECT(config.LEDGER_HISTORY == 256); + BEAST_EXPECT(config.ledgerHistory == 256); BEAST_EXPECT(!config.legacy("database_path").empty()); } { @@ -1069,7 +1069,7 @@ trust-these-validators.gov BEAST_EXPECT(!config.quiet()); BEAST_EXPECT(!config.silent()); BEAST_EXPECT(config.standalone()); - BEAST_EXPECT(config.LEDGER_HISTORY == 0); + BEAST_EXPECT(config.ledgerHistory == 0); BEAST_EXPECT(config.legacy("database_path").empty() == !explicitPath); } { @@ -1082,7 +1082,7 @@ trust-these-validators.gov BEAST_EXPECT(config.quiet()); BEAST_EXPECT(!config.silent()); BEAST_EXPECT(config.standalone()); - BEAST_EXPECT(config.LEDGER_HISTORY == 0); + BEAST_EXPECT(config.ledgerHistory == 0); BEAST_EXPECT(config.legacy("database_path").empty() == !explicitPath); } { @@ -1095,7 +1095,7 @@ trust-these-validators.gov BEAST_EXPECT(config.quiet()); BEAST_EXPECT(config.silent()); BEAST_EXPECT(config.standalone()); - BEAST_EXPECT(config.LEDGER_HISTORY == 0); + BEAST_EXPECT(config.ledgerHistory == 0); BEAST_EXPECT(config.legacy("database_path").empty() == !explicitPath); } { @@ -1108,7 +1108,7 @@ trust-these-validators.gov BEAST_EXPECT(config.quiet()); BEAST_EXPECT(config.silent()); BEAST_EXPECT(config.standalone()); - BEAST_EXPECT(config.LEDGER_HISTORY == 0); + BEAST_EXPECT(config.ledgerHistory == 0); BEAST_EXPECT(config.legacy("database_path").empty() == !explicitPath); } } @@ -1125,11 +1125,11 @@ trust-these-validators.gov ParsedPort rpc; if (!unexcept([&]() { parsePort(rpc, conf["port_rpc"], log); })) return; - BEAST_EXPECT(rpc.admin_nets_v4.size() + rpc.admin_nets_v6.size() == 2); + BEAST_EXPECT(rpc.adminNetsV4.size() + rpc.adminNetsV6.size() == 2); ParsedPort wss; if (!unexcept([&]() { parsePort(wss, conf["port_wss_admin"], log); })) return; - BEAST_EXPECT(wss.admin_nets_v4.size() + wss.admin_nets_v6.size() == 1); + BEAST_EXPECT(wss.adminNetsV4.size() + wss.adminNetsV6.size() == 1); } void @@ -1245,25 +1245,25 @@ r.ripple.com:51235 BEAST_EXPECT( cfg.exists(SECTION_IPS_FIXED) && cfg.section(SECTION_IPS_FIXED).lines().size() == 15 && cfg.section(SECTION_IPS_FIXED).values().size() == 15); - BEAST_EXPECT(cfg.IPS[0] == "r.ripple.com 51235"); + BEAST_EXPECT(cfg.ips[0] == "r.ripple.com 51235"); - BEAST_EXPECT(cfg.IPS_FIXED[0] == "s1.ripple.com 51235"); - BEAST_EXPECT(cfg.IPS_FIXED[1] == "s2.ripple.com 51235"); - BEAST_EXPECT(cfg.IPS_FIXED[2] == "anotherserversansport"); - BEAST_EXPECT(cfg.IPS_FIXED[3] == "anotherserverwithport 12"); - BEAST_EXPECT(cfg.IPS_FIXED[4] == "1.1.1.1 1"); - BEAST_EXPECT(cfg.IPS_FIXED[5] == "1.1.1.1 1"); - BEAST_EXPECT(cfg.IPS_FIXED[6] == "12.34.12.123 12345"); - BEAST_EXPECT(cfg.IPS_FIXED[7] == "12.34.12.123 12345"); + BEAST_EXPECT(cfg.ipsFixed[0] == "s1.ripple.com 51235"); + BEAST_EXPECT(cfg.ipsFixed[1] == "s2.ripple.com 51235"); + BEAST_EXPECT(cfg.ipsFixed[2] == "anotherserversansport"); + BEAST_EXPECT(cfg.ipsFixed[3] == "anotherserverwithport 12"); + BEAST_EXPECT(cfg.ipsFixed[4] == "1.1.1.1 1"); + BEAST_EXPECT(cfg.ipsFixed[5] == "1.1.1.1 1"); + BEAST_EXPECT(cfg.ipsFixed[6] == "12.34.12.123 12345"); + BEAST_EXPECT(cfg.ipsFixed[7] == "12.34.12.123 12345"); // all ipv6 should be ignored by colon replacer, howsoever formatted - BEAST_EXPECT(cfg.IPS_FIXED[8] == "::"); - BEAST_EXPECT(cfg.IPS_FIXED[9] == "2001:db8::"); - BEAST_EXPECT(cfg.IPS_FIXED[10] == "::1"); - BEAST_EXPECT(cfg.IPS_FIXED[11] == "::1:12345"); - BEAST_EXPECT(cfg.IPS_FIXED[12] == "[::1]:12345"); - BEAST_EXPECT(cfg.IPS_FIXED[13] == "2001:db8:3333:4444:5555:6666:7777:8888:12345"); - BEAST_EXPECT(cfg.IPS_FIXED[14] == "[2001:db8:3333:4444:5555:6666:7777:8888]:1"); + BEAST_EXPECT(cfg.ipsFixed[8] == "::"); + BEAST_EXPECT(cfg.ipsFixed[9] == "2001:db8::"); + BEAST_EXPECT(cfg.ipsFixed[10] == "::1"); + BEAST_EXPECT(cfg.ipsFixed[11] == "::1:12345"); + BEAST_EXPECT(cfg.ipsFixed[12] == "[::1]:12345"); + BEAST_EXPECT(cfg.ipsFixed[13] == "2001:db8:3333:4444:5555:6666:7777:8888:12345"); + BEAST_EXPECT(cfg.ipsFixed[14] == "[2001:db8:3333:4444:5555:6666:7777:8888]:1"); } void @@ -1274,51 +1274,51 @@ r.ripple.com:51235 std::string_view line; std::string_view field; std::string_view expect; - bool had_comment; + bool hadComment; }; std::array const tests = { {{.line = "password = aaaa\\#bbbb", .field = "password", .expect = "aaaa#bbbb", - .had_comment = false}, + .hadComment = false}, {.line = "password = aaaa#bbbb", .field = "password", .expect = "aaaa", - .had_comment = true}, + .hadComment = true}, {.line = "password = aaaa #bbbb", .field = "password", .expect = "aaaa", - .had_comment = true}, + .hadComment = true}, // since the value is all comment, this doesn't parse as k=v : {.line = "password = #aaaa #bbbb", .field = "", .expect = "password =", - .had_comment = true}, + .hadComment = true}, {.line = "password = aaaa\\# #bbbb", .field = "password", .expect = "aaaa#", - .had_comment = true}, + .hadComment = true}, {.line = "password = aaaa\\##bbbb", .field = "password", .expect = "aaaa#", - .had_comment = true}, - {.line = "aaaa#bbbb", .field = "", .expect = "aaaa", .had_comment = true}, - {.line = "aaaa\\#bbbb", .field = "", .expect = "aaaa#bbbb", .had_comment = false}, - {.line = "aaaa\\##bbbb", .field = "", .expect = "aaaa#", .had_comment = true}, - {.line = "aaaa #bbbb", .field = "", .expect = "aaaa", .had_comment = true}, - {.line = "1 #comment", .field = "", .expect = "1", .had_comment = true}, - {.line = "#whole thing is comment", .field = "", .expect = "", .had_comment = false}, + .hadComment = true}, + {.line = "aaaa#bbbb", .field = "", .expect = "aaaa", .hadComment = true}, + {.line = "aaaa\\#bbbb", .field = "", .expect = "aaaa#bbbb", .hadComment = false}, + {.line = "aaaa\\##bbbb", .field = "", .expect = "aaaa#", .hadComment = true}, + {.line = "aaaa #bbbb", .field = "", .expect = "aaaa", .hadComment = true}, + {.line = "1 #comment", .field = "", .expect = "1", .hadComment = true}, + {.line = "#whole thing is comment", .field = "", .expect = "", .hadComment = false}, {.line = " #whole comment with space", .field = "", .expect = "", - .had_comment = false}}}; + .hadComment = false}}}; for (auto const& t : tests) { Section s; s.append(std::string(t.line)); - BEAST_EXPECT(s.hadTrailingComments() == t.had_comment); + BEAST_EXPECT(s.hadTrailingComments() == t.hadComment); if (t.field.empty()) { BEAST_EXPECTS(s.legacy() == t.expect, s.legacy()); @@ -1481,7 +1481,7 @@ r.ripple.com:51235 c.loadFromString(toLoad); if (shouldPass) { - BEAST_EXPECT(c.AMENDMENT_MAJORITY_TIME.count() == val * sec); + BEAST_EXPECT(c.amendmentMajorityTime.count() == val * sec); } else { @@ -1512,7 +1512,7 @@ r.ripple.com:51235 { Config c; c.loadFromString("[overlay]\nmax_unknown_time=" + value); - return c.MAX_UNKNOWN_TIME; + return c.maxUnknownTime; } catch (std::runtime_error const&) { @@ -1546,7 +1546,7 @@ r.ripple.com:51235 { Config c; c.loadFromString("[overlay]\nmax_diverged_time=" + value); - return c.MAX_DIVERGED_TIME; + return c.maxDivergedTime; } catch (std::runtime_error const&) { diff --git a/src/test/core/Coroutine_test.cpp b/src/test/core/Coroutine_test.cpp index 103191ec59..bf1d98c779 100644 --- a/src/test/core/Coroutine_test.cpp +++ b/src/test/core/Coroutine_test.cpp @@ -58,7 +58,7 @@ public: testcase("correct order"); Env env(*this, envconfig([](std::unique_ptr cfg) { - cfg->FORCE_MULTI_THREAD = true; + cfg->forceMultiThread = true; return cfg; })); @@ -85,7 +85,7 @@ public: testcase("incorrect order"); Env env(*this, envconfig([](std::unique_ptr cfg) { - cfg->FORCE_MULTI_THREAD = true; + cfg->forceMultiThread = true; return cfg; })); diff --git a/src/test/csf/Scheduler.h b/src/test/csf/Scheduler.h index c70b7d4854..ede43be854 100644 --- a/src/test/csf/Scheduler.h +++ b/src/test/csf/Scheduler.h @@ -91,7 +91,7 @@ private: make_multiset>::type; // alloc_ is owned by the scheduler boost::container::pmr::monotonic_buffer_resource* alloc_; - by_when_set by_when_; + by_when_set byWhen_; public: using iterator = typename by_when_set::iterator; @@ -258,7 +258,7 @@ inline Scheduler::QueueType::QueueType(boost::container::pmr::monotonic_buffer_r inline Scheduler::QueueType::~QueueType() { - for (auto iter = by_when_.begin(); iter != by_when_.end();) + for (auto iter = byWhen_.begin(); iter != byWhen_.end();) { auto e = &*iter; ++iter; @@ -270,19 +270,19 @@ inline Scheduler::QueueType::~QueueType() inline bool Scheduler::QueueType::empty() const { - return by_when_.empty(); + return byWhen_.empty(); } inline auto Scheduler::QueueType::begin() -> iterator { - return by_when_.begin(); + return byWhen_.begin(); } inline auto Scheduler::QueueType::end() -> iterator { - return by_when_.end(); + return byWhen_.end(); } template @@ -292,14 +292,14 @@ Scheduler::QueueType::emplace(time_point when, Handler&& h) -> typename by_when_ using event_type = EventImpl>; auto const p = alloc_->allocate(sizeof(event_type)); auto& e = *new (p) event_type(when, std::forward(h)); - return by_when_.insert(e); + return byWhen_.insert(e); } inline auto Scheduler::QueueType::erase(iterator iter) -> typename by_when_set::iterator { auto& e = *iter; - auto next = by_when_.erase(iter); + auto next = byWhen_.erase(iter); e.~Event(); alloc_->deallocate(&e, sizeof(e)); return next; diff --git a/src/test/jtx/impl/AMMTest.cpp b/src/test/jtx/impl/AMMTest.cpp index 5801e8fb44..46c5e44bd3 100644 --- a/src/test/jtx/impl/AMMTest.cpp +++ b/src/test/jtx/impl/AMMTest.cpp @@ -213,9 +213,9 @@ AMMTest::pathTestEnv() // different from the current defaults. This function creates an env // with the search parameters that the tests were written for. return Env(*this, envconfig([](std::unique_ptr cfg) { - cfg->PATH_SEARCH_OLD = 7; - cfg->PATH_SEARCH = 7; - cfg->PATH_SEARCH_MAX = 10; + cfg->pathSearchOld = 7; + cfg->pathSearch = 7; + cfg->pathSearchMax = 10; return cfg; })); } diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index 1cd4dd1029..4b6955bafb 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -91,7 +91,7 @@ Env::AppBundle::AppBundle( timeKeeper = tk.get(); // Hack so we don't have to call Config::setup HTTPClient::initializeSSLContext( - config->SSL_VERIFY_DIR, config->SSL_VERIFY_FILE, config->SSL_VERIFY, debugLog()); + config->sslVerifyDir, config->sslVerifyFile, config->sslVerify, debugLog()); owned = makeApplication(std::move(config), std::move(logs), std::move(tk)); app = owned.get(); app->getLogs().threshold(thresh); diff --git a/src/test/jtx/impl/JSONRPCClient.cpp b/src/test/jtx/impl/JSONRPCClient.cpp index c8e6326ed2..cf81bfab0c 100644 --- a/src/test/jtx/impl/JSONRPCClient.cpp +++ b/src/test/jtx/impl/JSONRPCClient.cpp @@ -81,11 +81,11 @@ class JSONRPCClient : public AbstractClient boost::asio::ip::tcp::socket stream_; boost::beast::multi_buffer bin_; boost::beast::multi_buffer bout_; - unsigned rpc_version_; + unsigned rpcVersion_; public: explicit JSONRPCClient(Config const& cfg, unsigned rpcVersion) - : ep_(getEndpoint(cfg)), stream_(ios_), rpc_version_(rpcVersion) + : ep_(getEndpoint(cfg)), stream_(ios_), rpcVersion_(rpcVersion) { stream_.connect(ep_); } @@ -116,7 +116,7 @@ public: { json::Value jr; jr[jss::method] = cmd; - if (rpc_version_ == 2) + if (rpcVersion_ == 2) { jr[jss::jsonrpc] = "2.0"; jr[jss::ripplerpc] = "2.0"; @@ -148,7 +148,7 @@ public: [[nodiscard]] unsigned version() const override { - return rpc_version_; + return rpcVersion_; } }; diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index e9ea48fb39..c784c074de 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -172,9 +172,9 @@ pathTestEnv(beast::unit_test::Suite& suite) // with the search parameters that the tests were written for. using namespace jtx; return Env(suite, envconfig([](std::unique_ptr cfg) { - cfg->PATH_SEARCH_OLD = 7; - cfg->PATH_SEARCH = 7; - cfg->PATH_SEARCH_MAX = 10; + cfg->pathSearchOld = 7; + cfg->pathSearch = 7; + cfg->pathSearchMax = 10; return cfg; })); } diff --git a/src/test/jtx/impl/WSClient.cpp b/src/test/jtx/impl/WSClient.cpp index ba09647ce7..551fd1404b 100644 --- a/src/test/jtx/impl/WSClient.cpp +++ b/src/test/jtx/impl/WSClient.cpp @@ -120,7 +120,7 @@ class WSClientImpl : public WSClient std::condition_variable cv_; std::list> msgs_; - unsigned rpc_version_; + unsigned rpcVersion_; void cleanup() @@ -157,7 +157,7 @@ public: , thread_([&] { ios_.run(); }) , stream_(ios_) , ws_(stream_) - , rpc_version_(rpcVersion) + , rpcVersion_(rpcVersion) { try { @@ -197,7 +197,7 @@ public: json::Value jp; if (params) jp = params; - if (rpc_version_ == 2) + if (rpcVersion_ == 2) { jp[jss::method] = cmd; jp[jss::jsonrpc] = "2.0"; @@ -284,7 +284,7 @@ public: [[nodiscard]] unsigned version() const override { - return rpc_version_; + return rpcVersion_; } private: diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index 648f8e4460..56197e1078 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -20,12 +20,12 @@ setupConfigForUnitTests(Config& cfg) using namespace jtx; // Default fees to old values, so tests don't have to worry about changes in // Config.h - cfg.FEES.reference_fee = UNIT_TEST_REFERENCE_FEE; - cfg.FEES.account_reserve = XRP(200).value().xrp().drops(); - cfg.FEES.owner_reserve = XRP(50).value().xrp().drops(); + cfg.fees.referenceFee = UNIT_TEST_REFERENCE_FEE; + cfg.fees.accountReserve = XRP(200).value().xrp().drops(); + cfg.fees.ownerReserve = XRP(50).value().xrp().drops(); // The Beta API (currently v2) is always available to tests - cfg.BETA_RPC_API = true; + cfg.betaRpcApi = true; cfg.overwrite(ConfigSection::nodeDatabase(), "type", "memory"); cfg.overwrite(ConfigSection::nodeDatabase(), "path", "main"); @@ -54,7 +54,7 @@ setupConfigForUnitTests(Config& cfg) cfg[PORT_WS].set("admin", getEnvLocalhostAddr()); cfg[PORT_WS].set("port", "0"); cfg[PORT_WS].set("protocol", "ws"); - cfg.SSL_VERIFY = false; + cfg.sslVerify = false; } namespace jtx { @@ -96,7 +96,7 @@ secureGatewayLocalnet(std::unique_ptr cfg) std::unique_ptr singleThreadIo(std::unique_ptr cfg) { - cfg->IO_WORKERS = 1; + cfg->ioWorkers = 1; return cfg; } diff --git a/src/test/jtx/impl/permissioned_dex.cpp b/src/test/jtx/impl/permissioned_dex.cpp index 012932fed5..5c059e9e80 100644 --- a/src/test/jtx/impl/permissioned_dex.cpp +++ b/src/test/jtx/impl/permissioned_dex.cpp @@ -48,7 +48,7 @@ PermissionedDEX::PermissionedDEX(Env& env) , alice("permdex-alice") , bob("permdex-bob") , carol("permdex-carol") - , USD(gw["USD"]) + , usd(gw["USD"]) , credType("permdex-abcde") { // Fund accounts @@ -59,10 +59,10 @@ PermissionedDEX::PermissionedDEX(Env& env) for (auto const& account : {alice, bob, carol, domainOwner}) { - env.trust(USD(1000), account); + env.trust(usd(1000), account); env.close(); - env(pay(gw, account, USD(100))); + env(pay(gw, account, usd(100))); env.close(); } } diff --git a/src/test/jtx/impl/xchain_bridge.cpp b/src/test/jtx/impl/xchain_bridge.cpp index a88535a51e..24669e6d4f 100644 --- a/src/test/jtx/impl/xchain_bridge.cpp +++ b/src/test/jtx/impl/xchain_bridge.cpp @@ -396,7 +396,7 @@ XChainBridgeObjects::XChainBridgeObjects() } return result; }()) - , alt_signers([] { + , altSigners([] { static constexpr int kNumSigners = kUtXchainDefaultNumSigners; std::vector result; result.reserve(kNumSigners); @@ -431,17 +431,16 @@ XChainBridgeObjects::XChainBridgeObjects() return r; }()) , reward(XRP(1)) - , split_reward_quorum(divide(reward, STAmount(kUtXchainDefaultQuorum), reward.get())) - , split_reward_everyone( - divide(reward, STAmount(kUtXchainDefaultNumSigners), reward.get())) - , tiny_reward(drops(37)) - , tiny_reward_split( - (divide(tiny_reward, STAmount(kUtXchainDefaultQuorum), tiny_reward.get()))) - , tiny_reward_remainder( - tiny_reward - - multiply(tiny_reward_split, STAmount(kUtXchainDefaultQuorum), tiny_reward.get())) - , one_xrp(XRP(1)) - , xrp_dust(divide(one_xrp, STAmount(10000), one_xrp.get())) + , splitRewardQuorum(divide(reward, STAmount(kUtXchainDefaultQuorum), reward.get())) + , splitRewardEveryone(divide(reward, STAmount(kUtXchainDefaultNumSigners), reward.get())) + , tinyReward(drops(37)) + , tinyRewardSplit( + (divide(tinyReward, STAmount(kUtXchainDefaultQuorum), tinyReward.get()))) + , tinyRewardRemainder( + tinyReward - + multiply(tinyRewardSplit, STAmount(kUtXchainDefaultQuorum), tinyReward.get())) + , oneXrp(XRP(1)) + , xrpDust(divide(oneXrp, STAmount(10000), oneXrp.get())) { } diff --git a/src/test/jtx/permissioned_dex.h b/src/test/jtx/permissioned_dex.h index 025097116f..b0a4d4e2d5 100644 --- a/src/test/jtx/permissioned_dex.h +++ b/src/test/jtx/permissioned_dex.h @@ -20,7 +20,7 @@ public: Account alice; Account bob; Account carol; - IOU USD; + IOU usd; uint256 domainID; std::string credType; diff --git a/src/test/jtx/xchain_bridge.h b/src/test/jtx/xchain_bridge.h index c20b6f2fc5..e4c012510d 100644 --- a/src/test/jtx/xchain_bridge.h +++ b/src/test/jtx/xchain_bridge.h @@ -165,21 +165,21 @@ struct XChainBridgeObjects FeatureBitset const features; std::vector const signers; - std::vector const alt_signers; + std::vector const altSigners; std::vector const payee; std::vector const payees; std::uint32_t const quorum{kUtXchainDefaultQuorum}; - STAmount const reward; // 1 xrp - STAmount const split_reward_quorum; // 250,000 drops - STAmount const split_reward_everyone; // 200,000 drops + STAmount const reward; // 1 xrp + STAmount const splitRewardQuorum; // 250,000 drops + STAmount const splitRewardEveryone; // 200,000 drops - STAmount const tiny_reward; // 37 drops - STAmount const tiny_reward_split; // 9 drops - STAmount const tiny_reward_remainder; // 1 drops + STAmount const tinyReward; // 37 drops + STAmount const tinyRewardSplit; // 9 drops + STAmount const tinyRewardRemainder; // 1 drops - STAmount const one_xrp; - STAmount const xrp_dust; + STAmount const oneXrp; + STAmount const xrpDust; static constexpr int kDropPerXrp = 1000000; diff --git a/src/test/ledger/SkipList_test.cpp b/src/test/ledger/SkipList_test.cpp index fe671ed128..fd3cc2f214 100644 --- a/src/test/ledger/SkipList_test.cpp +++ b/src/test/ledger/SkipList_test.cpp @@ -25,7 +25,7 @@ class SkipList_test : public beast::unit_test::Suite auto prev = std::make_shared( kCreateGenesis, Rules{config.features}, - config.FEES.toFees(), + config.fees.toFees(), std::vector{}, env.app().getNodeFamily()); history.push_back(prev); diff --git a/src/test/ledger/View_test.cpp b/src/test/ledger/View_test.cpp index 1714702c18..d1c7316588 100644 --- a/src/test/ledger/View_test.cpp +++ b/src/test/ledger/View_test.cpp @@ -153,7 +153,7 @@ class View_test : public beast::unit_test::Suite std::shared_ptr const genesis = std::make_shared( kCreateGenesis, Rules{config.features}, - config.FEES.toFees(), + config.fees.toFees(), std::vector{}, env.app().getNodeFamily()); auto const ledger = @@ -421,7 +421,7 @@ class View_test : public beast::unit_test::Suite std::shared_ptr const genesis = std::make_shared( kCreateGenesis, Rules{config.features}, - config.FEES.toFees(), + config.fees.toFees(), std::vector{}, env.app().getNodeFamily()); auto const ledger = @@ -630,7 +630,7 @@ class View_test : public beast::unit_test::Suite std::shared_ptr const genesis = std::make_shared( kCreateGenesis, Rules{config.features}, - config.FEES.toFees(), + config.fees.toFees(), std::vector{}, env.app().getNodeFamily()); auto const ledger = @@ -1020,7 +1020,7 @@ class View_test : public beast::unit_test::Suite std::shared_ptr const genesis = std::make_shared( kCreateGenesis, Rules{config.features}, - config.FEES.toFees(), + config.fees.toFees(), std::vector{}, env.app().getNodeFamily()); auto const ledger = diff --git a/src/test/nodestore/Database_test.cpp b/src/test/nodestore/Database_test.cpp index 744717659a..50bdfefd4c 100644 --- a/src/test/nodestore/Database_test.cpp +++ b/src/test/nodestore/Database_test.cpp @@ -74,7 +74,7 @@ public: auto& section = p->section("sqlite"); section.set("safety_level", "high"); } - p->LEDGER_HISTORY = 100'000'000; + p->ledgerHistory = 100'000'000; return Env( *this, @@ -103,7 +103,7 @@ public: auto& section = p->section("sqlite"); section.set("safety_level", "low"); } - p->LEDGER_HISTORY = 100'000'000; + p->ledgerHistory = 100'000'000; return Env( *this, @@ -166,7 +166,7 @@ public: section.set("synchronous", "extra"); section.set("temp_store", "default"); } - p->LEDGER_HISTORY = 50'000'000; + p->ledgerHistory = 50'000'000; return Env( *this, diff --git a/src/test/nodestore/Timing_test.cpp b/src/test/nodestore/Timing_test.cpp index d7540f0b3a..67feace198 100644 --- a/src/test/nodestore/Timing_test.cpp +++ b/src/test/nodestore/Timing_test.cpp @@ -84,16 +84,16 @@ private: beast::xor_shift_engine gen_; std::uint8_t prefix_; - std::discrete_distribution d_type_; - std::uniform_int_distribution d_size_; + std::discrete_distribution dType_; + std::uniform_int_distribution dSize_; public: explicit Sequence(std::uint8_t prefix) : prefix_(prefix) // uniform distribution over hotLEDGER - hotTRANSACTION_NODE // but exclude hotTRANSACTION = 2 (removed) - , d_type_({1, 1, 0, 1, 1}) - , d_size_(kMinSize, kMaxSize) + , dType_({1, 1, 0, 1, 1}) + , dSize_(kMinSize, kMaxSize) { } @@ -116,10 +116,10 @@ public: auto const data = static_cast(&*key.begin()); *data = prefix_; rngcpy(data + 1, key.size() - 1, gen_); - Blob value(d_size_(gen_)); + Blob value(dSize_(gen_)); rngcpy(&value[0], value.size(), gen_); return NodeObject::createObject( - safeCast(d_type_(gen_)), std::move(value), key); + safeCast(dType_(gen_)), std::move(value), key); } // returns a batch of NodeObjects starting at n @@ -140,11 +140,11 @@ class Timing_test : public beast::unit_test::Suite public: static constexpr auto kMissingNodePercent = 20; // percent of fetches for missing nodes - std::size_t const default_repeat = 3; + std::size_t const defaultRepeat = 3; #ifndef NDEBUG - std::size_t const default_items = 10000; + std::size_t const defaultItems = 10000; #else - std::size_t const default_items = 100000; // release + std::size_t const defaultItems = 100000; // release #endif using clock_type = std::chrono::steady_clock; @@ -639,7 +639,7 @@ public: { w = std::max::size_type>(w, test.first.size()); } - log << threads << " Thread" << (threads > 1 ? "s" : "") << ", " << default_items + log << threads << " Thread" << (threads > 1 ? "s" : "") << ", " << defaultItems << " Objects" << std::endl; { std::stringstream ss; @@ -655,9 +655,9 @@ public: for (auto const& configString : configStrings) { Params params{}; - params.items = default_items; + params.items = defaultItems; params.threads = threads; - for (auto i = default_repeat; (i--) != 0u;) + for (auto i = defaultRepeat; (i--) != 0u;) { beast::TempDir const tempDir; Section config = parse(configString); diff --git a/src/test/overlay/compression_test.cpp b/src/test/overlay/compression_test.cpp index d98ffa9b33..60cc69a14f 100644 --- a/src/test/overlay/compression_test.cpp +++ b/src/test/overlay/compression_test.cpp @@ -112,16 +112,16 @@ public: return; std::vector decompressed; - decompressed.resize(header->uncompressed_size); + decompressed.resize(header->uncompressedSize); - BEAST_EXPECT(header->payload_wire_size == buffer.size() - header->header_size); + BEAST_EXPECT(header->payloadWireSize == buffer.size() - header->headerSize); ZeroCopyInputStream stream(buffers.data()); - stream.Skip(header->header_size); + stream.Skip(header->headerSize); auto decompressedSize = xrpl::compression::decompress( - stream, header->payload_wire_size, decompressed.data(), header->uncompressed_size); - BEAST_EXPECT(decompressedSize == header->uncompressed_size); + stream, header->payloadWireSize, decompressed.data(), header->uncompressedSize); + BEAST_EXPECT(decompressedSize == header->uncompressedSize); auto const proto1 = std::make_shared(); BEAST_EXPECT(proto1->ParseFromArray(decompressed.data(), decompressedSize)); @@ -408,9 +408,8 @@ public: << enable << "\n"; c.loadFromString(str.str()); auto env = std::make_shared(*this); - env->app().config().COMPRESSION = c.COMPRESSION; - env->app().config().VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE = - c.VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE; + env->app().config().compression = c.compression; + env->app().config().vpReduceRelayBaseSquelchEnable = c.vpReduceRelayBaseSquelchEnable; return env; }; auto handshake = [&](int outboundEnable, int inboundEnable) { @@ -419,10 +418,10 @@ public: auto env = getEnv(outboundEnable); auto request = xrpl::makeRequest( true, - env->app().config().COMPRESSION, + env->app().config().compression, false, - env->app().config().TX_REDUCE_RELAY_ENABLE, - env->app().config().VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE); + env->app().config().txReduceRelayEnable, + env->app().config().vpReduceRelayBaseSquelchEnable); http_request_type httpRequest; httpRequest.version(request.version()); httpRequest.base() = request.base(); diff --git a/src/test/overlay/reduce_relay_test.cpp b/src/test/overlay/reduce_relay_test.cpp index a0afd01d2a..54d8f555a2 100644 --- a/src/test/overlay/reduce_relay_test.cpp +++ b/src/test/overlay/reduce_relay_test.cpp @@ -973,8 +973,7 @@ protected: auto countingState = network_.overlay().isCountingState(validator); BEAST_EXPECT( countingState == false && - selected.size() == - env_.app().config().VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS); + selected.size() == env_.app().config().vpReduceRelaySquelchMaxSelectedPeers); } // Trigger Link Down or Peer Disconnect event @@ -1163,7 +1162,7 @@ protected: { BEAST_EXPECT( squelched == - kMaxPeers - env_.app().config().VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS); + kMaxPeers - env_.app().config().vpReduceRelaySquelchMaxSelectedPeers); n++; } }, @@ -1172,8 +1171,7 @@ protected: purge, resetClock); auto selected = network_.overlay().getSelected(network_.validator(0)); - BEAST_EXPECT( - selected.size() == env_.app().config().VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS); + BEAST_EXPECT(selected.size() == env_.app().config().vpReduceRelaySquelchMaxSelectedPeers); BEAST_EXPECT(n == 1); // only one selection round auto res = checkCounting(network_.validator(0), false); BEAST_EXPECT(res); @@ -1234,7 +1232,7 @@ protected: id, [&](PublicKey const& key, PeerWPtr const& peer) { unsquelched++; }); BEAST_EXPECT( unsquelched == - kMaxPeers - env_.app().config().VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS); + kMaxPeers - env_.app().config().vpReduceRelaySquelchMaxSelectedPeers); BEAST_EXPECT(checkCounting(network_.validator(0), true)); }); } @@ -1254,7 +1252,7 @@ protected: auto peers = network_.overlay().getPeers(network_.validator(0)); BEAST_EXPECT( unsquelched == - kMaxPeers - env_.app().config().VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS); + kMaxPeers - env_.app().config().vpReduceRelaySquelchMaxSelectedPeers); BEAST_EXPECT(checkCounting(network_.validator(0), true)); }); } @@ -1293,7 +1291,7 @@ vp_enable=1 )xrpldConfig"); c.loadFromString(toLoad); - BEAST_EXPECT(c.VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE == true); + BEAST_EXPECT(c.vpReduceRelayBaseSquelchEnable == true); }); doTest("Test Config - squelch disabled (legacy)", log, [&](bool log) { @@ -1305,7 +1303,7 @@ vp_enable=0 )xrpldConfig"); c.loadFromString(toLoad); - BEAST_EXPECT(c.VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE == false); + BEAST_EXPECT(c.vpReduceRelayBaseSquelchEnable == false); Config c1; @@ -1314,7 +1312,7 @@ vp_enable=0 )xrpldConfig"; c1.loadFromString(toLoad); - BEAST_EXPECT(c1.VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE == false); + BEAST_EXPECT(c1.vpReduceRelayBaseSquelchEnable == false); }); doTest("Test Config - squelch enabled", log, [&](bool log) { @@ -1326,7 +1324,7 @@ vp_base_squelch_enable=1 )xrpldConfig"); c.loadFromString(toLoad); - BEAST_EXPECT(c.VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE == true); + BEAST_EXPECT(c.vpReduceRelayBaseSquelchEnable == true); }); doTest("Test Config - squelch disabled", log, [&](bool log) { @@ -1338,7 +1336,7 @@ vp_base_squelch_enable=0 )xrpldConfig"); c.loadFromString(toLoad); - BEAST_EXPECT(c.VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE == false); + BEAST_EXPECT(c.vpReduceRelayBaseSquelchEnable == false); }); doTest("Test Config - legacy and new", log, [&](bool log) { @@ -1378,7 +1376,7 @@ vp_enable=0 )xrpldConfig"); c.loadFromString(toLoad); - BEAST_EXPECT(c.VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS == 5); + BEAST_EXPECT(c.vpReduceRelaySquelchMaxSelectedPeers == 5); Config c1; @@ -1388,7 +1386,7 @@ vp_base_squelch_max_selected_peers=6 )xrpldConfig"; c1.loadFromString(toLoad); - BEAST_EXPECT(c1.VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS == 6); + BEAST_EXPECT(c1.vpReduceRelaySquelchMaxSelectedPeers == 6); Config c2; @@ -1421,7 +1419,7 @@ vp_base_squelch_max_selected_peers=2 doTest("BaseSquelchReady", log, [&](bool log) { ManualClock::reset(); auto createSlots = [&](bool baseSquelchEnabled) -> reduce_relay::Slots { - env_.app().config().VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE = baseSquelchEnabled; + env_.app().config().vpReduceRelayBaseSquelchEnable = baseSquelchEnabled; return reduce_relay::Slots( env_.app(), network_.overlay(), env_.app().config()); }; @@ -1577,10 +1575,10 @@ vp_base_squelch_max_selected_peers=2 << "[compression]\n" << "1\n"; c.loadFromString(str.str()); - env_.app().config().VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE = - c.VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE; + env_.app().config().vpReduceRelayBaseSquelchEnable = + c.vpReduceRelayBaseSquelchEnable; - env_.app().config().COMPRESSION = c.COMPRESSION; + env_.app().config().compression = c.compression; }; auto handshake = [&](int outboundEnable, int inboundEnable) { beast::IP::Address const addr = boost::asio::ip::make_address("172.1.1.100"); @@ -1588,10 +1586,10 @@ vp_base_squelch_max_selected_peers=2 setEnv(outboundEnable); auto request = xrpl::makeRequest( true, - env_.app().config().COMPRESSION, + env_.app().config().compression, false, - env_.app().config().TX_REDUCE_RELAY_ENABLE, - env_.app().config().VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE); + env_.app().config().txReduceRelayEnable, + env_.app().config().vpReduceRelayBaseSquelchEnable); http_request_type httpRequest; httpRequest.version(request.version()); httpRequest.base() = request.base(); @@ -1626,8 +1624,8 @@ vp_base_squelch_max_selected_peers=2 public: reduce_relay_test() : env_(*this, jtx::envconfig([](std::unique_ptr cfg) { - cfg->VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE = true; - cfg->VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS = 6; + cfg->vpReduceRelayBaseSquelchEnable = true; + cfg->vpReduceRelaySquelchMaxSelectedPeers = 6; return cfg; })) , network_(env_.app()) diff --git a/src/test/overlay/short_read_test.cpp b/src/test/overlay/short_read_test.cpp index 95ab5e8c20..9af632d65c 100644 --- a/src/test/overlay/short_read_test.cpp +++ b/src/test/overlay/short_read_test.cpp @@ -62,7 +62,7 @@ private: using endpoint_type = boost::asio::ip::tcp::endpoint; using address_type = boost::asio::ip::address; - io_context_type io_context_; + io_context_type ioContext_; boost::optional> work_; std::thread thread_; std::shared_ptr context_; @@ -185,10 +185,10 @@ private: , server(server) , test(server.test_) , acceptor( - test.io_context_, + test.ioContext_, endpoint_type(boost::asio::ip::make_address(test::getEnvLocalhostAddr()), 0)) - , socket(test.io_context_) - , strand(boost::asio::make_strand(test.io_context_)) + , socket(test.ioContext_) + , strand(boost::asio::make_strand(test.ioContext_)) { acceptor.listen(); server.endpoint_ = acceptor.local_endpoint(); @@ -261,8 +261,8 @@ private: , test(server.test_) , socket(std::move(inSocket)) , stream(socket, *test.context_) - , strand(boost::asio::make_strand(test.io_context_)) - , timer(test.io_context_) + , strand(boost::asio::make_strand(test.ioContext_)) + , timer(test.ioContext_) { } @@ -450,10 +450,10 @@ private: : Child(client) , client(client) , test(client.test_) - , socket(test.io_context_) + , socket(test.ioContext_) , stream(socket, *test.context_) - , strand(boost::asio::make_strand(test.io_context_)) - , timer(test.io_context_) + , strand(boost::asio::make_strand(test.ioContext_)) + , timer(test.ioContext_) , ep(ep) { } @@ -632,10 +632,10 @@ private: public: short_read_test() - : work_(io_context_.get_executor()) + : work_(ioContext_.get_executor()) , thread_(std::thread([this]() { beast::setCurrentThreadName("io_context"); - this->io_context_.run(); + this->ioContext_.run(); })) , context_(makeSslContext("")) { diff --git a/src/test/overlay/tx_reduce_relay_test.cpp b/src/test/overlay/tx_reduce_relay_test.cpp index e053774108..a0d91d3aed 100644 --- a/src/test/overlay/tx_reduce_relay_test.cpp +++ b/src/test/overlay/tx_reduce_relay_test.cpp @@ -81,10 +81,10 @@ private: { c.loadFromString(str.str()); - BEAST_EXPECT(c.TX_REDUCE_RELAY_ENABLE == enable); - BEAST_EXPECT(c.TX_REDUCE_RELAY_METRICS == metrics); - BEAST_EXPECT(c.TX_REDUCE_RELAY_MIN_PEERS == min); - BEAST_EXPECT(c.TX_RELAY_PERCENTAGE == pct); + BEAST_EXPECT(c.txReduceRelayEnable == enable); + BEAST_EXPECT(c.txReduceRelayMetrics == metrics); + BEAST_EXPECT(c.txReduceRelayMinPeers == min); + BEAST_EXPECT(c.txRelayPercentage == pct); if (success) { pass(); @@ -173,7 +173,7 @@ private: std::uint16_t rid_{1}; shared_context context_; ProtocolVersion protocolVersion_; - boost::beast::multi_buffer read_buf_; + boost::beast::multi_buffer readBuf_; public: tx_reduce_relay_test() : context_(makeSslContext("")), protocolVersion_{1, 7} @@ -232,9 +232,9 @@ private: testcase(test); jtx::Env env(*this); std::vector> peers; - env.app().config().TX_REDUCE_RELAY_ENABLE = txRREnabled; - env.app().config().TX_REDUCE_RELAY_MIN_PEERS = minPeers; - env.app().config().TX_RELAY_PERCENTAGE = relayPercentage; + env.app().config().txReduceRelayEnable = txRREnabled; + env.app().config().txReduceRelayMinPeers = minPeers; + env.app().config().txRelayPercentage = relayPercentage; PeerTest::init(); lid_ = 0; rid_ = 0; diff --git a/src/test/peerfinder/PeerFinder_test.cpp b/src/test/peerfinder/PeerFinder_test.cpp index 2e4ab001b4..c4f129c1a3 100644 --- a/src/test/peerfinder/PeerFinder_test.cpp +++ b/src/test/peerfinder/PeerFinder_test.cpp @@ -421,8 +421,8 @@ public: c.loadFromString(toLoad); BEAST_EXPECT( - (c.PEERS_MAX == max && c.PEERS_IN_MAX == 0 && c.PEERS_OUT_MAX == 0) || - (c.PEERS_IN_MAX == *maxIn && c.PEERS_OUT_MAX == *maxOut)); + (c.peersMax == max && c.peersInMax == 0 && c.peersOutMax == 0) || + (c.peersInMax == *maxIn && c.peersOutMax == *maxOut)); Config const config = Config::makeConfig(c, port, false, 0, true); diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index 5d32eac8dc..421f6bd1fa 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -131,7 +131,7 @@ class AccountTx_test : public beast::unit_test::Suite using namespace test::jtx; Env env(*this, envconfig([](std::unique_ptr cfg) { - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; return cfg; })); Account const a1{"A1"}; @@ -821,7 +821,7 @@ class AccountTx_test : public beast::unit_test::Suite using namespace std::chrono_literals; auto cfg = makeConfig(); - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; Env env(*this, std::move(cfg)); Account const alice{"alice"}; diff --git a/src/test/rpc/Book_test.cpp b/src/test/rpc/Book_test.cpp index bde55d4985..83f7b64b4b 100644 --- a/src/test/rpc/Book_test.cpp +++ b/src/test/rpc/Book_test.cpp @@ -1593,7 +1593,7 @@ public: auto const carol = permDex.carol; auto const domainID = permDex.domainID; auto const gw = permDex.gw; - auto const usd = permDex.USD; + auto const usd = permDex.usd; auto wsc = makeWSClient(env.app().config()); @@ -1718,7 +1718,7 @@ public: auto const carol = permDex.carol; auto const domainID = permDex.domainID; auto const gw = permDex.gw; - auto const usd = permDex.USD; + auto const usd = permDex.usd; auto wsc = makeWSClient(env.app().config()); diff --git a/src/test/rpc/DeliveredAmount_test.cpp b/src/test/rpc/DeliveredAmount_test.cpp index 7daf05aaf1..ca5df7102e 100644 --- a/src/test/rpc/DeliveredAmount_test.cpp +++ b/src/test/rpc/DeliveredAmount_test.cpp @@ -206,7 +206,7 @@ class DeliveredAmount_test : public beast::unit_test::Suite for (bool const afterSwitchTime : {true, false}) { auto cfg = envconfig(); - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; Env env(*this, std::move(cfg)); env.fund(XRP(10000), alice, bob, carol, gw); env.trust(usd(1000), alice, bob, carol); @@ -297,7 +297,7 @@ class DeliveredAmount_test : public beast::unit_test::Suite for (bool const afterSwitchTime : {true, false}) { auto cfg = envconfig(); - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; Env env(*this, std::move(cfg)); env.fund(XRP(10000), alice, bob, carol, gw); env.trust(usd(1000), alice, bob, carol); diff --git a/src/test/rpc/JSONRPC_test.cpp b/src/test/rpc/JSONRPC_test.cpp index 10e4daf933..1f24d229f2 100644 --- a/src/test/rpc/JSONRPC_test.cpp +++ b/src/test/rpc/JSONRPC_test.cpp @@ -2697,7 +2697,7 @@ public: testcase("autofill NetworkID"); using namespace test::jtx; Env env{*this, envconfig([&](std::unique_ptr cfg) { - cfg->NETWORK_ID = 1025; + cfg->networkId = 1025; return cfg; })}; @@ -2743,7 +2743,7 @@ public: // "c" (phantom signer) is rPcNzota6B8YBokhYtcTNqQVCngtbnWfux. Env env(*this, envconfig([](std::unique_ptr cfg) { - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; return cfg; })); env.fund(XRP(100000), a, ed, g); diff --git a/src/test/rpc/KeyGeneration_test.cpp b/src/test/rpc/KeyGeneration_test.cpp index bb1f95bef1..aafe6f75a5 100644 --- a/src/test/rpc/KeyGeneration_test.cpp +++ b/src/test/rpc/KeyGeneration_test.cpp @@ -19,15 +19,15 @@ namespace xrpl::RPC { struct KeyStrings { - char const* account_id; - char const* master_key; - char const* master_seed; - char const* master_seed_hex; - char const* public_key; - char const* public_key_hex; - char const* secret_key_hex; + char const* accountId; + char const* masterKey; + char const* masterSeed; + char const* masterSeedHex; + char const* publicKey; + char const* publicKeyHex; + char const* secretKeyHex; char const* passphrase; - char const* passphrase_warning; + char const* passphraseWarning; }; namespace common { @@ -38,45 +38,45 @@ static char const* gMasterSeedHex = "BE6A670A19B209E112146D0A7ED2AAD7"; } // namespace common static KeyStrings const kSecP256K1Strings = { - .account_id = "r4Vtj2jrfmTVZGfSP3gH9hQPMqFPQFin8f", - .master_key = common::gMasterKey, - .master_seed = common::gMasterSeed, - .master_seed_hex = common::gMasterSeedHex, - .public_key = "aBQxK2YFNqzmAaXNczYcjqDjfiKkLsJUizsr1UBf44RCF8FHdrmX", - .public_key_hex = "038AAE247B2344B1837FBED8F57389C8C11774510A3F7D784F2A09F0CB6843236C", - .secret_key_hex = "1949ECD889EA71324BC7A30C8E81F4E93CB73EE19D59E9082111E78CC3DDABC2", + .accountId = "r4Vtj2jrfmTVZGfSP3gH9hQPMqFPQFin8f", + .masterKey = common::gMasterKey, + .masterSeed = common::gMasterSeed, + .masterSeedHex = common::gMasterSeedHex, + .publicKey = "aBQxK2YFNqzmAaXNczYcjqDjfiKkLsJUizsr1UBf44RCF8FHdrmX", + .publicKeyHex = "038AAE247B2344B1837FBED8F57389C8C11774510A3F7D784F2A09F0CB6843236C", + .secretKeyHex = "1949ECD889EA71324BC7A30C8E81F4E93CB73EE19D59E9082111E78CC3DDABC2", .passphrase = common::gPassphrase, - .passphrase_warning = + .passphraseWarning = "This wallet was generated using a user-supplied " "passphrase that has low entropy and is vulnerable " "to brute-force attacks.", }; static KeyStrings const kED25519Strings = { - .account_id = "r4qV6xTXerqaZav3MJfSY79ynmc1BSBev1", - .master_key = common::gMasterKey, - .master_seed = common::gMasterSeed, - .master_seed_hex = common::gMasterSeedHex, - .public_key = "aKEQmgLMyZPMruJFejUuedp169LgW6DbJt1rej1DJ5hWUMH4pHJ7", - .public_key_hex = "ED54C3F5BEDA8BD588B203D23A27398FAD9D20F88A974007D6994659CD7273FE1D", - .secret_key_hex = "77AAED2698D56D6676323629160F4EEF21CFD9EE3D0745CC78FA291461F98278", + .accountId = "r4qV6xTXerqaZav3MJfSY79ynmc1BSBev1", + .masterKey = common::gMasterKey, + .masterSeed = common::gMasterSeed, + .masterSeedHex = common::gMasterSeedHex, + .publicKey = "aKEQmgLMyZPMruJFejUuedp169LgW6DbJt1rej1DJ5hWUMH4pHJ7", + .publicKeyHex = "ED54C3F5BEDA8BD588B203D23A27398FAD9D20F88A974007D6994659CD7273FE1D", + .secretKeyHex = "77AAED2698D56D6676323629160F4EEF21CFD9EE3D0745CC78FA291461F98278", .passphrase = common::gPassphrase, - .passphrase_warning = + .passphraseWarning = "This wallet was generated using a user-supplied " "passphrase that has low entropy and is vulnerable " "to brute-force attacks.", }; static KeyStrings const kStrongBrainStrings = { - .account_id = "rBcvXmNb7KPkNdMkpckdWPpbvkWgcV3nir", - .master_key = "TED AVON CAVE HOUR BRAG JEFF RIFT NEAL TOLD FAT SEW SAN", - .master_seed = "shKdhWka8hS7Es3bpctCZXBiAwfUN", - .master_seed_hex = "74BA8389B44F98CF41E795CD91F9C93F", - .public_key = "aBRL2sqVuzrsM6zikPB4v8UBHGn1aKkrsxhYEffhcQxB2LKyywE5", - .public_key_hex = "03BD334FB9E06C58D69603E9922686528B18A754BC2F2E1ADA095FFE67DE952C64", - .secret_key_hex = "84262FB16AA25BE407174C7EDAB531220C30FA4D8A28AA9D564673FB3D34502C", + .accountId = "rBcvXmNb7KPkNdMkpckdWPpbvkWgcV3nir", + .masterKey = "TED AVON CAVE HOUR BRAG JEFF RIFT NEAL TOLD FAT SEW SAN", + .masterSeed = "shKdhWka8hS7Es3bpctCZXBiAwfUN", + .masterSeedHex = "74BA8389B44F98CF41E795CD91F9C93F", + .publicKey = "aBRL2sqVuzrsM6zikPB4v8UBHGn1aKkrsxhYEffhcQxB2LKyywE5", + .publicKeyHex = "03BD334FB9E06C58D69603E9922686528B18A754BC2F2E1ADA095FFE67DE952C64", + .secretKeyHex = "84262FB16AA25BE407174C7EDAB531220C30FA4D8A28AA9D564673FB3D34502C", .passphrase = "A4yKIRGdzrw0YQ$2%TFKYG9HP*&ok^!sy7E@RwICs", - .passphrase_warning = + .passphraseWarning = "This wallet was generated using a user-supplied " "passphrase. It may be vulnerable to brute-force " "attacks.", @@ -120,11 +120,11 @@ public: json::Value result = walletPropose(params); BEAST_EXPECT(!containsError(result)); - expectEquals(result[jss::account_id], s.account_id); - expectEquals(result[jss::master_seed], s.master_seed); - expectEquals(result[jss::master_seed_hex], s.master_seed_hex); - expectEquals(result[jss::public_key], s.public_key); - expectEquals(result[jss::public_key_hex], s.public_key_hex); + expectEquals(result[jss::account_id], s.accountId); + expectEquals(result[jss::master_seed], s.masterSeed); + expectEquals(result[jss::master_seed_hex], s.masterSeedHex); + expectEquals(result[jss::public_key], s.publicKey); + expectEquals(result[jss::public_key_hex], s.publicKeyHex); expectEquals( result[jss::key_type], params.isMember(jss::key_type) ? params[jss::key_type] : "secp256k1"); @@ -139,7 +139,7 @@ public: json::Value params; if (keyType) params[jss::key_type] = *keyType; - params[jss::seed] = strings.master_seed; + params[jss::seed] = strings.masterSeed; auto const wallet = testSecretWallet(params, strings); BEAST_EXPECT(!wallet.isMember(jss::warning)); @@ -153,7 +153,7 @@ public: json::Value params; if (keyType) params[jss::key_type] = *keyType; - params[jss::seed_hex] = strings.master_seed_hex; + params[jss::seed_hex] = strings.masterSeedHex; auto const wallet = testSecretWallet(params, strings); BEAST_EXPECT(!wallet.isMember(jss::warning)); @@ -173,7 +173,7 @@ public: auto const wallet = testSecretWallet(params, strings); if (value == strings.passphrase) { - BEAST_EXPECT(wallet[jss::warning] == strings.passphrase_warning); + BEAST_EXPECT(wallet[jss::warning] == strings.passphraseWarning); } else { @@ -187,9 +187,9 @@ public: testcase("passphrase"); testLegacyPassphrase(strings.passphrase, keyType, strings); - testLegacyPassphrase(strings.master_key, keyType, strings); - testLegacyPassphrase(strings.master_seed, keyType, strings); - testLegacyPassphrase(strings.master_seed_hex, keyType, strings); + testLegacyPassphrase(strings.masterKey, keyType, strings); + testLegacyPassphrase(strings.masterSeed, keyType, strings); + testLegacyPassphrase(strings.masterSeedHex, keyType, strings); } void @@ -205,8 +205,8 @@ public: json::Value params; if (keyType) params[jss::key_type] = *keyType; - params[jss::seed] = strings.master_seed; - params[jss::seed_hex] = strings.master_seed_hex; + params[jss::seed] = strings.masterSeed; + params[jss::seed_hex] = strings.masterSeedHex; // Secret fields are mutually exclusive. BEAST_EXPECT(containsError(walletPropose(params))); @@ -294,7 +294,7 @@ public: { testcase("keypairForSignature - " + (keyType ? *keyType : "no key_type")); - auto const publicKey = parseBase58(TokenType::AccountPublic, strings.public_key); + auto const publicKey = parseBase58(TokenType::AccountPublic, strings.publicKey); BEAST_EXPECT(publicKey); if (!keyType) @@ -302,7 +302,7 @@ public: { json::Value params; json::Value error; - params[jss::secret] = strings.master_seed; + params[jss::secret] = strings.masterSeed; auto ret = keypairForSignature(params, error); BEAST_EXPECT(!containsError(error)); @@ -316,7 +316,7 @@ public: { json::Value params; json::Value error; - params[jss::secret] = strings.master_seed_hex; + params[jss::secret] = strings.masterSeedHex; auto ret = keypairForSignature(params, error); BEAST_EXPECT(!containsError(error)); @@ -330,7 +330,7 @@ public: { json::Value params; json::Value error; - params[jss::secret] = strings.master_key; + params[jss::secret] = strings.masterKey; auto ret = keypairForSignature(params, error); BEAST_EXPECT(!containsError(error)); @@ -349,7 +349,7 @@ public: json::Value error; params[jss::key_type] = *keyType; - params[jss::seed] = strings.master_seed; + params[jss::seed] = strings.masterSeed; auto ret = keypairForSignature(params, error); BEAST_EXPECT(!containsError(error)); @@ -365,7 +365,7 @@ public: json::Value error; params[jss::key_type] = *keyType; - params[jss::seed_hex] = strings.master_seed_hex; + params[jss::seed_hex] = strings.masterSeedHex; auto ret = keypairForSignature(params, error); BEAST_EXPECT(!containsError(error)); @@ -381,7 +381,7 @@ public: json::Value error; params[jss::key_type] = *keyType; - params[jss::passphrase] = strings.master_key; + params[jss::passphrase] = strings.masterKey; auto ret = keypairForSignature(params, error); BEAST_EXPECT(!containsError(error)); diff --git a/src/test/rpc/LedgerClosed_test.cpp b/src/test/rpc/LedgerClosed_test.cpp index 81919e04d0..f02d23030d 100644 --- a/src/test/rpc/LedgerClosed_test.cpp +++ b/src/test/rpc/LedgerClosed_test.cpp @@ -23,7 +23,7 @@ public: // This test relies on ledger hash so must lock it to fee 10. auto p = envconfig(); - p->FEES.reference_fee = 10; + p->fees.referenceFee = 10; Env env{*this, std::move(p), FeatureBitset{}}; Account const alice{"alice"}; env.fund(XRP(10000), alice); diff --git a/src/test/rpc/LedgerEntry_test.cpp b/src/test/rpc/LedgerEntry_test.cpp index 0f9421beb2..d231a2d4a0 100644 --- a/src/test/rpc/LedgerEntry_test.cpp +++ b/src/test/rpc/LedgerEntry_test.cpp @@ -555,7 +555,7 @@ class LedgerEntry_test : public beast::unit_test::Suite using namespace test::jtx; auto cfg = envconfig(); - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; Env env{*this, std::move(cfg)}; Account const alice{"alice"}; @@ -2237,7 +2237,7 @@ class LedgerEntry_test : public beast::unit_test::Suite Account const bob{"bob"}; Env env{*this, envconfig([](auto cfg) { - cfg->START_UP = StartUpType::Fresh; + cfg->startUp = StartUpType::Fresh; return cfg; })}; @@ -2430,7 +2430,7 @@ class LedgerEntry_test : public beast::unit_test::Suite Account const bob{"bob"}; Env env{*this, envconfig([](auto cfg) { - cfg->START_UP = StartUpType::Fresh; + cfg->startUp = StartUpType::Fresh; return cfg; })}; diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index f8d7901ec0..f35dddffb1 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -268,7 +268,7 @@ class LedgerRPC_test : public beast::unit_test::Suite using namespace test::jtx; auto cfg = envconfig(); - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; Env env{*this, std::move(cfg), FeatureBitset{}}; // hashes requested below // assume no amendments env.fund(XRP(10000), "alice"); @@ -437,7 +437,7 @@ class LedgerRPC_test : public beast::unit_test::Suite return cfg; }); - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; Env env(*this, std::move(cfg)); json::Value jv; diff --git a/src/test/rpc/LedgerRequest_test.cpp b/src/test/rpc/LedgerRequest_test.cpp index 0a2b51dc8b..4759256b96 100644 --- a/src/test/rpc/LedgerRequest_test.cpp +++ b/src/test/rpc/LedgerRequest_test.cpp @@ -149,7 +149,7 @@ public: using namespace test::jtx; auto cfg = envconfig(); - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; Env env{*this, std::move(cfg), FeatureBitset{}}; // the hashes being checked below // assume no amendments Account const gw{"gateway"}; @@ -301,8 +301,8 @@ public: using namespace test::jtx; using namespace std::chrono_literals; Env env{*this, envconfig([](std::unique_ptr cfg) { - cfg->FEES.reference_fee = 10; - cfg->NODE_SIZE = 0; + cfg->fees.referenceFee = 10; + cfg->nodeSize = 0; return cfg; })}; Account const gw{"gateway"}; diff --git a/src/test/rpc/NoRippleCheck_test.cpp b/src/test/rpc/NoRippleCheck_test.cpp index 3793efffcd..9c17d291a1 100644 --- a/src/test/rpc/NoRippleCheck_test.cpp +++ b/src/test/rpc/NoRippleCheck_test.cpp @@ -296,7 +296,7 @@ class NoRippleCheckLimits_test : public beast::unit_test::Suite if (c.balance() > kWarningThreshold) { using ct = beast::AbstractClock; - c.entry().local_balance = + c.entry().localBalance = DecayingSample{steady_clock::now()}; } }; diff --git a/src/test/rpc/RPCCall_test.cpp b/src/test/rpc/RPCCall_test.cpp index 66ddfde8e2..cd3cd3cb41 100644 --- a/src/test/rpc/RPCCall_test.cpp +++ b/src/test/rpc/RPCCall_test.cpp @@ -5843,7 +5843,7 @@ makeNetworkConfig(uint32_t networkID) { using namespace test::jtx; return envconfig([&](std::unique_ptr cfg) { - cfg->NETWORK_ID = networkID; + cfg->networkId = networkID; return cfg; }); } diff --git a/src/test/rpc/Simulate_test.cpp b/src/test/rpc/Simulate_test.cpp index 60452e386a..9206bf42ac 100644 --- a/src/test/rpc/Simulate_test.cpp +++ b/src/test/rpc/Simulate_test.cpp @@ -492,7 +492,7 @@ class Simulate_test : public beast::unit_test::Suite using namespace jtx; Env env{*this, envconfig([&](std::unique_ptr cfg) { - cfg->NETWORK_ID = 0; + cfg->networkId = 0; return cfg; })}; static auto const kNewDomain = "123ABC"; @@ -1032,7 +1032,7 @@ class Simulate_test : public beast::unit_test::Suite using namespace jtx; Env env{*this, envconfig([&](std::unique_ptr cfg) { - cfg->NETWORK_ID = 1025; + cfg->networkId = 1025; return cfg; })}; static auto const kNewDomain = "123ABC"; @@ -1097,7 +1097,7 @@ class Simulate_test : public beast::unit_test::Suite using namespace jtx; using namespace std::chrono_literals; Env env{*this, envconfig([&](std::unique_ptr cfg) { - cfg->NETWORK_ID = 1025; + cfg->networkId = 1025; return cfg; })}; diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index 3011eb4170..090a599e7c 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -321,7 +321,7 @@ public: using namespace std::chrono_literals; using namespace jtx; Env env(*this, envconfig([](std::unique_ptr cfg) { - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; cfg = singleThreadIo(std::move(cfg)); return cfg; })); @@ -1319,7 +1319,7 @@ public: auto const carol = permDex.carol; auto const domainID = permDex.domainID; auto const gw = permDex.gw; - auto const usd = permDex.USD; + auto const usd = permDex.usd; auto wsc = makeWSClient(env.app().config()); diff --git a/src/test/rpc/TransactionEntry_test.cpp b/src/test/rpc/TransactionEntry_test.cpp index c5f562b2b2..8724e2974d 100644 --- a/src/test/rpc/TransactionEntry_test.cpp +++ b/src/test/rpc/TransactionEntry_test.cpp @@ -32,7 +32,7 @@ class TransactionEntry_test : public beast::unit_test::Suite testcase("Invalid request params"); using namespace test::jtx; Env env{*this, envconfig([](std::unique_ptr cfg) { - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; return cfg; })}; @@ -139,7 +139,7 @@ class TransactionEntry_test : public beast::unit_test::Suite testcase("Basic request API version " + std::to_string(apiVersion)); using namespace test::jtx; Env env{*this, envconfig([](std::unique_ptr cfg) { - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; return cfg; })}; diff --git a/src/test/rpc/Transaction_test.cpp b/src/test/rpc/Transaction_test.cpp index 47cc3687b0..8c50736b85 100644 --- a/src/test/rpc/Transaction_test.cpp +++ b/src/test/rpc/Transaction_test.cpp @@ -47,7 +47,7 @@ class Transaction_test : public beast::unit_test::Suite { using namespace test::jtx; return envconfig([&](std::unique_ptr cfg) { - cfg->NETWORK_ID = networkID; + cfg->networkId = networkID; return cfg; }); } @@ -749,7 +749,7 @@ class Transaction_test : public beast::unit_test::Suite using std::to_string; Env env{*this, envconfig([](std::unique_ptr cfg) { - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; return cfg; })}; Account const alice{"alice"}; @@ -827,7 +827,7 @@ class Transaction_test : public beast::unit_test::Suite using std::to_string; Env env{*this, envconfig([](std::unique_ptr cfg) { - cfg->FEES.reference_fee = 10; + cfg->fees.referenceFee = 10; return cfg; })}; Account const alice{"alice"}; diff --git a/src/test/rpc/Version_test.cpp b/src/test/rpc/Version_test.cpp index 75d74b4155..20740bfe53 100644 --- a/src/test/rpc/Version_test.cpp +++ b/src/test/rpc/Version_test.cpp @@ -65,7 +65,7 @@ class Version_test : public beast::unit_test::Suite "{\"api_version\": " + std::to_string(RPC::kApiMinimumSupportedVersion - 1) + "}"); BEAST_EXPECT(badVersion(re)); - BEAST_EXPECT(env.app().config().BETA_RPC_API); + BEAST_EXPECT(env.app().config().betaRpcApi); re = env.rpc( "json", "version", @@ -160,7 +160,7 @@ class Version_test : public beast::unit_test::Suite using namespace test::jtx; Env env{*this}; - BEAST_EXPECT(env.app().config().BETA_RPC_API); + BEAST_EXPECT(env.app().config().betaRpcApi); auto const withoutApiVerion = std::string("{ ") + "\"jsonrpc\": \"2.0\", " "\"ripplerpc\": \"2.0\", " @@ -194,19 +194,19 @@ class Version_test : public beast::unit_test::Suite testcase("config test"); { Config const c; - BEAST_EXPECT(c.BETA_RPC_API == false); + BEAST_EXPECT(c.betaRpcApi == false); } { Config c; c.loadFromString("\n[beta_rpc_api]\n1\n"); - BEAST_EXPECT(c.BETA_RPC_API == true); + BEAST_EXPECT(c.betaRpcApi == true); } { Config c; c.loadFromString("\n[beta_rpc_api]\n0\n"); - BEAST_EXPECT(c.BETA_RPC_API == false); + BEAST_EXPECT(c.betaRpcApi == false); } } @@ -220,7 +220,7 @@ class Version_test : public beast::unit_test::Suite c->loadFromString("\n[beta_rpc_api]\n1\n"); return c; })}; - if (!BEAST_EXPECT(env.app().config().BETA_RPC_API == true)) + if (!BEAST_EXPECT(env.app().config().betaRpcApi == true)) return; auto jrr = env.rpc( diff --git a/src/test/server/ServerStatus_test.cpp b/src/test/server/ServerStatus_test.cpp index eba36462b6..6569a2d2a4 100644 --- a/src/test/server/ServerStatus_test.cpp +++ b/src/test/server/ServerStatus_test.cpp @@ -843,7 +843,7 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En BEAST_EXPECT(resp.body().find("connectivity is working.") != std::string::npos); // with ELB_SUPPORT, status still does not indicate a problem - env.app().config().ELB_SUPPORT = true; + env.app().config().elbSupport = true; doRequest( yield, @@ -973,7 +973,7 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En BEAST_EXPECT(resp.result() == boost::beast::http::status::ok); BEAST_EXPECT(resp.body().find("connectivity is working.") != std::string::npos); - env.app().config().ELB_SUPPORT = true; + env.app().config().elbSupport = true; doRequest( yield, @@ -1111,7 +1111,7 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En using namespace test::jtx; Env env{*this, envconfig([](std::unique_ptr cfg) { - cfg->ELB_SUPPORT = true; + cfg->elbSupport = true; return cfg; })}; diff --git a/src/test/server/Server_test.cpp b/src/test/server/Server_test.cpp index 1dc40f6337..263fb9451f 100644 --- a/src/test/server/Server_test.cpp +++ b/src/test/server/Server_test.cpp @@ -48,15 +48,15 @@ public: class TestThread { private: - boost::asio::io_context io_context_; + boost::asio::io_context ioContext_; std::optional> work_; std::thread thread_; public: TestThread() - : work_(std::in_place, boost::asio::make_work_guard(io_context_)) - , thread_([&]() { this->io_context_.run(); }) + : work_(std::in_place, boost::asio::make_work_guard(ioContext_)) + , thread_([&]() { this->ioContext_.run(); }) { } @@ -69,7 +69,7 @@ public: boost::asio::io_context& getIoContext() { - return io_context_; + return ioContext_; } }; diff --git a/src/test/unit_test/multi_runner.cpp b/src/test/unit_test/multi_runner.cpp index 60e979299d..3a56d22654 100644 --- a/src/test/unit_test/multi_runner.cpp +++ b/src/test/unit_test/multi_runner.cpp @@ -139,28 +139,28 @@ template std::size_t MultiRunnerBase::Inner::checkoutJobIndex() { - return job_index++; + return jobIndex++; } template std::size_t MultiRunnerBase::Inner::checkoutTestIndex() { - return test_index++; + return testIndex++; } template bool MultiRunnerBase::Inner::anyFailed() const { - return any_failed; + return anyFailedFlag; } template void MultiRunnerBase::Inner::anyFailed(bool v) { - any_failed = any_failed || v; + anyFailedFlag = anyFailedFlag || v; } template @@ -183,14 +183,14 @@ template void MultiRunnerBase::Inner::incKeepAliveCount() { - ++keep_alive; + ++keepAlive; } template std::size_t MultiRunnerBase::Inner::getKeepAliveCount() { - return keep_alive; + return keepAlive; } template @@ -222,7 +222,7 @@ MultiRunnerBase::MultiRunnerBase() boost::interprocess::message_queue::remove(kMessageQueueName); } - shared_mem_ = boost::interprocess::shared_memory_object{ + sharedMem_ = boost::interprocess::shared_memory_object{ std::conditional_t< IsParent, boost::interprocess::create_only_t, @@ -232,8 +232,8 @@ MultiRunnerBase::MultiRunnerBase() if (IsParent) { - shared_mem_.truncate(sizeof(Inner)); - message_queue_ = std::make_unique( + sharedMem_.truncate(sizeof(Inner)); + messageQueue_ = std::make_unique( boost::interprocess::create_only, kMessageQueueName, /*max messages*/ 16, @@ -241,11 +241,11 @@ MultiRunnerBase::MultiRunnerBase() } else { - message_queue_ = std::make_unique( + messageQueue_ = std::make_unique( boost::interprocess::open_only, kMessageQueueName); } - region_ = boost::interprocess::mapped_region{shared_mem_, boost::interprocess::read_write}; + region_ = boost::interprocess::mapped_region{sharedMem_, boost::interprocess::read_write}; if (IsParent) { inner_ = new (region_.get_address()) Inner{}; @@ -340,8 +340,8 @@ MultiRunnerBase::messageQueueSend(MessageType mt, std::string const& s { // must use a mutex since the two "sends" must happen in order std::scoped_lock const l{inner_->m}; - message_queue_->send(&mt, sizeof(mt), /*priority*/ 0); - message_queue_->send(s.c_str(), s.size(), /*priority*/ 0); + messageQueue_->send(&mt, sizeof(mt), /*priority*/ 0); + messageQueue_->send(s.c_str(), s.size(), /*priority*/ 0); } template @@ -376,13 +376,13 @@ namespace test { MultiRunnerParent::MultiRunnerParent() : os_(std::cout) { - message_queue_thread_ = std::thread([this] { + messageQueueThread_ = std::thread([this] { std::vector buf(1 << 20); - while (this->continue_message_queue_ || this->message_queue_->get_num_msg()) + while (this->continueMessageQueue_ || this->messageQueue_->get_num_msg()) { // let children know the parent is still alive this->incKeepAliveCount(); - if (!this->message_queue_->get_num_msg()) + if (!this->messageQueue_->get_num_msg()) { // If a child does not see the keep alive count incremented, // it will assume the parent has died. This sleep time needs @@ -395,13 +395,13 @@ MultiRunnerParent::MultiRunnerParent() : os_(std::cout) { std::size_t recvdSize = 0; unsigned int priority = 0; - this->message_queue_->receive(buf.data(), buf.size(), recvdSize, priority); + this->messageQueue_->receive(buf.data(), buf.size(), recvdSize, priority); if (!recvdSize) continue; assert(recvdSize == 1); MessageType const mt{*reinterpret_cast(buf.data())}; - this->message_queue_->receive(buf.data(), buf.size(), recvdSize, priority); + this->messageQueue_->receive(buf.data(), buf.size(), recvdSize, priority); if (recvdSize) { std::string s{buf.data(), recvdSize}; @@ -412,10 +412,10 @@ MultiRunnerParent::MultiRunnerParent() : os_(std::cout) this->os_.flush(); break; case MessageType::TestStart: - running_suites_.insert(std::move(s)); + runningSuites_.insert(std::move(s)); break; case MessageType::TestEnd: - running_suites_.erase(s); + runningSuites_.erase(s); break; default: assert(0); // unknown message type @@ -440,14 +440,14 @@ MultiRunnerParent::~MultiRunnerParent() { using namespace beast::unit_test; - continue_message_queue_ = false; - message_queue_thread_.join(); + continueMessageQueue_ = false; + messageQueueThread_.join(); - addFailures(running_suites_.size()); + addFailures(runningSuites_.size()); printResults(os_); - for (auto const& s : running_suites_) + for (auto const& s : runningSuites_) { os_ << "\nSuite: " << s << " failed to complete. The child process may have crashed.\n"; } @@ -480,16 +480,13 @@ MultiRunnerParent::addFailures(std::size_t failures) //------------------------------------------------------------------------------ MultiRunnerChild::MultiRunnerChild(std::size_t numJobs, bool quiet, bool printLog) - : job_index_{checkoutJobIndex()} - , num_jobs_{numJobs} - , quiet_{quiet} - , print_log_{!quiet || printLog} + : jobIndex_{checkoutJobIndex()}, numJobs_{numJobs}, quiet_{quiet}, printLog_{!quiet || printLog} { - if (num_jobs_ > 1) + if (numJobs_ > 1) { - keep_alive_thread_ = std::thread([this] { + keepAliveThread_ = std::thread([this] { std::size_t lastCount = getKeepAliveCount(); - while (this->continue_keep_alive_) + while (this->continueKeepAlive_) { // Use a small sleep time so in the normal case the child // process may shutdown quickly. However, to protect against @@ -504,7 +501,7 @@ MultiRunnerChild::MultiRunnerChild(std::size_t numJobs, bool quiet, bool printLo if (curCount == lastCount) { // assume parent process is no longer alive - std::cerr << "multi_runner_child " << job_index_ + std::cerr << "multi_runner_child " << jobIndex_ << ": Assuming parent died, exiting.\n"; std::exit(EXIT_FAILURE); } @@ -517,10 +514,10 @@ MultiRunnerChild::MultiRunnerChild(std::size_t numJobs, bool quiet, bool printLo MultiRunnerChild::~MultiRunnerChild() { - if (num_jobs_ > 1) + if (numJobs_ > 1) { - continue_keep_alive_ = false; - keep_alive_thread_.join(); + continueKeepAlive_ = false; + keepAliveThread_.join(); } add(results_); @@ -548,75 +545,74 @@ MultiRunnerChild::addFailures(std::size_t failures) void MultiRunnerChild::onSuiteBegin(beast::unit_test::SuiteInfo const& info) { - suite_results_ = detail::SuiteResults{info.fullName()}; - messageQueueSend(MessageType::TestStart, suite_results_.name); + suiteResults_ = detail::SuiteResults{info.fullName()}; + messageQueueSend(MessageType::TestStart, suiteResults_.name); } void MultiRunnerChild::onSuiteEnd() { - if (print_log_ || suite_results_.failed > 0) + if (printLog_ || suiteResults_.failed > 0) { std::stringstream s; - if (num_jobs_ > 1) - s << job_index_ << "> "; - s << (suite_results_.failed > 0 ? "failed: " : "") << suite_results_.name << " had " - << suite_results_.failed << " failures." << std::endl; + if (numJobs_ > 1) + s << jobIndex_ << "> "; + s << (suiteResults_.failed > 0 ? "failed: " : "") << suiteResults_.name << " had " + << suiteResults_.failed << " failures." << std::endl; messageQueueSend(MessageType::Log, s.str()); } - results_.add(suite_results_); - messageQueueSend(MessageType::TestEnd, suite_results_.name); + results_.add(suiteResults_); + messageQueueSend(MessageType::TestEnd, suiteResults_.name); } void MultiRunnerChild::onCaseBegin(std::string const& name) { - case_results_ = detail::CaseResults(name); + caseResults_ = detail::CaseResults(name); if (quiet_) return; std::stringstream s; - if (num_jobs_ > 1) - s << job_index_ << "> "; - s << suite_results_.name << (case_results_.name.empty() ? "" : (" " + case_results_.name)) - << '\n'; + if (numJobs_ > 1) + s << jobIndex_ << "> "; + s << suiteResults_.name << (caseResults_.name.empty() ? "" : (" " + caseResults_.name)) << '\n'; messageQueueSend(MessageType::Log, s.str()); } void MultiRunnerChild::onCaseEnd() { - suite_results_.add(case_results_); + suiteResults_.add(caseResults_); } void MultiRunnerChild::onPass() { - ++case_results_.total; + ++caseResults_.total; } void MultiRunnerChild::onFail(std::string const& reason) { - ++case_results_.failed; - ++case_results_.total; + ++caseResults_.failed; + ++caseResults_.total; std::stringstream s; - if (num_jobs_ > 1) - s << job_index_ << "> "; - s << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ") << reason << '\n'; + if (numJobs_ > 1) + s << jobIndex_ << "> "; + s << "#" << caseResults_.total << " failed" << (reason.empty() ? "" : ": ") << reason << '\n'; messageQueueSend(MessageType::Log, s.str()); } void MultiRunnerChild::onLog(std::string const& msg) { - if (!print_log_) + if (!printLog_) return; std::stringstream s; - if (num_jobs_ > 1) - s << job_index_ << "> "; + if (numJobs_ > 1) + s << jobIndex_ << "> "; s << msg; messageQueueSend(MessageType::Log, s.str()); } diff --git a/src/test/unit_test/multi_runner.h b/src/test/unit_test/multi_runner.h index 5ebc8ec0a8..8b07559e8c 100644 --- a/src/test/unit_test/multi_runner.h +++ b/src/test/unit_test/multi_runner.h @@ -87,14 +87,14 @@ class MultiRunnerBase // way they communicate is through message queues. struct Inner { - std::atomic job_index{0}; - std::atomic test_index{0}; - std::atomic any_failed{false}; - // A parent process will periodically increment `keep_alive_`. The child - // processes will check if `keep_alive_` is being incremented. If it is + std::atomic jobIndex{0}; + std::atomic testIndex{0}; + std::atomic anyFailedFlag{false}; + // A parent process will periodically increment `keepAlive`. The child + // processes will check if `keepAlive` is being incremented. If it is // not incremented for a sufficiently long time, the child will assume // the parent process has died. - std::atomic keep_alive{0}; + std::atomic keepAlive{0}; mutable boost::interprocess::interprocess_mutex m; detail::Results results; @@ -139,11 +139,11 @@ class MultiRunnerBase // `inner_` will be created in shared memory Inner* inner_; // shared memory to use for the `inner` member - boost::interprocess::shared_memory_object shared_mem_; + boost::interprocess::shared_memory_object sharedMem_; boost::interprocess::mapped_region region_; protected: - std::unique_ptr message_queue_; + std::unique_ptr messageQueue_; enum class MessageType : std::uint8_t { TestStart, TestEnd, Log }; void @@ -201,10 +201,10 @@ class MultiRunnerParent : private detail::MultiRunnerBase private: // message_queue_ is used to collect log messages from the children std::ostream& os_; - std::atomic continue_message_queue_{true}; - std::thread message_queue_thread_; + std::atomic continueMessageQueue_{true}; + std::thread messageQueueThread_; // track running suites so if a child crashes the culprit can be flagged - std::set running_suites_; + std::set runningSuites_; public: MultiRunnerParent(MultiRunnerParent const&) = delete; @@ -235,16 +235,16 @@ class MultiRunnerChild : public beast::unit_test::Runner, private detail::MultiRunnerBase { private: - std::size_t job_index_; + std::size_t jobIndex_; detail::Results results_; - detail::SuiteResults suite_results_; - detail::CaseResults case_results_; - std::size_t num_jobs_{0}; + detail::SuiteResults suiteResults_; + detail::CaseResults caseResults_; + std::size_t numJobs_{0}; bool quiet_{false}; - bool print_log_{true}; + bool printLog_{true}; - std::atomic continue_keep_alive_{true}; - std::thread keep_alive_thread_; + std::atomic continueKeepAlive_{true}; + std::thread keepAliveThread_; public: MultiRunnerChild(MultiRunnerChild const&) = delete; @@ -318,12 +318,12 @@ MultiRunnerChild::runMulti(Pred pred) } catch (...) { - if (num_jobs_ <= 1) + if (numJobs_ <= 1) throw; // a single process can die // inform the parent std::stringstream s; - s << job_index_ << "> failed Unhandled exception in test.\n"; + s << jobIndex_ << "> failed Unhandled exception in test.\n"; messageQueueSend(MessageType::Log, s.str()); failed = true; } diff --git a/src/xrpld/app/ledger/LedgerHistory.cpp b/src/xrpld/app/ledger/LedgerHistory.cpp index f251f25620..8520fc941f 100644 --- a/src/xrpld/app/ledger/LedgerHistory.cpp +++ b/src/xrpld/app/ledger/LedgerHistory.cpp @@ -36,14 +36,14 @@ namespace xrpl { LedgerHistory::LedgerHistory(beast::insight::Collector::ptr const& collector, Application& app) : app_(app) , collector_(collector) - , mismatch_counter_(collector->makeCounter("ledger.history", "mismatch")) - , ledgers_by_hash_( + , mismatchCounter_(collector->makeCounter("ledger.history", "mismatch")) + , ledgersByHash_( "LedgerCache", app_.config().getValueFor(SizedItem::LedgerSize), std::chrono::seconds{app_.config().getValueFor(SizedItem::LedgerAge)}, stopwatch(), app_.getJournal("TaggedCache")) - , consensus_validated_( + , consensusValidated_( "ConsensusValidated", 64, std::chrono::minutes{5}, @@ -62,10 +62,9 @@ LedgerHistory::insert(std::shared_ptr const& ledger, bool validate XRPL_ASSERT( ledger->stateMap().getHash().isNonZero(), "xrpl::LedgerHistory::insert : nonzero hash"); - std::unique_lock const sl(ledgers_by_hash_.peekMutex()); + std::unique_lock const sl(ledgersByHash_.peekMutex()); - bool const alreadyHad = - ledgers_by_hash_.canonicalizeReplaceCache(ledger->header().hash, ledger); + bool const alreadyHad = ledgersByHash_.canonicalizeReplaceCache(ledger->header().hash, ledger); if (validated) ledgersByIndex_[ledger->header().seq] = ledger->header().hash; @@ -75,7 +74,7 @@ LedgerHistory::insert(std::shared_ptr const& ledger, bool validate LedgerHash LedgerHistory::getLedgerHash(LedgerIndex index) { - std::unique_lock const sl(ledgers_by_hash_.peekMutex()); + std::unique_lock const sl(ledgersByHash_.peekMutex()); if (auto it = ledgersByIndex_.find(index); it != ledgersByIndex_.end()) return it->second; return {}; @@ -85,7 +84,7 @@ std::shared_ptr LedgerHistory::getLedgerBySeq(LedgerIndex index) { { - std::unique_lock sl(ledgers_by_hash_.peekMutex()); + std::unique_lock sl(ledgersByHash_.peekMutex()); auto it = ledgersByIndex_.find(index); if (it != ledgersByIndex_.end()) @@ -97,7 +96,7 @@ LedgerHistory::getLedgerBySeq(LedgerIndex index) } Rules const rules{app_.config().features}; - Fees const fees = app_.config().FEES.toFees(); + Fees const fees = app_.config().fees.toFees(); std::shared_ptr ret = loadByIndex(index, rules, fees, app_); if (!ret) @@ -108,11 +107,11 @@ LedgerHistory::getLedgerBySeq(LedgerIndex index) { // Add this ledger to the local tracking by index - std::unique_lock const sl(ledgers_by_hash_.peekMutex()); + std::unique_lock const sl(ledgersByHash_.peekMutex()); XRPL_ASSERT( ret->isImmutable(), "xrpl::LedgerHistory::getLedgerBySeq : immutable result ledger"); - ledgers_by_hash_.canonicalizeReplaceClient(ret->header().hash, ret); + ledgersByHash_.canonicalizeReplaceClient(ret->header().hash, ret); ledgersByIndex_[ret->header().seq] = ret->header().hash; return (ret->header().seq == index) ? ret : nullptr; } @@ -121,7 +120,7 @@ LedgerHistory::getLedgerBySeq(LedgerIndex index) std::shared_ptr LedgerHistory::getLedgerByHash(LedgerHash const& hash) { - auto ret = ledgers_by_hash_.fetch(hash); + auto ret = ledgersByHash_.fetch(hash); if (ret) { @@ -137,7 +136,7 @@ LedgerHistory::getLedgerByHash(LedgerHash const& hash) } Rules const rules{app_.config().features}; - Fees const fees = app_.config().FEES.toFees(); + Fees const fees = app_.config().fees.toFees(); ret = loadByHash(hash, rules, fees, app_); if (!ret) @@ -148,7 +147,7 @@ LedgerHistory::getLedgerByHash(LedgerHash const& hash) XRPL_ASSERT( ret->header().hash == hash, "xrpl::LedgerHistory::getLedgerByHash : loaded ledger hash match"); - ledgers_by_hash_.canonicalizeReplaceClient(ret->header().hash, ret); + ledgersByHash_.canonicalizeReplaceClient(ret->header().hash, ret); XRPL_ASSERT( ret->header().hash == hash, "xrpl::LedgerHistory::getLedgerByHash : result hash match"); @@ -319,7 +318,7 @@ LedgerHistory::handleMismatch( json::Value const& consensus) { XRPL_ASSERT(built != valid, "xrpl::LedgerHistory::handleMismatch : unequal hashes"); - ++mismatch_counter_; + ++mismatchCounter_; auto builtLedger = getLedgerByHash(built); auto validLedger = getLedgerByHash(valid); @@ -430,10 +429,10 @@ LedgerHistory::builtLedger( LedgerHash const hash = ledger->header().hash; XRPL_ASSERT(!hash.isZero(), "xrpl::LedgerHistory::builtLedger : nonzero hash"); - std::unique_lock const sl(consensus_validated_.peekMutex()); + std::unique_lock const sl(consensusValidated_.peekMutex()); auto entry = std::make_shared(); - consensus_validated_.canonicalizeReplaceClient(index, entry); + consensusValidated_.canonicalizeReplaceClient(index, entry); if (entry->validated && !entry->built) { @@ -469,10 +468,10 @@ LedgerHistory::validatedLedger( LedgerHash const hash = ledger->header().hash; XRPL_ASSERT(!hash.isZero(), "xrpl::LedgerHistory::validatedLedger : nonzero hash"); - std::unique_lock const sl(consensus_validated_.peekMutex()); + std::unique_lock const sl(consensusValidated_.peekMutex()); auto entry = std::make_shared(); - consensus_validated_.canonicalizeReplaceClient(index, entry); + consensusValidated_.canonicalizeReplaceClient(index, entry); if (entry->built && !entry->validated) { @@ -504,7 +503,7 @@ LedgerHistory::validatedLedger( bool LedgerHistory::fixIndex(LedgerIndex ledgerIndex, LedgerHash const& ledgerHash) { - std::unique_lock const sl(ledgers_by_hash_.peekMutex()); + std::unique_lock const sl(ledgersByHash_.peekMutex()); auto it = ledgersByIndex_.find(ledgerIndex); if ((it != ledgersByIndex_.end()) && (it->second != ledgerHash)) @@ -518,11 +517,11 @@ LedgerHistory::fixIndex(LedgerIndex ledgerIndex, LedgerHash const& ledgerHash) void LedgerHistory::clearLedgerCachePrior(LedgerIndex seq) { - for (LedgerHash const it : ledgers_by_hash_.getKeys()) + for (LedgerHash const it : ledgersByHash_.getKeys()) { auto const ledger = getLedgerByHash(it); if (!ledger || ledger->header().seq < seq) - ledgers_by_hash_.del(it, false); + ledgersByHash_.del(it, false); } } diff --git a/src/xrpld/app/ledger/LedgerHistory.h b/src/xrpld/app/ledger/LedgerHistory.h index 01c88b1517..057de7b1bc 100644 --- a/src/xrpld/app/ledger/LedgerHistory.h +++ b/src/xrpld/app/ledger/LedgerHistory.h @@ -30,7 +30,7 @@ public: float getCacheHitRate() { - return ledgers_by_hash_.getHitRate(); + return ledgersByHash_.getHitRate(); } /** Get a ledger given its sequence number */ @@ -53,8 +53,8 @@ public: void sweep() { - ledgers_by_hash_.sweep(); - consensus_validated_.sweep(); + ledgersByHash_.sweep(); + consensusValidated_.sweep(); } /** Report that we have locally built a particular ledger */ @@ -99,11 +99,11 @@ private: Application& app_; beast::insight::Collector::ptr collector_; - beast::insight::Counter mismatch_counter_; + beast::insight::Counter mismatchCounter_; using LedgersByHash = TaggedCache; - LedgersByHash ledgers_by_hash_; + LedgersByHash ledgersByHash_; // Maps ledger indexes to the corresponding hashes // For debug and logging purposes @@ -121,7 +121,7 @@ private: std::optional consensus; }; using ConsensusValidated = TaggedCache; - ConsensusValidated consensus_validated_; + ConsensusValidated consensusValidated_; // Maps ledger indexes to the corresponding hash. std::map ledgersByIndex_; // validated ledgers diff --git a/src/xrpld/app/ledger/LedgerMaster.h b/src/xrpld/app/ledger/LedgerMaster.h index 0b3310ea79..885ab6db25 100644 --- a/src/xrpld/app/ledger/LedgerMaster.h +++ b/src/xrpld/app/ledger/LedgerMaster.h @@ -347,20 +347,20 @@ private: bool const standalone_; // How many ledgers before the current ledger do we allow peers to request? - std::uint32_t const fetch_depth_; + std::uint32_t const fetchDepth_; // How much history do we want to keep - std::uint32_t const ledger_history_; + std::uint32_t const ledgerHistorySize_; - std::uint32_t const ledger_fetch_size_; + std::uint32_t const ledgerFetchSize_; - TaggedCache fetch_packs_; + TaggedCache fetchPacks_; - std::uint32_t fetch_seq_{0}; + std::uint32_t fetchSeq_{0}; // Try to keep a validator from switching from test to live network // without first wiping the database. - LedgerIndex const max_ledger_difference_{1000000}; + LedgerIndex const maxLedgerDifference_{1000000}; // Time that the previous upgrade warning was issued. TimeKeeper::time_point upgradeWarningPrevTime_; diff --git a/src/xrpld/app/ledger/OpenLedger.h b/src/xrpld/app/ledger/OpenLedger.h index 884b6b02db..02e073bc9a 100644 --- a/src/xrpld/app/ledger/OpenLedger.h +++ b/src/xrpld/app/ledger/OpenLedger.h @@ -33,8 +33,8 @@ class OpenLedger private: beast::Journal const j_; CachedSLEs& cache_; - std::mutex mutable modify_mutex_; - std::mutex mutable current_mutex_; + std::mutex mutable modifyMutex_; + std::mutex mutable currentMutex_; std::shared_ptr current_; public: diff --git a/src/xrpld/app/ledger/detail/LedgerCleaner.cpp b/src/xrpld/app/ledger/detail/LedgerCleaner.cpp index 7613aab85a..9f2db9d2f2 100644 --- a/src/xrpld/app/ledger/detail/LedgerCleaner.cpp +++ b/src/xrpld/app/ledger/detail/LedgerCleaner.cpp @@ -286,7 +286,7 @@ private: } Rules const rules{app_.config().features}; - Fees const fees = app_.config().FEES.toFees(); + Fees const fees = app_.config().fees.toFees(); auto const dbLedger = loadByIndex(ledgerIndex, rules, fees, app_); if (!dbLedger || (dbLedger->header().hash != ledgerHash) || (dbLedger->header().parentHash != nodeLedger->header().parentHash)) diff --git a/src/xrpld/app/ledger/detail/LedgerMaster.cpp b/src/xrpld/app/ledger/detail/LedgerMaster.cpp index 50a3a6289c..9baad0ec90 100644 --- a/src/xrpld/app/ledger/detail/LedgerMaster.cpp +++ b/src/xrpld/app/ledger/detail/LedgerMaster.cpp @@ -127,10 +127,10 @@ LedgerMaster::LedgerMaster( , journal_(journal) , ledgerHistory_(collector, app) , standalone_(app_.config().standalone()) - , fetch_depth_(app_.getSHAMapStore().clampFetchDepth(app_.config().FETCH_DEPTH)) - , ledger_history_(app_.config().LEDGER_HISTORY) - , ledger_fetch_size_(app_.config().getValueFor(SizedItem::LedgerFetch)) - , fetch_packs_( + , fetchDepth_(app_.getSHAMapStore().clampFetchDepth(app_.config().fetchDepth)) + , ledgerHistorySize_(app_.config().ledgerHistory) + , ledgerFetchSize_(app_.config().getValueFor(SizedItem::LedgerFetch)) + , fetchPacks_( "FetchPack", 65536, std::chrono::seconds{45}, @@ -286,9 +286,9 @@ LedgerMaster::setValidLedger(std::shared_ptr const& l) validLedgerSign_ = signTime.time_since_epoch().count(); XRPL_ASSERT( validLedgerSeq_ || !app_.getMaxDisallowedLedger() || - l->header().seq + max_ledger_difference_ > app_.getMaxDisallowedLedger(), + l->header().seq + maxLedgerDifference_ > app_.getMaxDisallowedLedger(), "xrpl::LedgerMaster::setValidLedger : valid ledger sequence"); - (void)max_ledger_difference_; + (void)maxLedgerDifference_; validLedgerSeq_ = l->header().seq; app_.getOPs().updateLocalTx(*l); @@ -629,9 +629,9 @@ LedgerMaster::getEarliestFetch() // unless that creates a larger range than allowed std::uint32_t e = getClosedLedger()->header().seq; - if (e > fetch_depth_) + if (e > fetchDepth_) { - e -= fetch_depth_; + e -= fetchDepth_; } else { @@ -1277,10 +1277,10 @@ LedgerMaster::findNewLedgersToPublish(std::unique_lock& sl ledger = ledgerHistory_.getLedgerByHash(*hash); } - if (!app_.config().LEDGER_REPLAY) + if (!app_.config().ledgerReplay) { // Can we try to acquire the ledger we need? - if (!ledger && (++acqCount < ledger_fetch_size_)) + if (!ledger && (++acqCount < ledgerFetchSize_)) { ledger = app_.getInboundLedgers().acquire( *hash, seq, InboundLedger::Reason::GENERIC); @@ -1304,7 +1304,7 @@ LedgerMaster::findNewLedgersToPublish(std::unique_lock& sl << ex.what(); } - if (app_.config().LEDGER_REPLAY) + if (app_.config().ledgerReplay) { /* Narrow down the gap of ledgers, and try to replay them. * When replaying a ledger gap, if the local node has @@ -1737,7 +1737,7 @@ void LedgerMaster::sweep() { ledgerHistory_.sweep(); - fetch_packs_.sweep(); + fetchPacks_.sweep(); } float @@ -1789,11 +1789,11 @@ LedgerMaster::fetchForHistory( if (!app_.getInboundLedgers().isFailure(*hash)) { ledger = app_.getInboundLedgers().acquire(*hash, missing, reason); - if (!ledger && missing != fetch_seq_ && + if (!ledger && missing != fetchSeq_ && missing > app_.getNodeStore().earliestLedgerSeq()) { JLOG(journal_.trace()) << "fetchForHistory want fetch pack " << missing; - fetch_seq_ = missing; + fetchSeq_ = missing; getFetchPack(missing, reason); } else @@ -1833,8 +1833,7 @@ LedgerMaster::fetchForHistory( // Do not fetch ledger sequences lower // than the earliest ledger sequence fetchSz = app_.getNodeStore().earliestLedgerSeq(); - fetchSz = - missing >= fetchSz ? std::min(ledger_fetch_size_, (missing - fetchSz) + 1) : 0; + fetchSz = missing >= fetchSz ? std::min(ledgerFetchSize_, (missing - fetchSz) + 1) : 0; try { for (std::uint32_t i = 0; i < fetchSz; ++i) @@ -1903,7 +1902,7 @@ LedgerMaster::doAdvance(std::unique_lock& sl) if ((fillInProgress_ == 0 || *missing > fillInProgress_) && shouldAcquire( validLedgerSeq_, - ledger_history_, + ledgerHistorySize_, app_.getSHAMapStore().minimumOnline(), *missing, journal_)) @@ -1962,16 +1961,16 @@ LedgerMaster::doAdvance(std::unique_lock& sl) void LedgerMaster::addFetchPack(uint256 const& hash, std::shared_ptr data) { - fetch_packs_.canonicalizeReplaceClient(hash, data); + fetchPacks_.canonicalizeReplaceClient(hash, data); } std::optional LedgerMaster::getFetchPack(uint256 const& hash) { Blob data; - if (fetch_packs_.retrieve(hash, data)) + if (fetchPacks_.retrieve(hash, data)) { - fetch_packs_.del(hash, false); + fetchPacks_.del(hash, false); if (hash == sha512Half(makeSlice(data))) return data; } @@ -2170,7 +2169,7 @@ LedgerMaster::makeFetchPack( std::size_t LedgerMaster::getFetchPackCacheSize() const { - return fetch_packs_.getCacheSize(); + return fetchPacks_.getCacheSize(); } // Returns the minimum ledger sequence in SQL database, if any. diff --git a/src/xrpld/app/ledger/detail/OpenLedger.cpp b/src/xrpld/app/ledger/detail/OpenLedger.cpp index 0694e425e9..3bee4b9d13 100644 --- a/src/xrpld/app/ledger/detail/OpenLedger.cpp +++ b/src/xrpld/app/ledger/detail/OpenLedger.cpp @@ -48,26 +48,26 @@ OpenLedger::OpenLedger( bool OpenLedger::empty() const { - std::scoped_lock const lock(modify_mutex_); + std::scoped_lock const lock(modifyMutex_); return current_->txCount() == 0; } std::shared_ptr OpenLedger::current() const { - std::scoped_lock const lock(current_mutex_); + std::scoped_lock const lock(currentMutex_); return current_; } bool OpenLedger::modify(modify_type const& f) { - std::scoped_lock const lock1(modify_mutex_); + std::scoped_lock const lock1(modifyMutex_); auto next = std::make_shared(*current_); auto const changed = f(*next, j_); if (changed) { - std::scoped_lock const lock2(current_mutex_); + std::scoped_lock const lock2(currentMutex_); current_ = std::move(next); } return changed; @@ -96,7 +96,7 @@ OpenLedger::accept( // Block calls to modify, otherwise // new tx going into the open ledger // would get lost. - std::scoped_lock const lock1(modify_mutex_); + std::scoped_lock const lock1(modifyMutex_); // Apply tx from the current open view if (!current_->txs.empty()) { @@ -154,7 +154,7 @@ OpenLedger::accept( } // Switch to the new open view - std::scoped_lock const lock2(current_mutex_); + std::scoped_lock const lock2(currentMutex_); current_ = std::move(next); } diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index 23fa9865ed..a18462f0f7 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -286,14 +286,14 @@ public: return 1; #else - if (config.IO_WORKERS > 0) - return config.IO_WORKERS; + if (config.ioWorkers > 0) + return config.ioWorkers; auto const cores = std::thread::hardware_concurrency(); // Use a single thread when running on under-provisioned systems // or if we are configured to use minimal resources. - if ((cores == 1) || ((config.NODE_SIZE == 0) && (cores == 2))) + if ((cores == 1) || ((config.nodeSize == 0) && (cores == 2))) return 1; // Otherwise, prefer six threads. @@ -316,7 +316,7 @@ public: // PerfLog must be started before any other threads are launched. , perfLog_( perf::makePerfLog( - perf::setupPerfLog(config_->section("perf"), config_->CONFIG_DIR), + perf::setupPerfLog(config_->section("perf"), config_->configDir), *this, logs_->journal("PerfLog"), [this] { signalStop("PerfLog"); })) @@ -326,22 +326,22 @@ public: , jobQueue_( std::make_unique( [](std::unique_ptr const& config) { - if (config->standalone() && !config->FORCE_MULTI_THREAD) + if (config->standalone() && !config->forceMultiThread) return 1; - if (config->WORKERS) - return config->WORKERS; + if (config->workers) + return config->workers; auto count = 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. - if (config->NODE_SIZE >= 4 && count >= 16) + if (config->nodeSize >= 4 && count >= 16) { count = 6 + std::min(count, 8); } - else if (config->NODE_SIZE >= 3 && count >= 8) + else if (config->nodeSize >= 3 && count >= 8) { count = 4 + std::min(count, 6); } @@ -370,16 +370,16 @@ public: std::chrono::minutes(1), stopwatch(), logs_->journal("CachedSLEs")) - , networkIDService_(std::make_unique(config_->NETWORK_ID)) + , networkIDService_(std::make_unique(config_->networkId)) , validatorKeys_(*config_, journal_) , resourceManager_( Resource::makeManager(collectorManager_->collector(), logs_->journal("Resource"))) , nodeStore_(shaMapStore_->makeNodeStore( - config_->PREFETCH_WORKERS > 0 ? config_->PREFETCH_WORKERS : 4)) + config_->prefetchWorkers > 0 ? config_->prefetchWorkers : 4)) , nodeFamily_(*this, *collectorManager_) , orderBookDB_(makeOrderBookDb( *this, - {.pathSearchMax = config_->PATH_SEARCH_MAX, .standalone = config_->standalone()})) + {.pathSearchMax = config_->pathSearchMax, .standalone = config_->standalone()})) , pathRequestManager_( std::make_unique( *this, @@ -415,8 +415,8 @@ public: *this, stopwatch(), config_->standalone(), - config_->NETWORK_QUORUM, - config_->START_VALID, + config_->networkQuorum, + config_->startValid, *jobQueue_, *ledgerMaster_, validatorKeys_, @@ -435,7 +435,7 @@ public: *timeKeeper_, config_->legacy("database_path"), logs_->journal("ValidatorList"), - config_->VALIDATION_QUORUM)) + config_->validationQuorum)) , validatorSites_(std::make_unique(*this)) , serverHandler_(makeServerHandler( *this, @@ -918,7 +918,7 @@ public: { using namespace std::chrono; sweepTimer_.expires_after( - seconds{config_->SWEEP_INTERVAL.value_or( + seconds{config_->sweepInterval.value_or( config_->getValueFor(SizedItem::SweepInterval))}); sweepTimer_.async_wait(std::move(*optionalCountedHandler)); } @@ -1226,7 +1226,7 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) amendmentTable_ = makeAmendmentTable( *this, - config().AMENDMENT_MAJORITY_TIME, + config().amendmentMajorityTime, supported, upVoted, downVoted, @@ -1235,7 +1235,7 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) Pathfinder::initPathTable(); - auto const startUp = config_->START_UP; + auto const startUp = config_->startUp; JLOG(journal_.debug()) << "startUp: " << startUp; if (startUp == StartUpType::Fresh) { @@ -1250,13 +1250,13 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) JLOG(journal_.info()) << "Loading specified Ledger"; if (!loadOldLedger( - config_->START_LEDGER, + config_->startLedger, startUp == StartUpType::Replay, startUp == StartUpType::LoadFile, - config_->TRAP_TX_HASH)) + config_->trapTxHash)) { JLOG(journal_.error()) << "The specified ledger could not be loaded."; - if (config_->FAST_LOAD) + if (config_->fastLoad) { // Fall back to syncing from the network, such as // when there's no existing data. @@ -1282,7 +1282,7 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) startGenesisLedger(); } - if (auto const& forcedRange = config().FORCED_LEDGER_RANGE_PRESENT) + if (auto const& forcedRange = config().forcedLedgerRangePresent) { ledgerMaster_->setLedgerRangePresent(forcedRange->first, forcedRange->second); } @@ -1328,7 +1328,7 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) localSigningKey, config().section(SECTION_VALIDATORS).values(), config().section(SECTION_VALIDATOR_LIST_KEYS).values(), - config().VALIDATOR_LIST_THRESHOLD)) + config().validatorListThreshold)) { JLOG(journal_.fatal()) << "Invalid entry in validator configuration."; return false; @@ -1399,7 +1399,7 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) { // Should this message be here, conceptually? In theory this sort // of message, if displayed, should be displayed from PeerFinder. - if (config_->PEER_PRIVATE && config_->IPS_FIXED.empty()) + if (config_->peerPrivate && config_->ipsFixed.empty()) { JLOG(journal_.warn()) << "No outbound peer connections will be made"; } @@ -1659,14 +1659,14 @@ ApplicationImp::fdRequired() const void ApplicationImp::startGenesisLedger() { - std::vector const initialAmendments = (config_->START_UP == StartUpType::Fresh) + std::vector const initialAmendments = (config_->startUp == StartUpType::Fresh) ? amendmentTable_->getDesired() : std::vector{}; std::shared_ptr const genesis = std::make_shared( kCreateGenesis, Rules{config_->features}, - config_->FEES.toFees(), + config_->fees.toFees(), initialAmendments, nodeFamily_); ledgerMaster_->storeLedger(genesis); @@ -1690,7 +1690,7 @@ ApplicationImp::getLastFullLedger() try { auto const [ledger, seq, hash] = - getLatestLedger(Rules{config_->features}, config_->FEES.toFees(), *this); + getLatestLedger(Rules{config_->features}, config_->fees.toFees(), *this); if (!ledger) return ledger; @@ -1802,7 +1802,7 @@ ApplicationImp::loadLedgerFromFile(std::string const& name) } auto loadLedger = std::make_shared( - seq, closeTime, Rules{config_->features}, config_->FEES.toFees(), nodeFamily_); + seq, closeTime, Rules{config_->features}, config_->fees.toFees(), nodeFamily_); loadLedger->setTotalDrops(totalDrops); for (json::UInt index = 0; index < ledger.get().size(); ++index) @@ -1883,7 +1883,7 @@ ApplicationImp::loadOldLedger( if (hash.parseHex(ledgerID)) { loadLedger = - loadByHash(hash, Rules{config_->features}, config_->FEES.toFees(), *this); + loadByHash(hash, Rules{config_->features}, config_->fees.toFees(), *this); if (!loadLedger) { @@ -1912,7 +1912,7 @@ ApplicationImp::loadOldLedger( if (beast::lexicalCastChecked(index, ledgerID)) { loadLedger = - loadByIndex(index, Rules{config_->features}, config_->FEES.toFees(), *this); + loadByIndex(index, Rules{config_->features}, config_->fees.toFees(), *this); } } @@ -1931,7 +1931,7 @@ ApplicationImp::loadOldLedger( loadLedger = loadByHash( replayLedger->header().parentHash, Rules{config_->features}, - config_->FEES.toFees(), + config_->fees.toFees(), *this); if (!loadLedger) { @@ -2078,7 +2078,7 @@ ApplicationImp::loadOldLedger( bool ApplicationImp::serverOkay(std::string& reason) { - if (!config().ELB_SUPPORT) + if (!config().elbSupport) return true; if (isStopping()) diff --git a/src/xrpld/app/main/BasicApp.cpp b/src/xrpld/app/main/BasicApp.cpp index 71138c6517..c530c170cb 100644 --- a/src/xrpld/app/main/BasicApp.cpp +++ b/src/xrpld/app/main/BasicApp.cpp @@ -9,14 +9,14 @@ BasicApp::BasicApp(std::size_t numberOfThreads) { - work_.emplace(boost::asio::make_work_guard(io_context_)); + work_.emplace(boost::asio::make_work_guard(ioContext_)); threads_.reserve(numberOfThreads); for (std::size_t i = 0; i < numberOfThreads; ++i) { threads_.emplace_back([this, i]() { beast::setCurrentThreadName("io svc #" + std::to_string(i)); - this->io_context_.run(); + this->ioContext_.run(); }); } } diff --git a/src/xrpld/app/main/BasicApp.h b/src/xrpld/app/main/BasicApp.h index d55ab858db..5ba7c719e3 100644 --- a/src/xrpld/app/main/BasicApp.h +++ b/src/xrpld/app/main/BasicApp.h @@ -12,7 +12,7 @@ class BasicApp private: std::optional> work_; std::vector threads_; - boost::asio::io_context io_context_; + boost::asio::io_context ioContext_; public: BasicApp(std::size_t numberOfThreads); @@ -21,7 +21,7 @@ public: boost::asio::io_context& getIoContext() { - return io_context_; + return ioContext_; } [[nodiscard]] size_t diff --git a/src/xrpld/app/main/Main.cpp b/src/xrpld/app/main/Main.cpp index ed1d3333d8..f470a2d80f 100644 --- a/src/xrpld/app/main/Main.cpp +++ b/src/xrpld/app/main/Main.cpp @@ -627,7 +627,7 @@ run(int argc, char** argv) { throw std::runtime_error("Invalid force_ledger_present_range parameter"); } - config->FORCED_LEDGER_RANGE_PRESENT.emplace(r[0], r[1]); + config->forcedLedgerRangePresent.emplace(r[0], r[1]); } else { @@ -646,7 +646,7 @@ run(int argc, char** argv) if (vm.contains("start")) { - config->START_UP = StartUpType::Fresh; + config->startUp = StartUpType::Fresh; } if (vm.contains("import")) @@ -654,17 +654,17 @@ run(int argc, char** argv) if (vm.contains("ledger")) { - config->START_LEDGER = vm["ledger"].as(); + config->startLedger = vm["ledger"].as(); if (vm.contains("replay")) { - config->START_UP = StartUpType::Replay; + config->startUp = StartUpType::Replay; if (vm.contains("trap_tx_hash")) { uint256 tmp = {}; auto hash = vm["trap_tx_hash"].as(); if (tmp.parseHex(hash)) { - config->TRAP_TX_HASH = tmp; + config->trapTxHash = tmp; } else { @@ -677,17 +677,17 @@ run(int argc, char** argv) } else { - config->START_UP = StartUpType::Load; + config->startUp = StartUpType::Load; } } else if (vm.contains("ledgerfile")) { - config->START_LEDGER = vm["ledgerfile"].as(); - config->START_UP = StartUpType::LoadFile; + config->startLedger = vm["ledgerfile"].as(); + config->startUp = StartUpType::LoadFile; } - else if (vm.contains("load") || config->FAST_LOAD) + else if (vm.contains("load") || config->fastLoad) { - config->START_UP = StartUpType::Load; + config->startUp = StartUpType::Load; } if (vm.contains("trap_tx_hash") && !vm.contains("replay")) @@ -696,20 +696,20 @@ run(int argc, char** argv) return -1; } - if (vm.contains("net") && !config->FAST_LOAD) + if (vm.contains("net") && !config->fastLoad) { - if ((config->START_UP == StartUpType::Load) || (config->START_UP == StartUpType::Replay)) + if ((config->startUp == StartUpType::Load) || (config->startUp == StartUpType::Replay)) { std::cerr << "Net and load/replay options are incompatible" << std::endl; return -1; } - config->START_UP = StartUpType::Network; + config->startUp = StartUpType::Network; } if (vm.contains("valid")) { - config->START_VALID = true; + config->startValid = true; } // Override the RPC destination IP address. This must @@ -747,15 +747,15 @@ run(int argc, char** argv) } } - config->rpc_ip = std::move(*endpoint); + config->rpcIp = std::move(*endpoint); } if (vm.contains("quorum")) { try { - config->VALIDATION_QUORUM = vm["quorum"].as(); - if (config->VALIDATION_QUORUM == std::size_t{}) + config->validationQuorum = vm["quorum"].as(); + if (config->validationQuorum == std::size_t{}) { throw std::domain_error("0"); } diff --git a/src/xrpld/app/misc/FeeVoteImpl.cpp b/src/xrpld/app/misc/FeeVoteImpl.cpp index 0a55c20d29..c4e6c3cdc2 100644 --- a/src/xrpld/app/misc/FeeVoteImpl.cpp +++ b/src/xrpld/app/misc/FeeVoteImpl.cpp @@ -135,13 +135,10 @@ FeeVoteImpl::doValidation(Fees const& lastFees, Rules const& rules, STValidation v[sfield] = target; } }; - vote(lastFees.base, target_.reference_fee, "base fee", sfBaseFeeDrops); - vote(lastFees.reserve, target_.account_reserve, "base reserve", sfReserveBaseDrops); + vote(lastFees.base, target_.referenceFee, "base fee", sfBaseFeeDrops); + vote(lastFees.reserve, target_.accountReserve, "base reserve", sfReserveBaseDrops); vote( - lastFees.increment, - target_.owner_reserve, - "reserve increment", - sfReserveIncrementDrops); + lastFees.increment, target_.ownerReserve, "reserve increment", sfReserveIncrementDrops); } else { @@ -162,11 +159,11 @@ FeeVoteImpl::doValidation(Fees const& lastFees, Rules const& rules, STValidation } }; - vote(lastFees.base, target_.reference_fee, to64, "base fee", sfBaseFee); - vote(lastFees.reserve, target_.account_reserve, to32, "base reserve", sfReserveBase); + vote(lastFees.base, target_.referenceFee, to64, "base fee", sfBaseFee); + vote(lastFees.reserve, target_.accountReserve, to32, "base reserve", sfReserveBase); vote( lastFees.increment, - target_.owner_reserve, + target_.ownerReserve, to32, "reserve increment", sfReserveIncrement); @@ -184,11 +181,11 @@ FeeVoteImpl::doVoting( lastClosedLedger && isFlagLedger(lastClosedLedger->seq()), "xrpl::FeeVoteImpl::doVoting : has a flag ledger"); - detail::VotableValue baseFeeVote(lastClosedLedger->fees().base, target_.reference_fee); + detail::VotableValue baseFeeVote(lastClosedLedger->fees().base, target_.referenceFee); - detail::VotableValue baseReserveVote(lastClosedLedger->fees().reserve, target_.account_reserve); + detail::VotableValue baseReserveVote(lastClosedLedger->fees().reserve, target_.accountReserve); - detail::VotableValue incReserveVote(lastClosedLedger->fees().increment, target_.owner_reserve); + detail::VotableValue incReserveVote(lastClosedLedger->fees().increment, target_.ownerReserve); auto const& rules = lastClosedLedger->rules(); if (rules.enabled(featureXRPFees)) diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index d6f9d10f05..12c79b821c 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -327,7 +327,7 @@ public: validatorKeys.keys ? validatorKeys.keys->masterPublicKey : decltype(validatorMasterPK_){}) , ledgerMaster_(ledgerMaster) - , job_queue_(jobQueue) + , jobQueue_(jobQueue) , standalone_(standalone) , minPeerCount_(startValid ? 0 : minPeerCount) , stats_(std::bind(&NetworkOPsImp::collectMetrics, this), collector) @@ -828,7 +828,7 @@ private: ServerFeeSummary lastFeeSummary_; - JobQueue& job_queue_; + JobQueue& jobQueue_; // Whether we are in standalone mode. bool const standalone_; @@ -853,34 +853,34 @@ private: template Stats(Handler const& handler, beast::insight::Collector::ptr const& collector) : hook(collector->makeHook(handler)) - , disconnected_duration( + , disconnectedDuration( collector->makeGauge("State_Accounting", "Disconnected_duration")) - , connected_duration(collector->makeGauge("State_Accounting", "Connected_duration")) - , syncing_duration(collector->makeGauge("State_Accounting", "Syncing_duration")) - , tracking_duration(collector->makeGauge("State_Accounting", "Tracking_duration")) - , full_duration(collector->makeGauge("State_Accounting", "Full_duration")) - , disconnected_transitions( + , connectedDuration(collector->makeGauge("State_Accounting", "Connected_duration")) + , syncingDuration(collector->makeGauge("State_Accounting", "Syncing_duration")) + , trackingDuration(collector->makeGauge("State_Accounting", "Tracking_duration")) + , fullDuration(collector->makeGauge("State_Accounting", "Full_duration")) + , disconnectedTransitions( collector->makeGauge("State_Accounting", "Disconnected_transitions")) - , connected_transitions( + , connectedTransitions( collector->makeGauge("State_Accounting", "Connected_transitions")) - , syncing_transitions(collector->makeGauge("State_Accounting", "Syncing_transitions")) - , tracking_transitions(collector->makeGauge("State_Accounting", "Tracking_transitions")) - , full_transitions(collector->makeGauge("State_Accounting", "Full_transitions")) + , syncingTransitions(collector->makeGauge("State_Accounting", "Syncing_transitions")) + , trackingTransitions(collector->makeGauge("State_Accounting", "Tracking_transitions")) + , fullTransitions(collector->makeGauge("State_Accounting", "Full_transitions")) { } beast::insight::Hook hook; - beast::insight::Gauge disconnected_duration; - beast::insight::Gauge connected_duration; - beast::insight::Gauge syncing_duration; - beast::insight::Gauge tracking_duration; - beast::insight::Gauge full_duration; + beast::insight::Gauge disconnectedDuration; + beast::insight::Gauge connectedDuration; + beast::insight::Gauge syncingDuration; + beast::insight::Gauge trackingDuration; + beast::insight::Gauge fullDuration; - beast::insight::Gauge disconnected_transitions; - beast::insight::Gauge connected_transitions; - beast::insight::Gauge syncing_transitions; - beast::insight::Gauge tracking_transitions; - beast::insight::Gauge full_transitions; + beast::insight::Gauge disconnectedTransitions; + beast::insight::Gauge connectedTransitions; + beast::insight::Gauge syncingTransitions; + beast::insight::Gauge trackingTransitions; + beast::insight::Gauge fullTransitions; }; std::mutex statsMutex_; // Mutex to lock stats_ @@ -990,7 +990,7 @@ NetworkOPsImp::setTimer( // Only start the timer if waitHandlerCounter_ is not yet joined. if (auto optionalCountedHandler = waitHandlerCounter_.wrap([this, onExpire, onError](boost::system::error_code const& e) { - if ((e.value() == boost::system::errc::success) && (!job_queue_.isStopped())) + if ((e.value() == boost::system::errc::success) && (!jobQueue_.isStopped())) { onExpire(); } @@ -1017,7 +1017,7 @@ NetworkOPsImp::setHeartbeatTimer() heartbeatTimer_, consensus_.parms().ledgerGRANULARITY, [this]() { - job_queue_.addJob(JtNetopTimer, "NetHeart", [this]() { processHeartbeatTimer(); }); + jobQueue_.addJob(JtNetopTimer, "NetHeart", [this]() { processHeartbeatTimer(); }); }, [this]() { setHeartbeatTimer(); }); } @@ -1031,7 +1031,7 @@ NetworkOPsImp::setClusterTimer() clusterTimer_, 10s, [this]() { - job_queue_.addJob(JtNetopCluster, "NetCluster", [this]() { processClusterTimer(); }); + jobQueue_.addJob(JtNetopCluster, "NetCluster", [this]() { processClusterTimer(); }); }, [this]() { setClusterTimer(); }); } @@ -1247,7 +1247,7 @@ NetworkOPsImp::submitTransaction(std::shared_ptr const& iTrans) auto tx = std::make_shared(trans, reason, registry_.get().getApp()); - job_queue_.addJob(JtTransaction, "SubmitTxn", [this, tx]() { + jobQueue_.addJob(JtTransaction, "SubmitTxn", [this, tx]() { auto t = tx; processTransaction(t, false, false, FailHard::No); }); @@ -1312,7 +1312,7 @@ NetworkOPsImp::processTransaction( bool bLocal, FailHard failType) { - auto ev = job_queue_.makeLoadEvent(JtTxnProc, "ProcessTXN"); + auto ev = jobQueue_.makeLoadEvent(JtTxnProc, "ProcessTXN"); // preProcessTransaction can change our pointer if (!preProcessTransaction(transaction)) @@ -1344,7 +1344,7 @@ NetworkOPsImp::doTransactionAsync( if (dispatchState_ == DispatchState::None) { - if (job_queue_.addJob(JtBatch, "TxBatchAsync", [this]() { transactionBatch(); })) + if (jobQueue_.addJob(JtBatch, "TxBatchAsync", [this]() { transactionBatch(); })) { dispatchState_ = DispatchState::Scheduled; } @@ -1389,7 +1389,7 @@ NetworkOPsImp::doTransactionSyncBatch( if (!transactions_.empty()) { // More transactions need to be applied, but by another job. - if (job_queue_.addJob(JtBatch, "TxBatchSync", [this]() { transactionBatch(); })) + if (jobQueue_.addJob(JtBatch, "TxBatchSync", [this]() { transactionBatch(); })) { dispatchState_ = DispatchState::Scheduled; } @@ -1401,7 +1401,7 @@ NetworkOPsImp::doTransactionSyncBatch( void NetworkOPsImp::processTransactionSet(CanonicalTXSet const& set) { - auto ev = job_queue_.makeLoadEvent(JtTxnProc, "ProcessTXNSet"); + auto ev = jobQueue_.makeLoadEvent(JtTxnProc, "ProcessTXNSet"); std::vector> candidates; candidates.reserve(set.size()); for (auto const& [_, tx] : set) @@ -2571,7 +2571,7 @@ NetworkOPsImp::recvValidation(std::shared_ptr const& val, std::str // We will always relay trusted validations; if configured, we will // also relay all untrusted validations. - return registry_.get().getApp().config().RELAY_UNTRUSTED_VALIDATIONS == 1 || val->isTrusted(); + return registry_.get().getApp().config().relayUntrustedValidations == 1 || val->isTrusted(); } json::Value @@ -2631,8 +2631,8 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) info[jss::hostid] = getHostId(admin); // domain: if configured with a domain, report it: - if (!registry_.get().getApp().config().SERVER_DOMAIN.empty()) - info[jss::server_domain] = registry_.get().getApp().config().SERVER_DOMAIN; + if (!registry_.get().getApp().config().serverDomain.empty()) + info[jss::server_domain] = registry_.get().getApp().config().serverDomain; info[jss::build_version] = BuildInfo::getVersionString(); @@ -2652,7 +2652,7 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) // Note: By default the node size is "tiny". When parsing it's an error if the final // NODE_SIZE is over 4 so below code should be safe. // NOLINTNEXTLINE(bugprone-switch-missing-default-case) - switch (registry_.get().getApp().config().NODE_SIZE) + switch (registry_.get().getApp().config().nodeSize) { case 0: info[jss::node_size] = "tiny"; @@ -2787,7 +2787,7 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) // info[jss::consensus] = consensus_.getJson(); if (admin) - info[jss::load] = job_queue_.getJson(); + info[jss::load] = jobQueue_.getJson(); if (auto const netid = registry_.get().getOverlay().networkID()) info[jss::network_id] = static_cast(*netid); @@ -2952,8 +2952,8 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) { // Don't publish admin ports for non-admin users if (!admin && - !(port.admin_nets_v4.empty() && port.admin_nets_v6.empty() && - port.admin_user.empty() && port.admin_password.empty())) + !(port.adminNetsV4.empty() && port.adminNetsV6.empty() && port.adminUser.empty() && + port.adminPassword.empty())) continue; std::vector proto; // NOLINTNEXTLINE(modernize-use-ranges) @@ -3170,14 +3170,14 @@ NetworkOPsImp::reportFeeChange() // only schedule the job if something has changed if (f != lastFeeSummary_) { - job_queue_.addJob(JtClientFeeChange, "PubFee", [this]() { pubServer(); }); + jobQueue_.addJob(JtClientFeeChange, "PubFee", [this]() { pubServer(); }); } } void NetworkOPsImp::reportConsensusStateChange(ConsensusPhase phase) { - job_queue_.addJob(JtClientConsensus, "PubCons", [this, phase]() { pubConsensus(phase); }); + jobQueue_.addJob(JtClientConsensus, "PubCons", [this, phase]() { pubConsensus(phase); }); } inline void @@ -4626,26 +4626,25 @@ NetworkOPsImp::collectMetrics() counters[static_cast(mode)].dur += current; std::scoped_lock const lock(statsMutex_); - stats_.disconnected_duration.set( + stats_.disconnectedDuration.set( counters[static_cast(OperatingMode::DISCONNECTED)].dur.count()); - stats_.connected_duration.set( + stats_.connectedDuration.set( counters[static_cast(OperatingMode::CONNECTED)].dur.count()); - stats_.syncing_duration.set( + stats_.syncingDuration.set( counters[static_cast(OperatingMode::SYNCING)].dur.count()); - stats_.tracking_duration.set( + stats_.trackingDuration.set( counters[static_cast(OperatingMode::TRACKING)].dur.count()); - stats_.full_duration.set(counters[static_cast(OperatingMode::FULL)].dur.count()); + stats_.fullDuration.set(counters[static_cast(OperatingMode::FULL)].dur.count()); - stats_.disconnected_transitions.set( + stats_.disconnectedTransitions.set( counters[static_cast(OperatingMode::DISCONNECTED)].transitions); - stats_.connected_transitions.set( + stats_.connectedTransitions.set( counters[static_cast(OperatingMode::CONNECTED)].transitions); - stats_.syncing_transitions.set( + stats_.syncingTransitions.set( counters[static_cast(OperatingMode::SYNCING)].transitions); - stats_.tracking_transitions.set( + stats_.trackingTransitions.set( counters[static_cast(OperatingMode::TRACKING)].transitions); - stats_.full_transitions.set( - counters[static_cast(OperatingMode::FULL)].transitions); + stats_.fullTransitions.set(counters[static_cast(OperatingMode::FULL)].transitions); } void diff --git a/src/xrpld/app/misc/SHAMapStoreImp.cpp b/src/xrpld/app/misc/SHAMapStoreImp.cpp index 556c6f9239..fc3f71c0cc 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.cpp +++ b/src/xrpld/app/misc/SHAMapStoreImp.cpp @@ -116,7 +116,7 @@ SHAMapStoreImp::SHAMapStoreImp( section.set("cache_mb", std::to_string(config.getValueFor(SizedItem::HashNodeDbCache))); } - if (!section.exists("filter_bits") && (config.NODE_SIZE >= 2)) + if (!section.exists("filter_bits") && (config.nodeSize >= 2)) section.set("filter_bits", "10"); } @@ -148,15 +148,15 @@ SHAMapStoreImp::SHAMapStoreImp( "online_delete must be at least " + std::to_string(minInterval)); } - if (config.LEDGER_HISTORY > deleteInterval_) + if (config.ledgerHistory > deleteInterval_) { Throw( "online_delete must not be less than ledger_history " "(currently " + - std::to_string(config.LEDGER_HISTORY) + ")"); + std::to_string(config.ledgerHistory) + ")"); } - state_db_.init(config, dbName_); + stateDb_.init(config, dbName_); dbPaths(); } } @@ -169,14 +169,14 @@ SHAMapStoreImp::makeNodeStore(int readThreads) if (deleteInterval_ != 0u) { - SavedState state = state_db_.getState(); + SavedState state = stateDb_.getState(); auto writableBackend = makeBackendRotating(state.writableDb); auto archiveBackend = makeBackendRotating(state.archiveDb); if (state.writableDb.empty()) { state.writableDb = writableBackend->getName(); state.archiveDb = archiveBackend->getName(); - state_db_.setState(state); + stateDb_.setState(state); } // Create NodeStore with two backends to allow online deletion of @@ -251,12 +251,12 @@ void SHAMapStoreImp::run() { beast::setCurrentThreadName("SHAMapStore"); - LedgerIndex lastRotated = state_db_.getState().lastRotated; + LedgerIndex lastRotated = stateDb_.getState().lastRotated; netOPs_ = &app_.getOPs(); ledgerMaster_ = &app_.getLedgerMaster(); if (advisoryDelete_) - canDelete_ = state_db_.getCanDelete(); + canDelete_ = stateDb_.getCanDelete(); while (true) { @@ -286,7 +286,7 @@ SHAMapStoreImp::run() if (lastRotated == 0u) { lastRotated = validatedSeq; - state_db_.setLastRotated(lastRotated); + stateDb_.setLastRotated(lastRotated); } bool const readyToRotate = validatedSeq >= lastRotated + deleteInterval_ && @@ -354,7 +354,7 @@ SHAMapStoreImp::run() savedState.writableDb = writableName; savedState.archiveDb = archiveName; savedState.lastRotated = lastRotated; - state_db_.setState(savedState); + stateDb_.setState(savedState); clearCaches(validatedSeq); }); @@ -383,7 +383,7 @@ SHAMapStoreImp::dbPaths() boost::filesystem::create_directories(dbPath); } - SavedState state = state_db_.getState(); + SavedState state = stateDb_.getState(); { auto update = [&dbPath](std::string& sPath) { @@ -403,7 +403,7 @@ SHAMapStoreImp::dbPaths() if (update(state.writableDb)) { update(state.archiveDb); - state_db_.setState(state); + stateDb_.setState(state); } } diff --git a/src/xrpld/app/misc/SHAMapStoreImp.h b/src/xrpld/app/misc/SHAMapStoreImp.h index 1eb2beb561..4f0ce04e0d 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.h +++ b/src/xrpld/app/misc/SHAMapStoreImp.h @@ -66,7 +66,7 @@ private: NodeStore::Scheduler& scheduler_; beast::Journal const journal_; NodeStore::DatabaseRotating* dbRotating_ = nullptr; - SavedStateDB state_db_; + SavedStateDB stateDb_; std::thread thread_; bool stop_ = false; bool healthy_ = true; @@ -113,7 +113,7 @@ public: { if (advisoryDelete_) canDelete_ = seq; - return state_db_.setCanDelete(seq); + return stateDb_.setCanDelete(seq); } bool @@ -127,7 +127,7 @@ public: LedgerIndex getLastRotated() override { - return state_db_.getState().lastRotated; + return stateDb_.getState().lastRotated; } // All ledgers before and including this are unprotected diff --git a/src/xrpld/app/misc/ValidatorSite.h b/src/xrpld/app/misc/ValidatorSite.h index c4d2545217..55f060baf1 100644 --- a/src/xrpld/app/misc/ValidatorSite.h +++ b/src/xrpld/app/misc/ValidatorSite.h @@ -96,10 +96,10 @@ private: Application& app_; beast::Journal const j_; - // If both mutex are to be locked at the same time, `sites_mutex_` must be - // locked before `state_mutex_` or we may deadlock. - std::mutex mutable sites_mutex_; - std::mutex mutable state_mutex_; + // If both mutex are to be locked at the same time, `sitesMutex_` must be + // locked before `stateMutex_` or we may deadlock. + std::mutex mutable sitesMutex_; + std::mutex mutable stateMutex_; std::condition_variable cv_; std::weak_ptr work_; diff --git a/src/xrpld/app/misc/detail/ValidatorSite.cpp b/src/xrpld/app/misc/detail/ValidatorSite.cpp index 7b5d7ee951..76fd078174 100644 --- a/src/xrpld/app/misc/detail/ValidatorSite.cpp +++ b/src/xrpld/app/misc/detail/ValidatorSite.cpp @@ -114,7 +114,7 @@ ValidatorSite::ValidatorSite( ValidatorSite::~ValidatorSite() { - std::unique_lock lock{state_mutex_}; + std::unique_lock lock{stateMutex_}; if (timer_.expiry() > clock_type::time_point{}) { if (!stopping_) @@ -141,7 +141,7 @@ ValidatorSite::load(std::vector const& siteURIs) { JLOG(j_.debug()) << "Loading configured validator list sites"; - std::scoped_lock const lock{sites_mutex_}; + std::scoped_lock const lock{sitesMutex_}; return load(siteURIs, lock); } @@ -178,8 +178,8 @@ ValidatorSite::load( void ValidatorSite::start() { - std::scoped_lock const l0{sites_mutex_}; - std::scoped_lock const l1{state_mutex_}; + std::scoped_lock const l0{sitesMutex_}; + std::scoped_lock const l1{stateMutex_}; if (timer_.expiry() == clock_type::time_point{}) setTimer(l0, l1); } @@ -187,14 +187,14 @@ ValidatorSite::start() void ValidatorSite::join() { - std::unique_lock lock{state_mutex_}; + std::unique_lock lock{stateMutex_}; cv_.wait(lock, [&] { return !pending_; }); } void ValidatorSite::stop() { - std::unique_lock lock{state_mutex_}; + std::unique_lock lock{stateMutex_}; stopping_ = true; // work::cancel() must be called before the // cv wait in order to kick any asio async operations @@ -246,7 +246,7 @@ ValidatorSite::makeRequest( sites_[siteIdx].activeResource = resource; std::shared_ptr sp; auto timeoutCancel = [this]() { - std::scoped_lock const lockState{state_mutex_}; + std::scoped_lock const lockState{stateMutex_}; // docs indicate cancel_one() can throw, but this // should be reconsidered if it changes to noexcept try @@ -312,7 +312,7 @@ ValidatorSite::makeRequest( sp->run(); // start a timer for the request, which shouldn't take more // than requestTimeout_ to complete - std::scoped_lock const lockState{state_mutex_}; + std::scoped_lock const lockState{stateMutex_}; timer_.expires_after(requestTimeout_); timer_.async_wait([this, siteIdx](boost::system::error_code const& ec) { this->onRequestTimeout(siteIdx, ec); @@ -326,7 +326,7 @@ ValidatorSite::onRequestTimeout(std::size_t siteIdx, error_code const& ec) return; { - std::scoped_lock const lockSite{sites_mutex_}; + std::scoped_lock const lockSite{sitesMutex_}; // In some circumstances, both this function and the response // handler (onSiteFetch or onTextFetch) can get queued and // processed. In all observed cases, the response handler @@ -343,7 +343,7 @@ ValidatorSite::onRequestTimeout(std::size_t siteIdx, error_code const& ec) "already been processed"; } - std::scoped_lock const lockState{state_mutex_}; + std::scoped_lock const lockState{stateMutex_}; if (auto sp = work_.lock()) sp->cancel(); } @@ -362,7 +362,7 @@ ValidatorSite::onTimer(std::size_t siteIdx, error_code const& ec) try { - std::scoped_lock const lock{sites_mutex_}; + std::scoped_lock const lock{sitesMutex_}; sites_[siteIdx].nextRefresh = clock_type::now() + sites_[siteIdx].refreshInterval; sites_[siteIdx].redirCount = 0; // the WorkSSL client ctor can throw if SSL init fails @@ -545,7 +545,7 @@ ValidatorSite::onSiteFetch( detail::response_type const& res, std::size_t siteIdx) { - std::scoped_lock lockSites{sites_mutex_}; + std::scoped_lock lockSites{sitesMutex_}; { if (endpoint != endpoint_type{}) sites_[siteIdx].lastRequestEndpoint = endpoint; @@ -617,7 +617,7 @@ ValidatorSite::onSiteFetch( sites_[siteIdx].activeResource.reset(); } - std::scoped_lock const lockState{state_mutex_}; + std::scoped_lock const lockState{stateMutex_}; fetching_ = false; if (!stopping_) setTimer(lockSites, lockState); @@ -630,7 +630,7 @@ ValidatorSite::onTextFetch( std::string const& res, std::size_t siteIdx) { - std::scoped_lock const lockSites{sites_mutex_}; + std::scoped_lock const lockSites{sitesMutex_}; { try { @@ -657,7 +657,7 @@ ValidatorSite::onTextFetch( sites_[siteIdx].activeResource.reset(); } - std::scoped_lock const lockState{state_mutex_}; + std::scoped_lock const lockState{stateMutex_}; fetching_ = false; if (!stopping_) setTimer(lockSites, lockState); @@ -673,7 +673,7 @@ ValidatorSite::getJson() const json::Value jrr(json::ValueType::Object); json::Value& jSites = (jrr[jss::validator_sites] = json::ValueType::Array); { - std::scoped_lock const lock{sites_mutex_}; + std::scoped_lock const lock{sitesMutex_}; for (Site const& site : sites_) { json::Value& v = jSites.append(json::ValueType::Object); diff --git a/src/xrpld/app/misc/detail/WorkSSL.cpp b/src/xrpld/app/misc/detail/WorkSSL.cpp index 0a8d53b1a2..e1b864e81f 100644 --- a/src/xrpld/app/misc/detail/WorkSSL.cpp +++ b/src/xrpld/app/misc/detail/WorkSSL.cpp @@ -30,9 +30,9 @@ WorkSSL::WorkSSL( callback_type cb) : WorkBase(host, path, port, ios, lastEndpoint, lastStatus, cb) , context_( - config.SSL_VERIFY_DIR, - config.SSL_VERIFY_FILE, - config.SSL_VERIFY, + config.sslVerifyDir, + config.sslVerifyFile, + config.sslVerify, j, boost::asio::ssl::context::tlsv12_client) , stream_(socket_, context_.context()) diff --git a/src/xrpld/consensus/Consensus.cpp b/src/xrpld/consensus/Consensus.cpp index 5498f7cf79..d529ab2e44 100644 --- a/src/xrpld/consensus/Consensus.cpp +++ b/src/xrpld/consensus/Consensus.cpp @@ -35,7 +35,7 @@ shouldCloseLedger( << ", timeSincePrevClose: " << timeSincePrevClose.count() << "ms" << ", openTime: " << openTime.count() << "ms" << ", idleInterval: " << idleInterval.count() << "ms" - << ", ledgerMIN_CLOSE: " << parms.ledgerMIN_CLOSE.count() << "ms" + << ", ledgerMIN_CLOSE: " << parms.ledgerMinClose.count() << "ms" << ". "; using namespace std::chrono_literals; if ((prevRoundTime < -1s) || (prevRoundTime > 10min) || (timeSincePrevClose > 10min)) @@ -67,7 +67,7 @@ shouldCloseLedger( } // Preserve minimum ledger open time - if (openTime < parms.ledgerMIN_CLOSE) + if (openTime < parms.ledgerMinClose) { JLOG(j.debug()) << "Must wait minimum time before closing"; CLOG(clog) << "not closing because under ledgerMIN_CLOSE. "; @@ -175,12 +175,12 @@ checkConsensus( << " agree=" << currentAgree << " validated=" << currentFinished << " time=" << currentAgreeTime.count() << "/" << previousAgreeTime.count() << " proposing? " << proposing - << " minimum duration to reach consensus: " << parms.ledgerMIN_CONSENSUS.count() + << " minimum duration to reach consensus: " << parms.ledgerMinConsensus.count() << "ms" - << " max consensus time " << parms.ledgerMAX_CONSENSUS.count() << "ms" - << " minimum consensus percentage: " << parms.minCONSENSUS_PCT << ". "; + << " max consensus time " << parms.ledgerMaxConsensus.count() << "ms" + << " minimum consensus percentage: " << parms.minConsensusPct << ". "; - if (currentAgreeTime <= parms.ledgerMIN_CONSENSUS) + if (currentAgreeTime <= parms.ledgerMinConsensus) { CLOG(clog) << "Not reached. "; return ConsensusState::No; @@ -190,7 +190,7 @@ checkConsensus( { // Less than 3/4 of the last ledger's proposers are present; don't // rush: we may need more time. - if (currentAgreeTime < (previousAgreeTime + parms.ledgerMIN_CONSENSUS)) + if (currentAgreeTime < (previousAgreeTime + parms.ledgerMinConsensus)) { JLOG(j.trace()) << "too fast, not enough proposers"; CLOG(clog) << "Too fast, not enough proposers. Not reached. "; @@ -204,8 +204,8 @@ checkConsensus( currentAgree, currentProposers, proposing, - parms.minCONSENSUS_PCT, - currentAgreeTime > parms.ledgerMAX_CONSENSUS, + parms.minConsensusPct, + currentAgreeTime > parms.ledgerMaxConsensus, stalled, clog)) { @@ -221,8 +221,8 @@ checkConsensus( currentFinished, currentProposers, false, - parms.minCONSENSUS_PCT, - currentAgreeTime > parms.ledgerMAX_CONSENSUS, + parms.minConsensusPct, + currentAgreeTime > parms.ledgerMaxConsensus, false, clog)) { @@ -232,9 +232,9 @@ checkConsensus( } std::chrono::milliseconds const maxAgreeTime = - previousAgreeTime * parms.ledgerABANDON_CONSENSUS_FACTOR; + previousAgreeTime * parms.ledgerAbandonConsensusFactor; if (currentAgreeTime > - std::clamp(maxAgreeTime, parms.ledgerMAX_CONSENSUS, parms.ledgerABANDON_CONSENSUS)) + std::clamp(maxAgreeTime, parms.ledgerMaxConsensus, parms.ledgerAbandonConsensus)) { JLOG(j.warn()) << "consensus taken too long"; CLOG(clog) << "Consensus taken too long. "; diff --git a/src/xrpld/consensus/Consensus.h b/src/xrpld/consensus/Consensus.h index 8b4e971440..131db30ce0 100644 --- a/src/xrpld/consensus/Consensus.h +++ b/src/xrpld/consensus/Consensus.h @@ -625,7 +625,7 @@ Consensus::startRound( if (firstRound_) { // take our initial view of closeTime_ from the seed ledger - prevRoundTime_ = adaptor_.parms().ledgerIDLE_INTERVAL; + prevRoundTime_ = adaptor_.parms().ledgerIdleInterval; prevCloseTime_ = prevLedger.closeTime(); firstRound_ = false; } @@ -1169,9 +1169,9 @@ Consensus::phaseOpen(std::unique_ptr const& clog) } auto const idleInterval = std::max( - adaptor_.parms().ledgerIDLE_INTERVAL, 2 * previousLedger_.closeTimeResolution()); + adaptor_.parms().ledgerIdleInterval, 2 * previousLedger_.closeTimeResolution()); CLOG(clog) << "idle interval set to " << idleInterval.count() << "ms based on " - << "ledgerIDLE_INTERVAL: " << adaptor_.parms().ledgerIDLE_INTERVAL.count() + << "ledgerIDLE_INTERVAL: " << adaptor_.parms().ledgerIdleInterval.count() << ", previous ledger close time resolution: " << previousLedger_.closeTimeResolution().count() << "ms. "; @@ -1218,7 +1218,7 @@ Consensus::shouldPause(std::unique_ptr const& clog) << "roundTime: " << result_->roundTime.read().count() << ", " // NOLINTEND(bugprone-unchecked-optional-access) - << "max consensus time: " << parms.ledgerMAX_CONSENSUS.count() << ", " + << "max consensus time: " << parms.ledgerMaxConsensus.count() << ", " << "validators: " << totalValidators << ", " << "laggards: " << laggards << ", " << "offline: " << offline << ", " @@ -1227,7 +1227,7 @@ Consensus::shouldPause(std::unique_ptr const& clog) if ((ahead == 0u) || (laggards == 0u) || (totalValidators == 0u) || !adaptor_.validator() || !adaptor_.haveValidated() || // NOLINTNEXTLINE(bugprone-unchecked-optional-access) result_ set as shouldPause called - result_->roundTime.read() > parms.ledgerMAX_CONSENSUS) + result_->roundTime.read() > parms.ledgerMaxConsensus) { j_.debug() << "not pausing (early)" << vars.str(); CLOG(clog) << "Not pausing (early). "; @@ -1337,17 +1337,17 @@ Consensus::phaseEstablish(std::unique_ptr const& clo result_->proposers = currPeerPositions_.size(); convergePercent_ = result_->roundTime.read() * 100 / - std::max(prevRoundTime_, parms.avMIN_CONSENSUS_TIME); + std::max(prevRoundTime_, parms.avMinConsensusTime); CLOG(clog) << "convergePercent_ " << convergePercent_ << " is based on round duration so far: " << result_->roundTime.read().count() << "ms, " << "previous round duration: " << prevRoundTime_.count() << "ms, " - << "avMIN_CONSENSUS_TIME: " << parms.avMIN_CONSENSUS_TIME.count() << "ms. "; + << "avMIN_CONSENSUS_TIME: " << parms.avMinConsensusTime.count() << "ms. "; // Give everyone a chance to take an initial position - if (result_->roundTime.read() < parms.ledgerMIN_CONSENSUS) + if (result_->roundTime.read() < parms.ledgerMinConsensus) { - CLOG(clog) << "ledgerMIN_CONSENSUS not reached: " << parms.ledgerMIN_CONSENSUS.count() + CLOG(clog) << "ledgerMIN_CONSENSUS not reached: " << parms.ledgerMinConsensus.count() << "ms. "; return; } @@ -1542,7 +1542,7 @@ Consensus::updateOurPositions(std::unique_ptr const& int threshVote = participantsNeeded(participants, neededWeight); // Threshold to declare consensus - int const threshConsensus = participantsNeeded(participants, parms.avCT_CONSENSUS_PCT); + int const threshConsensus = participantsNeeded(participants, parms.avCtConsensusPct); std::stringstream ss; ss << "Proposers:" << currPeerPositions_.size() << " nw:" << neededWeight @@ -1696,7 +1696,7 @@ Consensus::haveConsensus(std::unique_ptr const& clog // Consensus has taken far too long. Drop out of the round. if (result_->state == ConsensusState::Expired) { - static auto const kMinimumCounter = parms.avalancheCutoffs.size() * parms.avMIN_ROUNDS; + static auto const kMinimumCounter = parms.avalancheCutoffs.size() * parms.avMinRounds; std::stringstream ss; if (establishCounter_ < kMinimumCounter) { diff --git a/src/xrpld/consensus/ConsensusParms.h b/src/xrpld/consensus/ConsensusParms.h index 88a6318b3c..e6dd7f046e 100644 --- a/src/xrpld/consensus/ConsensusParms.h +++ b/src/xrpld/consensus/ConsensusParms.h @@ -28,7 +28,7 @@ struct ConsensusParms This is a safety to protect against very old validations and the time it takes to adjust the close time accuracy window. */ - std::chrono::seconds const validationVALID_WALL = std::chrono::minutes{5}; + std::chrono::seconds const validationValidWall = std::chrono::minutes{5}; /** Duration a validation remains current after first observed. @@ -36,14 +36,14 @@ struct ConsensusParms first saw it. This provides faster recovery in very rare cases where the number of validations produced by the network is lower than normal */ - std::chrono::seconds const validationVALID_LOCAL = std::chrono::minutes{3}; + std::chrono::seconds const validationValidLocal = std::chrono::minutes{3}; /** Duration pre-close in which validations are acceptable. The number of seconds before a close time that we consider a validation acceptable. This protects against extreme clock errors */ - std::chrono::seconds const validationVALID_EARLY = std::chrono::minutes{3}; + std::chrono::seconds const validationValidEarly = std::chrono::minutes{3}; //! How long we consider a proposal fresh std::chrono::seconds const proposeFRESHNESS = std::chrono::seconds{20}; @@ -56,13 +56,13 @@ struct ConsensusParms // millisecond resolution. //! The percentage threshold above which we can declare consensus. - std::size_t const minCONSENSUS_PCT = 80; + std::size_t const minConsensusPct = 80; //! The duration a ledger may remain idle before closing - std::chrono::milliseconds const ledgerIDLE_INTERVAL = std::chrono::seconds{15}; + std::chrono::milliseconds const ledgerIdleInterval = std::chrono::seconds{15}; //! The number of seconds we wait minimum to ensure participation - std::chrono::milliseconds const ledgerMIN_CONSENSUS = std::chrono::milliseconds{1950}; + std::chrono::milliseconds const ledgerMinConsensus = std::chrono::milliseconds{1950}; /** The maximum amount of time to spend pausing for laggards. * @@ -70,16 +70,16 @@ struct ConsensusParms * validators don't appear to be offline that are merely waiting for * laggards. */ - std::chrono::milliseconds const ledgerMAX_CONSENSUS = std::chrono::seconds{15}; + std::chrono::milliseconds const ledgerMaxConsensus = std::chrono::seconds{15}; //! Minimum number of seconds to wait to ensure others have computed the LCL - std::chrono::milliseconds const ledgerMIN_CLOSE = std::chrono::seconds{2}; + std::chrono::milliseconds const ledgerMinClose = std::chrono::seconds{2}; //! How often we check state or change positions std::chrono::milliseconds const ledgerGRANULARITY = std::chrono::seconds{1}; //! How long to wait before completely abandoning consensus - std::size_t const ledgerABANDON_CONSENSUS_FACTOR = 10; + std::size_t const ledgerAbandonConsensusFactor = 10; /** * Maximum amount of time to give a consensus round @@ -87,7 +87,7 @@ struct ConsensusParms * Does not include the time to build the LCL, so there is no reason for a * round to go this long, regardless of how big the ledger is. */ - std::chrono::milliseconds const ledgerABANDON_CONSENSUS = std::chrono::seconds{120}; + std::chrono::milliseconds const ledgerAbandonConsensus = std::chrono::seconds{120}; /** The minimum amount of time to consider the previous round to have taken. @@ -99,7 +99,7 @@ struct ConsensusParms twice the interval between proposals (0.7s) divided by the interval between mid and late consensus ([85-50]/100). */ - std::chrono::milliseconds const avMIN_CONSENSUS_TIME = std::chrono::seconds{5}; + std::chrono::milliseconds const avMinConsensusTime = std::chrono::seconds{5}; //------------------------------------------------------------------------------ // Avalanche tuning @@ -136,16 +136,16 @@ struct ConsensusParms }; //! Percentage of nodes required to reach agreement on ledger close time - std::size_t const avCT_CONSENSUS_PCT = 75; + std::size_t const avCtConsensusPct = 75; //! Number of rounds before certain actions can happen. // (Moving to the next avalanche level, considering that votes are stalled // without consensus.) - std::size_t const avMIN_ROUNDS = 2; + std::size_t const avMinRounds = 2; //! Number of rounds before a stuck vote is considered unlikely to change //! because voting stalled - std::size_t const avSTALLED_ROUNDS = 4; + std::size_t const avStalledRounds = 4; }; inline std::pair> diff --git a/src/xrpld/consensus/DisputedTx.h b/src/xrpld/consensus/DisputedTx.h index e888b95ed6..1c85a3537d 100644 --- a/src/xrpld/consensus/DisputedTx.h +++ b/src/xrpld/consensus/DisputedTx.h @@ -79,20 +79,20 @@ public: // enough, so there's room for change. Check the times in case the state // machine is altered to allow states to loop. if (nextCutoff.consensusTime > currentCutoff.consensusTime || - avalancheCounter_ < p.avMIN_ROUNDS) + avalancheCounter_ < p.avMinRounds) return false; // We've haven't had this vote for minimum rounds yet. Things could // change. - if (proposing && currentVoteCounter_ < p.avMIN_ROUNDS) + if (proposing && currentVoteCounter_ < p.avMinRounds) return false; // If we or any peers have changed a vote in several rounds, then // things could still change. But if _either_ has not changed in that // long, we're unlikely to change our vote any time soon. (This prevents // a malicious peer from flip-flopping a vote to prevent consensus.) - if (peersUnchanged < p.avSTALLED_ROUNDS && - (proposing && currentVoteCounter_ < p.avSTALLED_ROUNDS)) + if (peersUnchanged < p.avStalledRounds && + (proposing && currentVoteCounter_ < p.avStalledRounds)) return false; // Does this transaction have more than 80% agreement @@ -108,7 +108,7 @@ 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. - bool const stalled = weight > p.minCONSENSUS_PCT || weight < (100 - p.minCONSENSUS_PCT); + bool const stalled = weight > p.minConsensusPct || weight < (100 - p.minConsensusPct); if (stalled) { @@ -276,7 +276,7 @@ DisputedTx::updateVote(int percentTime, bool proposing, ConsensusPar // Proposing or not, we need to keep track of which state we've reached so // we can determine if the vote has stalled. auto const [requiredPct, newState] = - getNeededWeight(p, avalancheState_, percentTime, ++avalancheCounter_, p.avMIN_ROUNDS); + getNeededWeight(p, avalancheState_, percentTime, ++avalancheCounter_, p.avMinRounds); if (newState) { avalancheState_ = *newState; diff --git a/src/xrpld/consensus/Validations.h b/src/xrpld/consensus/Validations.h index fd32b52518..7be578060e 100644 --- a/src/xrpld/consensus/Validations.h +++ b/src/xrpld/consensus/Validations.h @@ -33,7 +33,7 @@ struct ValidationParms This is a safety to protect against very old validations and the time it takes to adjust the close time accuracy window. */ - std::chrono::seconds validationCURRENT_WALL = std::chrono::minutes{5}; + std::chrono::seconds validationCurrentWall = std::chrono::minutes{5}; /** Duration a validation remains current after first observed. @@ -41,14 +41,14 @@ struct ValidationParms first saw it. This provides faster recovery in very rare cases where the number of validations produced by the network is lower than normal */ - std::chrono::seconds validationCURRENT_LOCAL = std::chrono::minutes{3}; + std::chrono::seconds validationCurrentLocal = std::chrono::minutes{3}; /** Duration pre-close in which validations are acceptable. The number of seconds before a close time that we consider a validation acceptable. This protects against extreme clock errors */ - std::chrono::seconds validationCURRENT_EARLY = std::chrono::minutes{3}; + std::chrono::seconds validationCurrentEarly = std::chrono::minutes{3}; /** Duration a set of validations for a given ledger hash remain valid @@ -56,7 +56,7 @@ struct ValidationParms hash can expire. This keeps validations for recent ledgers available for a reasonable interval. */ - std::chrono::seconds validationSET_EXPIRES = std::chrono::minutes{10}; + std::chrono::seconds validationSetExpires = std::chrono::minutes{10}; /** How long we consider a validation fresh. * @@ -98,7 +98,7 @@ public: bool operator()(time_point now, Seq s, ValidationParms const& p) { - if (now > (when_ + p.validationSET_EXPIRES)) + if (now > (when_ + p.validationSetExpires)) seq_ = Seq{0}; if (s <= seq_) return false; @@ -139,9 +139,9 @@ isCurrent( // promoted from unsigned 32 bit to signed 64 bit prior // to computation. - return (signTime > (now - p.validationCURRENT_EARLY)) && - (signTime < (now + p.validationCURRENT_WALL)) && - ((seenTime == NetClock::time_point{}) || (seenTime < (now + p.validationCURRENT_LOCAL))); + return (signTime > (now - p.validationCurrentEarly)) && + (signTime < (now + p.validationCurrentWall)) && + ((seenTime == NetClock::time_point{}) || (seenTime < (now + p.validationCurrentLocal))); } /** Status of validation we received */ @@ -610,7 +610,7 @@ public: auto const diff = std::max(seqit->second.signTime(), val.signTime()) - std::min(seqit->second.signTime(), val.signTime()); - if (diff > parms_.validationCURRENT_WALL && + if (diff > parms_.validationCurrentWall && val.signTime() > seqit->second.signTime()) seqit->second = val; } @@ -707,7 +707,7 @@ public: { // The next refresh time is shortly before the expiration // time from now. - kRefreshTime = now + parms_.validationSET_EXPIRES - parms_.validationFRESHNESS; + kRefreshTime = now + parms_.validationSetExpires - parms_.validationFRESHNESS; for (auto i = byLedger_.begin(); i != byLedger_.end(); ++i) { @@ -732,8 +732,8 @@ public: } } - beast::expire(byLedger_, parms_.validationSET_EXPIRES); - beast::expire(bySequence_, parms_.validationSET_EXPIRES); + beast::expire(byLedger_, parms_.validationSetExpires); + beast::expire(bySequence_, parms_.validationSetExpires); } JLOG(j.debug()) << "Validations sets sweep lock duration " << std::chrono::duration_cast( diff --git a/src/xrpld/core/Config.h b/src/xrpld/core/Config.h index 322683e119..a18b68a508 100644 --- a/src/xrpld/core/Config.h +++ b/src/xrpld/core/Config.h @@ -48,13 +48,13 @@ A default-constructed Setup contains recommended values. struct FeeSetup { /** The cost of a reference transaction in drops. */ - XRPAmount reference_fee{10}; + XRPAmount referenceFee{10}; /** The account reserve requirement in drops. */ - XRPAmount account_reserve{10 * kDropsPerXrp}; + XRPAmount accountReserve{10 * kDropsPerXrp}; /** The per-owned item reserve requirement in drops. */ - XRPAmount owner_reserve{2 * kDropsPerXrp}; + XRPAmount ownerReserve{2 * kDropsPerXrp}; /* (Remember to update the example cfg files when changing any of these * values.) */ @@ -63,7 +63,7 @@ struct FeeSetup [[nodiscard]] Fees toFees() const { - return Fees{reference_fee, account_reserve, owner_reserve}; + return Fees{referenceFee, accountReserve, ownerReserve}; } }; @@ -86,20 +86,20 @@ public: getDebugLogFile() const; private: - boost::filesystem::path CONFIG_FILE_; + boost::filesystem::path configFile_; public: - boost::filesystem::path CONFIG_DIR; + boost::filesystem::path configDir; private: - boost::filesystem::path DEBUG_LOGFILE_; + boost::filesystem::path debugLogfile_; void load(); beast::Journal const j_; - bool QUIET_ = false; // Minimize logging verbosity. - bool SILENT_ = false; // No output to console after startup. + bool quiet_ = false; // Minimize logging verbosity. + bool silent_ = false; // No output to console after startup. /** Operate in stand-alone mode. In stand alone mode: @@ -109,9 +109,9 @@ private: - If no ledger is loaded, the default ledger with the root account is created. */ - bool RUN_STANDALONE_ = false; + bool runStandalone_ = false; - bool USE_TX_TABLES_ = true; + bool useTxTables_ = true; /** Determines if the server will sign a tx, given an account's secret seed. @@ -126,45 +126,45 @@ private: public: bool doImport = false; - bool ELB_SUPPORT = false; + bool elbSupport = false; // Entries from [ips] config stanza - std::vector IPS; + std::vector ips; // Entries from [ips_fixed] config stanza - std::vector IPS_FIXED; + std::vector ipsFixed; - StartUpType START_UP = StartUpType::Normal; + StartUpType startUp = StartUpType::Normal; - bool START_VALID = false; + bool startValid = false; - std::string START_LEDGER; + std::string startLedger; - std::optional TRAP_TX_HASH; + std::optional trapTxHash; // Network parameters - uint32_t NETWORK_ID = 0; + uint32_t networkId = 0; // Note: The following parameters do not relate to the UNL or trust at all // Minimum number of nodes to consider the network present - std::size_t NETWORK_QUORUM = 1; + std::size_t networkQuorum = 1; // Peer networking parameters // 1 = relay, 0 = do not relay (but process), -1 = drop completely (do NOT // process) - int RELAY_UNTRUSTED_VALIDATIONS = 1; - int RELAY_UNTRUSTED_PROPOSALS = 0; + int relayUntrustedValidations = 1; + int relayUntrustedProposals = 0; // True to ask peers not to relay current IP. - bool PEER_PRIVATE = false; + bool peerPrivate = false; // peers_max is a legacy configuration, which is going to be replaced // with individual inbound peers peers_in_max and outbound peers // peers_out_max configuration. for now we support both the legacy and // the new configuration. if peers_max is configured then peers_in_max and // peers_out_max are ignored. - std::size_t PEERS_MAX = 0; - std::size_t PEERS_OUT_MAX = 0; - std::size_t PEERS_IN_MAX = 0; + std::size_t peersMax = 0; + std::size_t peersOutMax = 0; + std::size_t peersInMax = 0; // Path searching: these were reasonable default values at some point but // further research is needed to decide if they still are @@ -178,110 +178,110 @@ public: // Servers operating as validators disable path finding by // default by setting the `PATH_SEARCH_MAX` option to 0 // unless it is explicitly set in the configuration file. - int PATH_SEARCH_OLD = 2; - int PATH_SEARCH = 2; - int PATH_SEARCH_FAST = 2; - int PATH_SEARCH_MAX = 3; + int pathSearchOld = 2; + int pathSearch = 2; + int pathSearchFast = 2; + int pathSearchMax = 3; // Validation - std::optional VALIDATION_QUORUM; // validations to consider ledger authoritative + std::optional validationQuorum; // validations to consider ledger authoritative - FeeSetup FEES; + FeeSetup fees; // Node storage configuration - std::uint32_t LEDGER_HISTORY = 256; - std::uint32_t FETCH_DEPTH = 1000000000; + std::uint32_t ledgerHistory = 256; + std::uint32_t fetchDepth = 1000000000; // Tunable that adjusts various parameters, typically associated // with hardware parameters (RAM size and CPU cores). The default // is 'tiny'. - std::size_t NODE_SIZE = 0; + std::size_t nodeSize = 0; - bool SSL_VERIFY = true; - std::string SSL_VERIFY_FILE; - std::string SSL_VERIFY_DIR; + bool sslVerify = true; + std::string sslVerifyFile; + std::string sslVerifyDir; // Compression - bool COMPRESSION = false; + bool compression = false; // Enable the experimental Ledger Replay functionality - bool LEDGER_REPLAY = false; + bool ledgerReplay = false; // Work queue limits - int MAX_TRANSACTIONS = 250; + int maxTransactions = 250; static constexpr int kMaxJobQueueTx = 1000; static constexpr int kMinJobQueueTx = 100; // Amendment majority time - std::chrono::seconds AMENDMENT_MAJORITY_TIME = kDefaultAmendmentMajorityTime; + std::chrono::seconds amendmentMajorityTime = kDefaultAmendmentMajorityTime; // Thread pool configuration (0 = choose for me) - int WORKERS = 0; // jobqueue thread count. default: upto 6 - int IO_WORKERS = 0; // io svc thread count. default: 2 - int PREFETCH_WORKERS = 0; // prefetch thread count. default: 4 + int workers = 0; // jobqueue thread count. default: upto 6 + int ioWorkers = 0; // io svc thread count. default: 2 + int prefetchWorkers = 0; // prefetch thread count. default: 4 // Can only be set in code, specifically unit tests - bool FORCE_MULTI_THREAD = false; + bool forceMultiThread = false; // Normally the sweep timer is automatically deduced based on the node // size, but we allow admins to explicitly set it in the config. - std::optional SWEEP_INTERVAL; + std::optional sweepInterval; // Reduce-relay - Experimental parameters to control p2p routing algorithms // Enable base squelching of duplicate validation/proposal messages - bool VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE = false; + bool vpReduceRelayBaseSquelchEnable = false; ///////////////////// !!TEMPORARY CODE BLOCK!! //////////////////////// // Temporary squelching config for the peers selected as a source of // // validator messages. The config must be removed once squelching is // // made the default routing algorithm // - std::size_t VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS = 5; + std::size_t vpReduceRelaySquelchMaxSelectedPeers = 5; ///////////////// END OF TEMPORARY CODE BLOCK ///////////////////// // Transaction reduce-relay feature - bool TX_REDUCE_RELAY_ENABLE = false; + bool txReduceRelayEnable = false; // If tx reduce-relay feature is disabled // and this flag is enabled then some // tx-related metrics is collected. It // is ignored if tx reduce-relay feature is // enabled. It is used in debugging to compare // metrics with the feature disabled/enabled. - bool TX_REDUCE_RELAY_METRICS = false; + bool txReduceRelayMetrics = false; // Minimum peers a server should have before // selecting random peers - std::size_t TX_REDUCE_RELAY_MIN_PEERS = 20; + std::size_t txReduceRelayMinPeers = 20; // Percentage of peers with the tx reduce-relay feature enabled // to relay to out of total active peers - std::size_t TX_RELAY_PERCENTAGE = 25; + std::size_t txRelayPercentage = 25; // These override the command line client settings - std::optional rpc_ip; + std::optional rpcIp; std::unordered_set> features; - std::string SERVER_DOMAIN; + std::string serverDomain; // How long can a peer remain in the "unknown" state - std::chrono::seconds MAX_UNKNOWN_TIME{600}; + std::chrono::seconds maxUnknownTime{600}; // How long can a peer remain in the "diverged" state - std::chrono::seconds MAX_DIVERGED_TIME{300}; + std::chrono::seconds maxDivergedTime{300}; // Enable the beta API version - bool BETA_RPC_API = false; + bool betaRpcApi = false; // First, attempt to load the latest ledger directly from disk. - bool FAST_LOAD = false; + bool fastLoad = false; // When starting xrpld with existing database it do not know it has those // ledgers locally until the server naturally tries to backfill. This makes // is difficult to test some functionality (in particular performance // testing sidechains). With this variable the user is able to force xrpld // to consider the ledger range to be present. It should be used for testing // only. - std::optional> FORCED_LEDGER_RANGE_PRESENT; + std::optional> forcedLedgerRangePresent; - std::optional VALIDATOR_LIST_THRESHOLD; + std::optional validatorListThreshold; public: Config(); @@ -305,23 +305,23 @@ public: [[nodiscard]] bool quiet() const { - return QUIET_; + return quiet_; } [[nodiscard]] bool silent() const { - return SILENT_; + return silent_; } [[nodiscard]] bool standalone() const { - return RUN_STANDALONE_; + return runStandalone_; } [[nodiscard]] bool useTxTables() const { - return USE_TX_TABLES_; + return useTxTables_; } [[nodiscard]] bool diff --git a/src/xrpld/core/detail/Config.cpp b/src/xrpld/core/detail/Config.cpp index d8789795c0..6eedc43edd 100644 --- a/src/xrpld/core/detail/Config.cpp +++ b/src/xrpld/core/detail/Config.cpp @@ -272,11 +272,11 @@ Config::Config() void Config::setupControl(bool bQuiet, bool bSilent, bool bStandalone) { - XRPL_ASSERT(NODE_SIZE == 0, "xrpl::Config::setupControl : node size not set"); + XRPL_ASSERT(nodeSize == 0, "xrpl::Config::setupControl : node size not set"); - QUIET_ = bQuiet || bSilent; - SILENT_ = bSilent; - RUN_STANDALONE_ = bStandalone; + quiet_ = bQuiet || bSilent; + silent_ = bSilent; + runStandalone_ = bStandalone; // We try to autodetect the appropriate node size by checking available // RAM and CPU resources. We default to "tiny" for standalone mode. @@ -293,15 +293,15 @@ Config::setupControl(bool bQuiet, bool bSilent, bool bStandalone) XRPL_ASSERT(ns != threshold.second.end(), "xrpl::Config::setupControl : valid node size"); if (ns != threshold.second.end()) - NODE_SIZE = std::distance(threshold.second.begin(), ns); + nodeSize = std::distance(threshold.second.begin(), ns); // Adjust the size based on the number of hardware threads of // execution available to us: if (auto const hc = std::thread::hardware_concurrency(); hc != 0) - NODE_SIZE = std::min(hc / 2, NODE_SIZE); + nodeSize = std::min(hc / 2, nodeSize); } - XRPL_ASSERT(NODE_SIZE <= 4, "xrpl::Config::setupControl : node size is set"); + XRPL_ASSERT(nodeSize <= 4, "xrpl::Config::setupControl : node size is set"); } void @@ -319,10 +319,10 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand if (!strConf.empty()) { // --conf= : everything is relative that file. - CONFIG_FILE_ = strConf; - CONFIG_DIR = boost::filesystem::absolute(CONFIG_FILE_); - CONFIG_DIR.remove_filename(); - dataDir = CONFIG_DIR / kDatabaseDirName; + configFile_ = strConf; + configDir = boost::filesystem::absolute(configFile_); + configDir.remove_filename(); + dataDir = configDir / kDatabaseDirName; } else { @@ -331,13 +331,13 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand // Check if either of the config files exist in the current working // directory, in which case the databases will be stored in a // subdirectory. - CONFIG_DIR = boost::filesystem::current_path(); - dataDir = CONFIG_DIR / kDatabaseDirName; - CONFIG_FILE_ = CONFIG_DIR / kConfigFileName; - if (boost::filesystem::exists(CONFIG_FILE_)) + configDir = boost::filesystem::current_path(); + dataDir = configDir / kDatabaseDirName; + configFile_ = configDir / kConfigFileName; + if (boost::filesystem::exists(configFile_)) break; - CONFIG_FILE_ = CONFIG_DIR / kConfigLegacyName; - if (boost::filesystem::exists(CONFIG_FILE_)) + configFile_ = configDir / kConfigLegacyName; + if (boost::filesystem::exists(configFile_)) break; // Check if the home directory is set, and optionally the XDG config @@ -362,22 +362,22 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand // Check if either of the config files exist in the XDG config // dir. dataDir = strXdgDataHome + "/" + systemName(); - CONFIG_DIR = strXdgConfigHome + "/" + systemName(); - CONFIG_FILE_ = CONFIG_DIR / kConfigFileName; - if (boost::filesystem::exists(CONFIG_FILE_)) + configDir = strXdgConfigHome + "/" + systemName(); + configFile_ = configDir / kConfigFileName; + if (boost::filesystem::exists(configFile_)) break; - CONFIG_FILE_ = CONFIG_DIR / kConfigLegacyName; - if (boost::filesystem::exists(CONFIG_FILE_)) + configFile_ = configDir / kConfigLegacyName; + if (boost::filesystem::exists(configFile_)) break; } // As a last resort, check the system config directory. dataDir = "/var/lib/" + systemName(); - CONFIG_DIR = "/etc/" + systemName(); - CONFIG_FILE_ = CONFIG_DIR / kConfigFileName; - if (boost::filesystem::exists(CONFIG_FILE_)) + configDir = "/etc/" + systemName(); + configFile_ = configDir / kConfigFileName; + if (boost::filesystem::exists(configFile_)) break; - CONFIG_FILE_ = CONFIG_DIR / kConfigLegacyName; + configFile_ = configDir / kConfigLegacyName; } while (false); } @@ -390,7 +390,7 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand { dataDir = boost::filesystem::path(dbPath); } - else if (RUN_STANDALONE_) + else if (runStandalone_) { dataDir.clear(); } @@ -407,17 +407,16 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand legacy("database_path", boost::filesystem::absolute(dataDir).string()); } - HTTPClient::initializeSSLContext( - this->SSL_VERIFY_DIR, this->SSL_VERIFY_FILE, this->SSL_VERIFY, j_); + HTTPClient::initializeSSLContext(this->sslVerifyDir, this->sslVerifyFile, this->sslVerify, j_); - if (RUN_STANDALONE_) - LEDGER_HISTORY = 0; + if (runStandalone_) + ledgerHistory = 0; Section const ledgerTxTablesSection = section("ledger_tx_tables"); - getIfExists(ledgerTxTablesSection, "use_tx_tables", USE_TX_TABLES_); + getIfExists(ledgerTxTablesSection, "use_tx_tables", useTxTables_); Section const& nodeDbSection{section(ConfigSection::nodeDatabase())}; - getIfExists(nodeDbSection, "fast_load", FAST_LOAD); + getIfExists(nodeDbSection, "fast_load", fastLoad); } // 0 ports are allowed for unit tests, but still not allowed to be present in @@ -454,16 +453,16 @@ Config::load() // NOTE: this writes to cerr because we want cout to be reserved // for the writing of the json response (so that stdout can be part of a // pipeline, for instance) - if (!QUIET_) - std::cerr << "Loading: " << CONFIG_FILE_ << "\n"; + if (!quiet_) + std::cerr << "Loading: " << configFile_ << "\n"; boost::system::error_code ec; - auto const fileContents = getFileContents(ec, CONFIG_FILE_); + auto const fileContents = getFileContents(ec, configFile_); if (ec) { - std::cerr << "Failed to read '" << CONFIG_FILE_ << "'." << ec.value() << ": " - << ec.message() << std::endl; + std::cerr << "Failed to read '" << configFile_ << "'." << ec.value() << ": " << ec.message() + << std::endl; return; } @@ -479,10 +478,10 @@ Config::loadFromString(std::string const& fileContents) build(secConfig); if (auto s = getIniFileSection(secConfig, SECTION_IPS)) - IPS = *s; + ips = *s; if (auto s = getIniFileSection(secConfig, SECTION_IPS_FIXED)) - IPS_FIXED = *s; + ipsFixed = *s; // if the user has specified ip:port then replace : with a space. { @@ -502,8 +501,8 @@ Config::loadFromString(std::string const& fileContents) } }; - replaceColons(IPS_FIXED); - replaceColons(IPS); + replaceColons(ipsFixed); + replaceColons(ips); } { @@ -521,47 +520,47 @@ Config::loadFromString(std::string const& fileContents) { if (strTemp == "main") { - NETWORK_ID = 0; + networkId = 0; } else if (strTemp == "testnet") { - NETWORK_ID = 1; + networkId = 1; } else if (strTemp == "devnet") { - NETWORK_ID = 2; + networkId = 2; } else { - NETWORK_ID = beast::lexicalCastThrow(strTemp); + networkId = beast::lexicalCastThrow(strTemp); } } if (getSingleSection(secConfig, SECTION_PEER_PRIVATE, strTemp, j_)) - PEER_PRIVATE = beast::lexicalCastThrow(strTemp); + peerPrivate = beast::lexicalCastThrow(strTemp); if (getSingleSection(secConfig, SECTION_PEERS_MAX, strTemp, j_)) { - PEERS_MAX = beast::lexicalCastThrow(strTemp); + peersMax = beast::lexicalCastThrow(strTemp); } else { - std::optional peersInMax{}; + std::optional peersInMaxOpt{}; if (getSingleSection(secConfig, SECTION_PEERS_IN_MAX, strTemp, j_)) { - peersInMax = beast::lexicalCastThrow(strTemp); - if (*peersInMax > 1000) + peersInMaxOpt = beast::lexicalCastThrow(strTemp); + if (*peersInMaxOpt > 1000) { Throw("Invalid value specified in [" SECTION_PEERS_IN_MAX "] section; the value must be less or equal than 1000"); } } - std::optional peersOutMax{}; + std::optional peersOutMaxOpt{}; if (getSingleSection(secConfig, SECTION_PEERS_OUT_MAX, strTemp, j_)) { - peersOutMax = beast::lexicalCastThrow(strTemp); - if (*peersOutMax < 10 || *peersOutMax > 1000) + peersOutMaxOpt = beast::lexicalCastThrow(strTemp); + if (*peersOutMaxOpt < 10 || *peersOutMaxOpt > 1000) { Throw("Invalid value specified in [" SECTION_PEERS_OUT_MAX "] section; the value must be in range 10-1000"); @@ -569,17 +568,17 @@ Config::loadFromString(std::string const& fileContents) } // if one section is configured then the other must be configured too - if ((peersInMax && !peersOutMax) || (peersOutMax && !peersInMax)) + if ((peersInMaxOpt && !peersOutMaxOpt) || (peersOutMaxOpt && !peersInMaxOpt)) { Throw("Both sections [" SECTION_PEERS_IN_MAX "]" "and [" SECTION_PEERS_OUT_MAX "] must be configured"); } - if (peersInMax && peersOutMax) + if (peersInMaxOpt && peersOutMaxOpt) { - PEERS_IN_MAX = *peersInMax; - PEERS_OUT_MAX = *peersOutMax; + peersInMax = *peersInMaxOpt; + peersOutMax = *peersOutMaxOpt; } } @@ -587,27 +586,27 @@ Config::loadFromString(std::string const& fileContents) { if (boost::iequals(strTemp, "tiny")) { - NODE_SIZE = 0; + nodeSize = 0; } else if (boost::iequals(strTemp, "small")) { - NODE_SIZE = 1; + nodeSize = 1; } else if (boost::iequals(strTemp, "medium")) { - NODE_SIZE = 2; + nodeSize = 2; } else if (boost::iequals(strTemp, "large")) { - NODE_SIZE = 3; + nodeSize = 3; } else if (boost::iequals(strTemp, "huge")) { - NODE_SIZE = 4; + nodeSize = 4; } else { - NODE_SIZE = std::min(4, beast::lexicalCastThrow(strTemp)); + nodeSize = std::min(4, beast::lexicalCastThrow(strTemp)); } } @@ -615,27 +614,27 @@ Config::loadFromString(std::string const& fileContents) signingEnabled_ = beast::lexicalCastThrow(strTemp); if (getSingleSection(secConfig, SECTION_ELB_SUPPORT, strTemp, j_)) - ELB_SUPPORT = beast::lexicalCastThrow(strTemp); + elbSupport = beast::lexicalCastThrow(strTemp); - getSingleSection(secConfig, SECTION_SSL_VERIFY_FILE, SSL_VERIFY_FILE, j_); - getSingleSection(secConfig, SECTION_SSL_VERIFY_DIR, SSL_VERIFY_DIR, j_); + getSingleSection(secConfig, SECTION_SSL_VERIFY_FILE, sslVerifyFile, j_); + getSingleSection(secConfig, SECTION_SSL_VERIFY_DIR, sslVerifyDir, j_); if (getSingleSection(secConfig, SECTION_SSL_VERIFY, strTemp, j_)) - SSL_VERIFY = beast::lexicalCastThrow(strTemp); + sslVerify = beast::lexicalCastThrow(strTemp); if (getSingleSection(secConfig, SECTION_RELAY_VALIDATIONS, strTemp, j_)) { if (boost::iequals(strTemp, "all")) { - RELAY_UNTRUSTED_VALIDATIONS = 1; + relayUntrustedValidations = 1; } else if (boost::iequals(strTemp, "trusted")) { - RELAY_UNTRUSTED_VALIDATIONS = 0; + relayUntrustedValidations = 0; } else if (boost::iequals(strTemp, "drop_untrusted")) { - RELAY_UNTRUSTED_VALIDATIONS = -1; + relayUntrustedValidations = -1; } else { @@ -648,15 +647,15 @@ Config::loadFromString(std::string const& fileContents) { if (boost::iequals(strTemp, "all")) { - RELAY_UNTRUSTED_PROPOSALS = 1; + relayUntrustedProposals = 1; } else if (boost::iequals(strTemp, "trusted")) { - RELAY_UNTRUSTED_PROPOSALS = 0; + relayUntrustedProposals = 0; } else if (boost::iequals(strTemp, "drop_untrusted")) { - RELAY_UNTRUSTED_PROPOSALS = -1; + relayUntrustedProposals = -1; } else { @@ -672,28 +671,28 @@ Config::loadFromString(std::string const& fileContents) } if (getSingleSection(secConfig, SECTION_NETWORK_QUORUM, strTemp, j_)) - NETWORK_QUORUM = beast::lexicalCastThrow(strTemp); + networkQuorum = beast::lexicalCastThrow(strTemp); - FEES = setupFeeVote(section("voting")); + fees = setupFeeVote(section("voting")); /* [fee_default] is documented in the example config files as useful for * things like offline transaction signing. Until that's completely * deprecated, allow it to override the [voting] section. */ if (getSingleSection(secConfig, SECTION_FEE_DEFAULT, strTemp, j_)) - FEES.reference_fee = beast::lexicalCastThrow(strTemp); + fees.referenceFee = beast::lexicalCastThrow(strTemp); if (getSingleSection(secConfig, SECTION_LEDGER_HISTORY, strTemp, j_)) { if (boost::iequals(strTemp, "full")) { - LEDGER_HISTORY = std::numeric_limits::max(); + ledgerHistory = std::numeric_limits::max(); } else if (boost::iequals(strTemp, "none")) { - LEDGER_HISTORY = 0; + ledgerHistory = 0; } else { - LEDGER_HISTORY = beast::lexicalCastThrow(strTemp); + ledgerHistory = beast::lexicalCastThrow(strTemp); } } @@ -701,42 +700,42 @@ Config::loadFromString(std::string const& fileContents) { if (boost::iequals(strTemp, "none")) { - FETCH_DEPTH = 0; + fetchDepth = 0; } else if (boost::iequals(strTemp, "full")) { - FETCH_DEPTH = std::numeric_limits::max(); + fetchDepth = std::numeric_limits::max(); } else { - FETCH_DEPTH = beast::lexicalCastThrow(strTemp); + fetchDepth = beast::lexicalCastThrow(strTemp); } - FETCH_DEPTH = std::max(FETCH_DEPTH, 10); + fetchDepth = std::max(fetchDepth, 10); } // By default, validators don't have pathfinding enabled, unless it is // explicitly requested by the server's admin. if (exists(SECTION_VALIDATION_SEED) || exists(SECTION_VALIDATOR_TOKEN)) - PATH_SEARCH_MAX = 0; + pathSearchMax = 0; if (getSingleSection(secConfig, SECTION_PATH_SEARCH_OLD, strTemp, j_)) - PATH_SEARCH_OLD = beast::lexicalCastThrow(strTemp); + pathSearchOld = beast::lexicalCastThrow(strTemp); if (getSingleSection(secConfig, SECTION_PATH_SEARCH, strTemp, j_)) - PATH_SEARCH = beast::lexicalCastThrow(strTemp); + pathSearch = beast::lexicalCastThrow(strTemp); if (getSingleSection(secConfig, SECTION_PATH_SEARCH_FAST, strTemp, j_)) - PATH_SEARCH_FAST = beast::lexicalCastThrow(strTemp); + pathSearchFast = beast::lexicalCastThrow(strTemp); if (getSingleSection(secConfig, SECTION_PATH_SEARCH_MAX, strTemp, j_)) - PATH_SEARCH_MAX = beast::lexicalCastThrow(strTemp); + pathSearchMax = beast::lexicalCastThrow(strTemp); if (getSingleSection(secConfig, SECTION_DEBUG_LOGFILE, strTemp, j_)) - DEBUG_LOGFILE_ = strTemp; + debugLogfile_ = strTemp; if (getSingleSection(secConfig, SECTION_SWEEP_INTERVAL, strTemp, j_)) { - SWEEP_INTERVAL = beast::lexicalCastThrow(strTemp); + sweepInterval = beast::lexicalCastThrow(strTemp); - if (SWEEP_INTERVAL < 10 || SWEEP_INTERVAL > 600) + if (sweepInterval < 10 || sweepInterval > 600) { Throw("Invalid " SECTION_SWEEP_INTERVAL ": must be between 10 and 600 inclusive"); @@ -745,9 +744,9 @@ Config::loadFromString(std::string const& fileContents) if (getSingleSection(secConfig, SECTION_WORKERS, strTemp, j_)) { - WORKERS = beast::lexicalCastThrow(strTemp); + workers = beast::lexicalCastThrow(strTemp); - if (WORKERS < 1 || WORKERS > 1024) + if (workers < 1 || workers > 1024) { Throw("Invalid " SECTION_WORKERS ": must be between 1 and 1024 inclusive."); @@ -756,9 +755,9 @@ Config::loadFromString(std::string const& fileContents) if (getSingleSection(secConfig, SECTION_IO_WORKERS, strTemp, j_)) { - IO_WORKERS = beast::lexicalCastThrow(strTemp); + ioWorkers = beast::lexicalCastThrow(strTemp); - if (IO_WORKERS < 1 || IO_WORKERS > 1024) + if (ioWorkers < 1 || ioWorkers > 1024) { Throw("Invalid " SECTION_IO_WORKERS ": must be between 1 and 1024 inclusive."); @@ -767,9 +766,9 @@ Config::loadFromString(std::string const& fileContents) if (getSingleSection(secConfig, SECTION_PREFETCH_WORKERS, strTemp, j_)) { - PREFETCH_WORKERS = beast::lexicalCastThrow(strTemp); + prefetchWorkers = beast::lexicalCastThrow(strTemp); - if (PREFETCH_WORKERS < 1 || PREFETCH_WORKERS > 1024) + if (prefetchWorkers < 1 || prefetchWorkers > 1024) { Throw("Invalid " SECTION_PREFETCH_WORKERS ": must be between 1 and 1024 inclusive."); @@ -777,10 +776,10 @@ Config::loadFromString(std::string const& fileContents) } if (getSingleSection(secConfig, SECTION_COMPRESSION, strTemp, j_)) - COMPRESSION = beast::lexicalCastThrow(strTemp); + compression = beast::lexicalCastThrow(strTemp); if (getSingleSection(secConfig, SECTION_LEDGER_REPLAY, strTemp, j_)) - LEDGER_REPLAY = beast::lexicalCastThrow(strTemp); + ledgerReplay = beast::lexicalCastThrow(strTemp); if (exists(SECTION_REDUCE_RELAY)) { @@ -803,15 +802,15 @@ Config::loadFromString(std::string const& fileContents) if (sec.exists("vp_base_squelch_enable")) { - VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE = sec.valueOr("vp_base_squelch_enable", false); + vpReduceRelayBaseSquelchEnable = sec.valueOr("vp_base_squelch_enable", false); } else if (sec.exists("vp_enable")) { - VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE = sec.valueOr("vp_enable", false); + vpReduceRelayBaseSquelchEnable = sec.valueOr("vp_enable", false); } else { - VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE = false; + vpReduceRelayBaseSquelchEnable = false; } ///////////////// !!END OF TEMPORARY CODE BLOCK!! ///////////////////// @@ -819,9 +818,8 @@ Config::loadFromString(std::string const& fileContents) // Temporary squelching config for the peers selected as a source of // // validator messages. The config must be removed once squelching is // // made the default routing algorithm. // - VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS = - sec.valueOr("vp_base_squelch_max_selected_peers", 5); - if (VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS < 3) + vpReduceRelaySquelchMaxSelectedPeers = sec.valueOr("vp_base_squelch_max_selected_peers", 5); + if (vpReduceRelaySquelchMaxSelectedPeers < 3) { Throw("Invalid " SECTION_REDUCE_RELAY " vp_base_squelch_max_selected_peers must be " @@ -829,11 +827,11 @@ Config::loadFromString(std::string const& fileContents) } ///////////////// !!END OF TEMPORARY CODE BLOCK!! ///////////////////// - TX_REDUCE_RELAY_ENABLE = sec.valueOr("tx_enable", false); - TX_REDUCE_RELAY_METRICS = sec.valueOr("tx_metrics", false); - TX_REDUCE_RELAY_MIN_PEERS = sec.valueOr("tx_min_peers", 20); - TX_RELAY_PERCENTAGE = sec.valueOr("tx_relay_percentage", 25); - if (TX_RELAY_PERCENTAGE < 10 || TX_RELAY_PERCENTAGE > 100 || TX_REDUCE_RELAY_MIN_PEERS < 10) + txReduceRelayEnable = sec.valueOr("tx_enable", false); + txReduceRelayMetrics = sec.valueOr("tx_metrics", false); + txReduceRelayMinPeers = sec.valueOr("tx_min_peers", 20); + txRelayPercentage = sec.valueOr("tx_relay_percentage", 25); + if (txRelayPercentage < 10 || txRelayPercentage > 100 || txReduceRelayMinPeers < 10) { Throw("Invalid " SECTION_REDUCE_RELAY ", tx_min_peers must be greater than or equal to 10" @@ -844,7 +842,7 @@ Config::loadFromString(std::string const& fileContents) if (getSingleSection(secConfig, SECTION_MAX_TRANSACTIONS, strTemp, j_)) { - MAX_TRANSACTIONS = + maxTransactions = std::clamp(beast::lexicalCastThrow(strTemp), kMinJobQueueTx, kMaxJobQueueTx); } @@ -857,7 +855,7 @@ Config::loadFromString(std::string const& fileContents) ": the domain name does not appear to meet the requirements."); } - SERVER_DOMAIN = strTemp; + serverDomain = strTemp; } if (exists(SECTION_OVERLAY)) @@ -869,7 +867,7 @@ Config::loadFromString(std::string const& fileContents) try { if (auto val = sec.get("max_unknown_time")) - MAX_UNKNOWN_TIME = seconds{beast::lexicalCastThrow(*val)}; + maxUnknownTime = seconds{beast::lexicalCastThrow(*val)}; } catch (...) { @@ -877,7 +875,7 @@ Config::loadFromString(std::string const& fileContents) ": must be of the form '' representing seconds."); } - if (MAX_UNKNOWN_TIME < seconds{300} || MAX_UNKNOWN_TIME > seconds{1800}) + if (maxUnknownTime < seconds{300} || maxUnknownTime > seconds{1800}) { Throw( "Invalid value 'max_unknown_time' in " SECTION_OVERLAY @@ -887,7 +885,7 @@ Config::loadFromString(std::string const& fileContents) try { if (auto val = sec.get("max_diverged_time")) - MAX_DIVERGED_TIME = seconds{beast::lexicalCastThrow(*val)}; + maxDivergedTime = seconds{beast::lexicalCastThrow(*val)}; } catch (...) { @@ -895,7 +893,7 @@ Config::loadFromString(std::string const& fileContents) ": must be of the form '' representing seconds."); } - if (MAX_DIVERGED_TIME < seconds{60} || MAX_DIVERGED_TIME > seconds{900}) + if (maxDivergedTime < seconds{60} || maxDivergedTime > seconds{900}) { Throw("Invalid value 'max_diverged_time' in " SECTION_OVERLAY ": the time must be between 60 and 900 seconds, inclusive."); @@ -917,22 +915,22 @@ Config::loadFromString(std::string const& fileContents) if (boost::iequals(match[2], "minutes")) { - AMENDMENT_MAJORITY_TIME = minutes(duration); + amendmentMajorityTime = minutes(duration); } else if (boost::iequals(match[2], "hours")) { - AMENDMENT_MAJORITY_TIME = hours(duration); + amendmentMajorityTime = hours(duration); } else if (boost::iequals(match[2], "days")) { - AMENDMENT_MAJORITY_TIME = days(duration); + amendmentMajorityTime = days(duration); } else if (boost::iequals(match[2], "weeks")) { - AMENDMENT_MAJORITY_TIME = weeks(duration); + amendmentMajorityTime = weeks(duration); } - if (AMENDMENT_MAJORITY_TIME < minutes(15)) + if (amendmentMajorityTime < minutes(15)) { Throw("Invalid " SECTION_AMENDMENT_MAJORITY_TIME ", the minimum amount of time an amendment must hold a " @@ -941,10 +939,10 @@ Config::loadFromString(std::string const& fileContents) } if (getSingleSection(secConfig, SECTION_BETA_RPC_API, strTemp, j_)) - BETA_RPC_API = beast::lexicalCastThrow(strTemp); + betaRpcApi = beast::lexicalCastThrow(strTemp); // Do not load trusted validator configuration for standalone mode - if (!RUN_STANDALONE_) + if (!runStandalone_) { // If a file was explicitly specified, then throw if the // path is malformed or if the file does not exist or is @@ -966,8 +964,8 @@ Config::loadFromString(std::string const& fileContents) "]"); } - if (!validatorsFile.is_absolute() && !CONFIG_DIR.empty()) - validatorsFile = CONFIG_DIR / validatorsFile; + if (!validatorsFile.is_absolute() && !configDir.empty()) + validatorsFile = configDir / validatorsFile; if (!boost::filesystem::exists(validatorsFile)) { @@ -986,9 +984,9 @@ Config::loadFromString(std::string const& fileContents) validatorsFile.string()); } } - else if (!CONFIG_DIR.empty()) + else if (!configDir.empty()) { - validatorsFile = CONFIG_DIR / kValidatorsFileName; + validatorsFile = configDir / kValidatorsFileName; if (!validatorsFile.empty()) { @@ -1061,7 +1059,7 @@ Config::loadFromString(std::string const& fileContents) } } - VALIDATOR_LIST_THRESHOLD = [&]() -> std::optional { + validatorListThreshold = [&]() -> std::optional { auto const& listThreshold = section(SECTION_VALIDATOR_LIST_THRESHOLD); if (listThreshold.lines().empty()) { @@ -1119,14 +1117,14 @@ Config::loadFromString(std::string const& fileContents) // This doesn't properly belong here, but check to make sure that the // value specified for network_quorum is achievable: { - auto pm = PEERS_MAX; + auto pm = peersMax; // FIXME this apparently magic value is actually defined as a constant // elsewhere (see defaultMaxPeers) but we handle this check here. if (pm == 0) pm = 21; - if (NETWORK_QUORUM > pm) + if (networkQuorum > pm) { Throw( "The minimum number of required peers (network_quorum) exceeds " @@ -1138,13 +1136,13 @@ Config::loadFromString(std::string const& fileContents) boost::filesystem::path Config::getDebugLogFile() const { - auto logFile = DEBUG_LOGFILE_; + auto logFile = debugLogfile_; if (!logFile.empty() && !logFile.is_absolute()) { // Unless an absolute path for the log file is specified, the // path is relative to the config file directory. - logFile = boost::filesystem::absolute(logFile, CONFIG_DIR); + logFile = boost::filesystem::absolute(logFile, configDir); } if (!logFile.empty()) @@ -1175,7 +1173,7 @@ Config::getValueFor(SizedItem item, std::optional node) const auto const index = static_cast>(item); XRPL_ASSERT(index < kSizedItems.size(), "xrpl::Config::getValueFor : valid index input"); XRPL_ASSERT(!node || *node <= 4, "xrpl::Config::getValueFor : unset or valid node"); - return kSizedItems.at(index).second.at(node.value_or(NODE_SIZE)); + return kSizedItems.at(index).second.at(node.value_or(nodeSize)); } FeeSetup @@ -1186,14 +1184,14 @@ setupFeeVote(Section const& section) std::uint64_t temp = 0; if (set(temp, "reference_fee", section) && temp <= std::numeric_limits::max()) - setup.reference_fee = temp; + setup.referenceFee = temp; } { std::uint32_t temp = 0; if (set(temp, "account_reserve", section)) - setup.account_reserve = temp; + setup.accountReserve = temp; if (set(temp, "owner_reserve", section)) - setup.owner_reserve = temp; + setup.ownerReserve = temp; } return setup; } @@ -1203,7 +1201,7 @@ setupDatabaseCon(Config const& c, std::optional j) { DatabaseCon::Setup setup; - setup.startUp = c.START_UP; + setup.startUp = c.startUp; setup.standAlone = c.standalone(); setup.dataDir = c.legacy("database_path"); if (!setup.standAlone && setup.dataDir.empty()) @@ -1307,7 +1305,7 @@ setupDatabaseCon(Config const& c, std::optional j) } } - if (showRiskWarning && j && c.LEDGER_HISTORY > kSqliteTuningCutoff) + if (showRiskWarning && j && c.ledgerHistory > kSqliteTuningCutoff) { JLOG(j->warn()) << "reducing the data integrity guarantees from the " "default [sqlite] behavior is not recommended for " diff --git a/src/xrpld/overlay/Message.h b/src/xrpld/overlay/Message.h index 30d4b01cda..cd21ca40c6 100644 --- a/src/xrpld/overlay/Message.h +++ b/src/xrpld/overlay/Message.h @@ -80,7 +80,7 @@ private: std::vector buffer_; std::vector bufferCompressed_; std::size_t category_; - std::once_flag once_flag_; + std::once_flag onceFlag_; std::optional validatorKey_; /** Set the payload header diff --git a/src/xrpld/overlay/Slot.h b/src/xrpld/overlay/Slot.h index 12fecbbf42..0600265500 100644 --- a/src/xrpld/overlay/Slot.h +++ b/src/xrpld/overlay/Slot.h @@ -544,8 +544,8 @@ public: : handler_(handler) , logs_(registry.getLogs()) , journal_(registry.getJournal("Slots")) - , baseSquelchEnabled_(config.VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE) - , maxSelectedPeers_(config.VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS) + , baseSquelchEnabled_(config.vpReduceRelayBaseSquelchEnable) + , maxSelectedPeers_(config.vpReduceRelaySquelchMaxSelectedPeers) { } ~Slots() = default; diff --git a/src/xrpld/overlay/detail/ConnectAttempt.cpp b/src/xrpld/overlay/detail/ConnectAttempt.cpp index ad58cc0d2c..1167e19ca3 100644 --- a/src/xrpld/overlay/detail/ConnectAttempt.cpp +++ b/src/xrpld/overlay/detail/ConnectAttempt.cpp @@ -432,10 +432,10 @@ ConnectAttempt::onHandshake(error_code ec) req_ = makeRequest( !overlay_.peerFinder().config().peerPrivate, - app_.config().COMPRESSION, - app_.config().LEDGER_REPLAY, - app_.config().TX_REDUCE_RELAY_ENABLE, - app_.config().VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE); + app_.config().compression, + app_.config().ledgerReplay, + app_.config().txReduceRelayEnable, + app_.config().vpReduceRelayBaseSquelchEnable); buildHandshake( req_, diff --git a/src/xrpld/overlay/detail/Handshake.cpp b/src/xrpld/overlay/detail/Handshake.cpp index 3465dbb816..39fd93f1d4 100644 --- a/src/xrpld/overlay/detail/Handshake.cpp +++ b/src/xrpld/overlay/detail/Handshake.cpp @@ -209,8 +209,8 @@ buildHandshake( h.insert("Instance-Cookie", std::to_string(app.instanceID())); - if (!app.config().SERVER_DOMAIN.empty()) - h.insert("Server-Domain", app.config().SERVER_DOMAIN); + if (!app.config().serverDomain.empty()) + h.insert("Server-Domain", app.config().serverDomain); if (beast::IP::isPublic(remoteIp)) h.insert("Remote-IP", remoteIp.to_string()); @@ -408,10 +408,10 @@ makeResponse( "X-Protocol-Ctl", makeFeaturesResponseHeader( req, - app.config().COMPRESSION, - app.config().LEDGER_REPLAY, - app.config().TX_REDUCE_RELAY_ENABLE, - app.config().VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE)); + app.config().compression, + app.config().ledgerReplay, + app.config().txReduceRelayEnable, + app.config().vpReduceRelayBaseSquelchEnable)); buildHandshake(resp, sharedValue, networkID, publicIp, remoteIp, app); diff --git a/src/xrpld/overlay/detail/Message.cpp b/src/xrpld/overlay/detail/Message.cpp index 68629ce9a4..120b34c78c 100644 --- a/src/xrpld/overlay/detail/Message.cpp +++ b/src/xrpld/overlay/detail/Message.cpp @@ -203,7 +203,7 @@ Message::getBuffer(Compressed tryCompressed) if (tryCompressed == Compressed::Off) return buffer_; - std::call_once(once_flag_, &Message::compress, this); + std::call_once(onceFlag_, &Message::compress, this); if (!bufferCompressed_.empty()) { diff --git a/src/xrpld/overlay/detail/OverlayImpl.cpp b/src/xrpld/overlay/detail/OverlayImpl.cpp index a39002bd51..b31f54058a 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.cpp +++ b/src/xrpld/overlay/detail/OverlayImpl.cpp @@ -112,7 +112,7 @@ OverlayImpl::Child::~Child() //------------------------------------------------------------------------------ -OverlayImpl::Timer::Timer(OverlayImpl& overlay) : Child(overlay), timer(overlay_.io_context_) +OverlayImpl::Timer::Timer(OverlayImpl& overlay) : Child(overlay), timer(overlay_.ioContext_) { } @@ -150,10 +150,10 @@ OverlayImpl::Timer::onTimer(error_code ec) overlay_.peerFinder_->oncePerSecond(); overlay_.sendEndpoints(); overlay_.autoConnect(); - if (overlay_.app_.config().TX_REDUCE_RELAY_ENABLE) + if (overlay_.app_.config().txReduceRelayEnable) overlay_.sendTxQueue(); - if ((++overlay_.timer_count_ % Tuning::kCheckIdlePeers) == 0) + if ((++overlay_.timerCount_ % Tuning::kCheckIdlePeers) == 0) overlay_.deleteIdlePeers(); asyncWait(); @@ -171,9 +171,9 @@ OverlayImpl::OverlayImpl( BasicConfig const& config, beast::insight::Collector::ptr const& collector) : app_(app) - , io_context_(ioContext) - , work_(std::in_place, boost::asio::make_work_guard(io_context_)) - , strand_(boost::asio::make_strand(io_context_)) + , ioContext_(ioContext) + , work_(std::in_place, boost::asio::make_work_guard(ioContext_)) + , strand_(boost::asio::make_strand(ioContext_)) , setup_(std::move(setup)) , journal_(app_.getJournal("Overlay")) , serverHandler_(serverHandler) @@ -186,7 +186,7 @@ OverlayImpl::OverlayImpl( config, collector)) , resolver_(resolver) - , next_id_(1) + , nextId_(1) , slots_(app, *this, app.config()) , stats_( std::bind(&OverlayImpl::collectMetrics, this), @@ -209,7 +209,7 @@ OverlayImpl::onHandoff( http_request_type&& request, endpoint_type remoteEndpoint) { - auto const id = next_id_++; + auto const id = nextId_++; auto peerJournal = app_.getJournal("Peer"); beast::WrappedSink sink(peerJournal.sink(), makePrefix(id)); beast::Journal const journal(sink); @@ -259,7 +259,7 @@ OverlayImpl::onHandoff( { handoff.moved = false; handoff.response = makeRedirectResponse(slot, request, remoteEndpoint.address()); - handoff.keep_alive = beast::rfc2616::isKeepAlive(request); + handoff.keepAlive = beast::rfc2616::isKeepAlive(request); return handoff; } } @@ -271,7 +271,7 @@ OverlayImpl::onHandoff( handoff.moved = false; handoff.response = makeErrorResponse( slot, request, remoteEndpoint.address(), "Unable to agree on a protocol version"); - handoff.keep_alive = false; + handoff.keepAlive = false; return handoff; } @@ -282,7 +282,7 @@ OverlayImpl::onHandoff( handoff.moved = false; handoff.response = makeErrorResponse(slot, request, remoteEndpoint.address(), "Incorrect security cookie"); - handoff.keep_alive = false; + handoff.keepAlive = false; return handoff; } @@ -311,7 +311,7 @@ OverlayImpl::onHandoff( << "Peer " << remoteEndpoint << " redirected, " << to_string(result); handoff.moved = false; handoff.response = makeRedirectResponse(slot, request, remoteEndpoint.address()); - handoff.keep_alive = false; + handoff.keepAlive = false; return handoff; } } @@ -351,7 +351,7 @@ OverlayImpl::onHandoff( peerFinder_->onClosed(slot); handoff.moved = false; handoff.response = makeErrorResponse(slot, request, remoteEndpoint.address(), e.what()); - handoff.keep_alive = false; + handoff.keepAlive = false; return handoff; } } @@ -444,11 +444,11 @@ OverlayImpl::connect(beast::IP::Endpoint const& remoteEndpoint) auto const p = std::make_shared( app_, - io_context_, + ioContext_, beast::IPAddressConversion::toAsioEndpoint(remoteEndpoint), usage, setup_.context, - next_id_++, + nextId_++, slot, app_.getJournal("Peer"), *this); @@ -516,7 +516,7 @@ OverlayImpl::start() // Populate our boot cache: if there are no entries in [ips] then we use // the entries in [ips_fixed]. - auto bootstrapIps = app_.config().IPS.empty() ? app_.config().IPS_FIXED : app_.config().IPS; + auto bootstrapIps = app_.config().ips.empty() ? app_.config().ipsFixed : app_.config().ips; // If nothing is specified, default to several well-known high-capacity // servers to serve as bootstrap: @@ -558,10 +558,10 @@ OverlayImpl::start() }); // Add the ips_fixed from the xrpld.cfg file - if (!app_.config().standalone() && !app_.config().IPS_FIXED.empty()) + if (!app_.config().standalone() && !app_.config().ipsFixed.empty()) { resolver_.resolve( - app_.config().IPS_FIXED, + app_.config().ipsFixed, [this](std::string const& name, std::vector const& addresses) { std::vector ips; ips.reserve(addresses.size()); @@ -1257,7 +1257,7 @@ OverlayImpl::relay( if (!relay) { - if (!app_.config().TX_REDUCE_RELAY_ENABLE) + if (!app_.config().txReduceRelayEnable) return; peers = getActivePeers(toSkip, total, disabled, enabledInSkip); @@ -1270,13 +1270,13 @@ OverlayImpl::relay( auto& txn = tx->get(); auto const sm = std::make_shared(txn, protocol::mtTRANSACTION); peers = getActivePeers(toSkip, total, disabled, enabledInSkip); - auto const minRelay = app_.config().TX_REDUCE_RELAY_MIN_PEERS + disabled; + auto const minRelay = app_.config().txReduceRelayMinPeers + disabled; - if (!app_.config().TX_REDUCE_RELAY_ENABLE || total <= minRelay) + if (!app_.config().txReduceRelayEnable || total <= minRelay) { for (auto const& p : peers) p->send(sm); - if (app_.config().TX_REDUCE_RELAY_ENABLE || app_.config().TX_REDUCE_RELAY_METRICS) + if (app_.config().txReduceRelayEnable || app_.config().txReduceRelayMetrics) txMetrics_.addMetrics(total, toSkip.size(), 0); return; } @@ -1284,8 +1284,8 @@ OverlayImpl::relay( // We have more peers than the minimum (disabled + minimum enabled), // relay to all disabled and some randomly selected enabled that // do not have the transaction. - auto const enabledTarget = app_.config().TX_REDUCE_RELAY_MIN_PEERS + - ((total - minRelay) * app_.config().TX_RELAY_PERCENTAGE / 100); + auto const enabledTarget = app_.config().txReduceRelayMinPeers + + ((total - minRelay) * app_.config().txRelayPercentage / 100); txMetrics_.addMetrics(enabledTarget, toSkip.size(), disabled); diff --git a/src/xrpld/overlay/detail/OverlayImpl.h b/src/xrpld/overlay/detail/OverlayImpl.h index 65c93a5845..6fcc2df854 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.h +++ b/src/xrpld/overlay/detail/OverlayImpl.h @@ -80,7 +80,7 @@ private: }; Application& app_; - boost::asio::io_context& io_context_; + boost::asio::io_context& ioContext_; std::optional> work_; boost::asio::strand strand_; mutable std::recursive_mutex mutex_; // VFALCO use std::mutex @@ -96,8 +96,8 @@ private: hash_map, std::weak_ptr> peers_; hash_map> ids_; Resolver& resolver_; - std::atomic next_id_; - int timer_count_{0}; + std::atomic nextId_; + int timerCount_{0}; std::atomic jqTransOverflow_{0}; std::atomic peerDisconnects_{0}; std::atomic peerDisconnectsCharges_{0}; diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 070f91c0bf..23e1785f6b 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -158,22 +158,20 @@ PeerImp::PeerImp( , request_(std::move(request)) , headers_(request_) , compressionEnabled_( - peerFeatureEnabled(headers_, kFeatureCompr, "lz4", app_.config().COMPRESSION) + peerFeatureEnabled(headers_, kFeatureCompr, "lz4", app_.config().compression) ? Compressed::On : Compressed::Off) , txReduceRelayEnabled_( - peerFeatureEnabled(headers_, kFeatureTxrr, app_.config().TX_REDUCE_RELAY_ENABLE)) + peerFeatureEnabled(headers_, kFeatureTxrr, app_.config().txReduceRelayEnable)) , ledgerReplayEnabled_( - peerFeatureEnabled(headers_, kFeatureLedgerReplay, app_.config().LEDGER_REPLAY)) + peerFeatureEnabled(headers_, kFeatureLedgerReplay, app_.config().ledgerReplay)) , ledgerReplayMsgHandler_(app, app.getLedgerReplayer()) { - JLOG(journal_.info()) << "compression enabled " << (compressionEnabled_ == Compressed::On) - << " vp reduce-relay base squelch enabled " - << peerFeatureEnabled( - headers_, - kFeatureVprr, - app_.config().VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE) - << " tx reduce-relay enabled " << txReduceRelayEnabled_; + JLOG(journal_.info()) + << "compression enabled " << (compressionEnabled_ == Compressed::On) + << " vp reduce-relay base squelch enabled " + << peerFeatureEnabled(headers_, kFeatureVprr, app_.config().vpReduceRelayBaseSquelchEnable) + << " tx reduce-relay enabled " << txReduceRelayEnabled_; } PeerImp::~PeerImp() @@ -817,8 +815,8 @@ PeerImp::onTimer(error_code const& ec) duration = clock_type::now() - trackingTime_; } - if ((t == Tracking::Diverged && (duration > app_.config().MAX_DIVERGED_TIME)) || - (t == Tracking::Unknown && (duration > app_.config().MAX_UNKNOWN_TIME))) + if ((t == Tracking::Diverged && (duration > app_.config().maxDivergedTime)) || + (t == Tracking::Unknown && (duration > app_.config().maxUnknownTime))) { overlay_.peerFinder().onFailure(slot_); fail("Not useful"); @@ -1208,7 +1206,7 @@ PeerImp::onMessageBegin( // LEDGER_DATA category == TrafficCount::Category::GlTscShare || category == TrafficCount::Category::GlTscGet) && - (txReduceRelayEnabled() || app_.config().TX_REDUCE_RELAY_METRICS)) + (txReduceRelayEnabled() || app_.config().txReduceRelayMetrics)) { overlay_.addTxMetrics(static_cast(type), static_cast(size)); } @@ -1518,7 +1516,7 @@ PeerImp::handleTransaction( { JLOG(pJournal_.trace()) << "No new transactions until synchronized"; } - else if (app_.getJobQueue().getJobCount(JtTransaction) > app_.config().MAX_TRANSACTIONS) + else if (app_.getJobQueue().getJobCount(JtTransaction) > app_.config().maxTransactions) { overlay_.incJqTransOverflow(); JLOG(pJournal_.info()) << "Transaction queue is full"; @@ -1889,7 +1887,7 @@ PeerImp::onMessage(std::shared_ptr const& m) overlay_.reportInboundTraffic( TrafficCount::Category::ProposalUntrusted, Message::messageSize(*m)); - if (app_.config().RELAY_UNTRUSTED_PROPOSALS == -1) + if (app_.config().relayUntrustedProposals == -1) return; } @@ -2496,7 +2494,7 @@ PeerImp::onMessage(std::shared_ptr const& m) overlay_.reportInboundTraffic( TrafficCount::Category::ValidationUntrusted, Message::messageSize(*m)); - if (app_.config().RELAY_UNTRUSTED_VALIDATIONS == -1) + if (app_.config().relayUntrustedValidations == -1) return; } @@ -3093,7 +3091,7 @@ PeerImp::checkPropose( } else { - relay = app_.config().RELAY_UNTRUSTED_PROPOSALS == 1 || cluster(); + relay = app_.config().relayUntrustedProposals == 1 || cluster(); } if (relay) diff --git a/src/xrpld/overlay/detail/PeerImp.h b/src/xrpld/overlay/detail/PeerImp.h index 9c66347810..dca70d88bd 100644 --- a/src/xrpld/overlay/detail/PeerImp.h +++ b/src/xrpld/overlay/detail/PeerImp.h @@ -839,25 +839,23 @@ PeerImp::PeerImp( , response_(std::move(response)) , headers_(response_) , compressionEnabled_( - peerFeatureEnabled(headers_, kFeatureCompr, "lz4", app_.config().COMPRESSION) + peerFeatureEnabled(headers_, kFeatureCompr, "lz4", app_.config().compression) ? Compressed::On : Compressed::Off) , txReduceRelayEnabled_( - peerFeatureEnabled(headers_, kFeatureTxrr, app_.config().TX_REDUCE_RELAY_ENABLE)) + peerFeatureEnabled(headers_, kFeatureTxrr, app_.config().txReduceRelayEnable)) , ledgerReplayEnabled_( - peerFeatureEnabled(headers_, kFeatureLedgerReplay, app_.config().LEDGER_REPLAY)) + peerFeatureEnabled(headers_, kFeatureLedgerReplay, app_.config().ledgerReplay)) , ledgerReplayMsgHandler_(app, app.getLedgerReplayer()) { readBuffer_.commit( boost::asio::buffer_copy(readBuffer_.prepare(boost::asio::buffer_size(buffers)), buffers)); - JLOG(journal_.info()) << "compression enabled " << (compressionEnabled_ == Compressed::On) - << " vp reduce-relay base squelch enabled " - << peerFeatureEnabled( - headers_, - kFeatureVprr, - app_.config().VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE) - << " tx reduce-relay enabled " << txReduceRelayEnabled_ << " on " - << remoteAddress_ << " " << id_; + JLOG(journal_.info()) + << "compression enabled " << (compressionEnabled_ == Compressed::On) + << " vp reduce-relay base squelch enabled " + << peerFeatureEnabled(headers_, kFeatureVprr, app_.config().vpReduceRelayBaseSquelchEnable) + << " tx reduce-relay enabled " << txReduceRelayEnabled_ << " on " << remoteAddress_ << " " + << id_; } template diff --git a/src/xrpld/overlay/detail/ProtocolMessage.h b/src/xrpld/overlay/detail/ProtocolMessage.h index ab41cbe337..b1a30bad10 100644 --- a/src/xrpld/overlay/detail/ProtocolMessage.h +++ b/src/xrpld/overlay/detail/ProtocolMessage.h @@ -98,19 +98,19 @@ struct MessageHeader @note This is the sum of sizes of the header and the payload. */ - std::uint32_t total_wire_size = 0; + std::uint32_t totalWireSize = 0; /** The size of the header associated with this message. */ - std::uint32_t header_size = 0; + std::uint32_t headerSize = 0; /** The size of the payload on the wire. */ - std::uint32_t payload_wire_size = 0; + std::uint32_t payloadWireSize = 0; /** Uncompressed message size if the message is compressed. */ - std::uint32_t uncompressed_size = 0; + std::uint32_t uncompressedSize = 0; /** The type of the message. */ - std::uint16_t message_type = 0; + std::uint16_t messageType = 0; /** Indicates which compression algorithm the payload is compressed with. * Currently only lz4 is supported. If None then the message is not @@ -159,10 +159,10 @@ parseMessageHeader(boost::system::error_code& ec, BufferSequence const& bufs, st // - 32 bits are the uncompressed data size if (*iter & 0x80) { - hdr.header_size = kHeaderBytesCompressed; + hdr.headerSize = kHeaderBytesCompressed; // not enough bytes to parse the header - if (size < hdr.header_size) + if (size < hdr.headerSize) { ec = make_error_code(boost::system::errc::success); return std::nullopt; @@ -183,18 +183,18 @@ parseMessageHeader(boost::system::error_code& ec, BufferSequence const& bufs, st } for (int i = 0; i != 4; ++i) - hdr.payload_wire_size = (hdr.payload_wire_size << 8) + *iter++; + hdr.payloadWireSize = (hdr.payloadWireSize << 8) + *iter++; // clear the top four bits (the compression bits). - hdr.payload_wire_size &= 0x0FFFFFFF; + hdr.payloadWireSize &= 0x0FFFFFFF; - hdr.total_wire_size = hdr.header_size + hdr.payload_wire_size; + hdr.totalWireSize = hdr.headerSize + hdr.payloadWireSize; for (int i = 0; i != 2; ++i) - hdr.message_type = (hdr.message_type << 8) + *iter++; + hdr.messageType = (hdr.messageType << 8) + *iter++; for (int i = 0; i != 4; ++i) - hdr.uncompressed_size = (hdr.uncompressed_size << 8) + *iter++; + hdr.uncompressedSize = (hdr.uncompressedSize << 8) + *iter++; return hdr; } @@ -204,9 +204,9 @@ parseMessageHeader(boost::system::error_code& ec, BufferSequence const& bufs, st // - 26 bits are the payload size if ((*iter & 0xFC) == 0) { - hdr.header_size = kHeaderBytes; + hdr.headerSize = kHeaderBytes; - if (size < hdr.header_size) + if (size < hdr.headerSize) { ec = make_error_code(boost::system::errc::success); return std::nullopt; @@ -215,13 +215,13 @@ parseMessageHeader(boost::system::error_code& ec, BufferSequence const& bufs, st hdr.algorithm = Algorithm::None; for (int i = 0; i != 4; ++i) - hdr.payload_wire_size = (hdr.payload_wire_size << 8) + *iter++; + hdr.payloadWireSize = (hdr.payloadWireSize << 8) + *iter++; - hdr.uncompressed_size = hdr.payload_wire_size; - hdr.total_wire_size = hdr.header_size + hdr.payload_wire_size; + hdr.uncompressedSize = hdr.payloadWireSize; + hdr.totalWireSize = hdr.headerSize + hdr.payloadWireSize; for (int i = 0; i != 2; ++i) - hdr.message_type = (hdr.message_type << 8) + *iter++; + hdr.messageType = (hdr.messageType << 8) + *iter++; return hdr; } @@ -240,18 +240,18 @@ parseMessageContent(MessageHeader const& header, Buffers const& buffers) auto m = std::make_shared(); ZeroCopyInputStream stream(buffers); - stream.Skip(header.header_size); + stream.Skip(header.headerSize); if (header.algorithm != compression::Algorithm::None) { std::vector payload; - payload.resize(header.uncompressed_size); + payload.resize(header.uncompressedSize); auto const payloadSize = xrpl::compression::decompress( stream, - header.payload_wire_size, + header.payloadWireSize, payload.data(), - header.uncompressed_size, + header.uncompressedSize, header.algorithm); if (payloadSize == 0 || !m->ParseFromArray(payload.data(), payloadSize)) @@ -279,13 +279,13 @@ invoke(MessageHeader const& header, Buffers const& buffers, Handler& handler) using namespace xrpl::compression; handler.onMessageBegin( - header.message_type, + header.messageType, m, - header.payload_wire_size, - header.uncompressed_size, + header.payloadWireSize, + header.uncompressedSize, header.algorithm != Algorithm::None); handler.onMessage(m); - handler.onMessageEnd(header.message_type, m); + handler.onMessageEnd(header.messageType, m); return true; } @@ -329,8 +329,8 @@ invokeProtocolMessage(Buffers const& buffers, Handler& handler, std::size_t& hin // whose size exceeds this may result in the connection being dropped. A // larger message size may be supported in the future or negotiated as // part of a protocol upgrade. - if (header->payload_wire_size > kMaximumMessageSize || - header->uncompressed_size > kMaximumMessageSize) + if (header->payloadWireSize > kMaximumMessageSize || + header->uncompressedSize > kMaximumMessageSize) { result.second = make_error_code(boost::system::errc::message_size); return result; @@ -345,15 +345,15 @@ invokeProtocolMessage(Buffers const& buffers, Handler& handler, std::size_t& hin // We don't have the whole message yet. This isn't an error but we have // nothing to do. - if (header->total_wire_size > size) + if (header->totalWireSize > size) { - hint = header->total_wire_size - size; + hint = header->totalWireSize - size; return result; } bool success = false; - switch (header->message_type) + switch (header->messageType) { case protocol::mtMANIFESTS: success = detail::invoke(*header, buffers, handler); @@ -420,12 +420,12 @@ invokeProtocolMessage(Buffers const& buffers, Handler& handler, std::size_t& hin success = detail::invoke(*header, buffers, handler); break; default: - handler.onMessageUnknown(header->message_type); + handler.onMessageUnknown(header->messageType); success = true; break; } - result.first = header->total_wire_size; + result.first = header->totalWireSize; if (!success) result.second = make_error_code(boost::system::errc::bad_message); diff --git a/src/xrpld/overlay/detail/TxMetrics.cpp b/src/xrpld/overlay/detail/TxMetrics.cpp index 2df072ef0c..d2597cb19f 100644 --- a/src/xrpld/overlay/detail/TxMetrics.cpp +++ b/src/xrpld/overlay/detail/TxMetrics.cpp @@ -77,13 +77,13 @@ SingleMetrics::addMetrics(std::uint32_t val) { using namespace std::chrono_literals; accum += val; - N++; + n++; auto const timeElapsed = clock_type::now() - intervalStart; auto const timeElapsedInSecs = std::chrono::duration_cast(timeElapsed); if (timeElapsedInSecs >= 1s) { - auto const avg = accum / (perTimeUnit ? timeElapsedInSecs.count() : N); + auto const avg = accum / (perTimeUnit ? timeElapsedInSecs.count() : n); rollingAvgAggregate.push_back(avg); auto const total = @@ -92,7 +92,7 @@ SingleMetrics::addMetrics(std::uint32_t val) intervalStart = clock_type::now(); accum = 0; - N = 0; + n = 0; } } diff --git a/src/xrpld/overlay/detail/TxMetrics.h b/src/xrpld/overlay/detail/TxMetrics.h index 7b8b4bae1b..50df7a65cc 100644 --- a/src/xrpld/overlay/detail/TxMetrics.h +++ b/src/xrpld/overlay/detail/TxMetrics.h @@ -29,7 +29,7 @@ struct SingleMetrics clock_type::time_point intervalStart{clock_type::now()}; std::uint64_t accum{0}; std::uint64_t rollingAvg{0}; - std::uint32_t N{0}; + std::uint32_t n{0}; bool perTimeUnit{true}; boost::circular_buffer rollingAvgAggregate{30, 0ull}; /** Add metrics value diff --git a/src/xrpld/peerfinder/detail/Counts.h b/src/xrpld/peerfinder/detail/Counts.h index ff88421e09..67b8370996 100644 --- a/src/xrpld/peerfinder/detail/Counts.h +++ b/src/xrpld/peerfinder/detail/Counts.h @@ -42,9 +42,9 @@ public: return true; if (s.inbound()) - return in_active_ < in_max_; + return inActive_ < inMax_; - return out_active_ < out_max_; + return outActive_ < outMax_; } /** Returns the number of attempts needed to bring us to the max. */ @@ -67,7 +67,7 @@ public: [[nodiscard]] int outMax() const { - return out_max_; + return outMax_; } /** Returns the number of outbound peers assigned an open slot. @@ -76,7 +76,7 @@ public: [[nodiscard]] int outActive() const { - return out_active_; + return outActive_; } /** Returns the number of fixed connections. */ @@ -90,7 +90,7 @@ public: [[nodiscard]] std::size_t fixedActive() const { - return fixed_active_; + return fixedActive_; } //-------------------------------------------------------------------------- @@ -99,9 +99,9 @@ public: void onConfig(Config const& config) { - out_max_ = config.outPeers; + outMax_ = config.outPeers; if (config.wantIncoming) - in_max_ = config.inPeers; + inMax_ = config.inPeers; } /** Returns the number of accepted connections that haven't handshaked. */ @@ -129,21 +129,21 @@ public: [[nodiscard]] int inMax() const { - return in_max_; + return inMax_; } /** Returns the number of inbound peers assigned an open slot. */ [[nodiscard]] int inboundActive() const { - return in_active_; + return inActive_; } /** Returns the total number of active peers excluding fixed peers. */ [[nodiscard]] int totalActive() const { - return in_active_ + out_active_; + return inActive_ + outActive_; } /** Returns the number of unused inbound slots. @@ -152,8 +152,8 @@ public: [[nodiscard]] int inboundSlotsFree() const { - if (in_active_ < in_max_) - return in_max_ - in_active_; + if (inActive_ < inMax_) + return inMax_ - inActive_; return 0; } @@ -163,8 +163,8 @@ public: [[nodiscard]] int outboundSlotsFree() const { - if (out_active_ < out_max_) - return out_max_ - out_active_; + if (outActive_ < outMax_) + return outMax_ - outActive_; return 0; } @@ -181,7 +181,7 @@ public: // // Fixed peers do not count towards the active outgoing total. - return out_max_ <= 0; + return outMax_ <= 0; } /** Output statistics. */ @@ -191,9 +191,9 @@ public: map["accept"] = acceptCount(); map["connect"] = connectCount(); map["close"] = closingCount(); - map["in"] << in_active_ << "/" << in_max_; - map["out"] << out_active_ << "/" << out_max_; - map["fixed"] = fixed_active_; + map["in"] << inActive_ << "/" << inMax_; + map["out"] << outActive_ << "/" << outMax_; + map["fixed"] = fixedActive_; map["reserved"] = reserved_; map["total"] = active_; } @@ -203,7 +203,7 @@ public: stateString() const { std::stringstream ss; - ss << out_active_ << "/" << out_max_ << " out, " << in_active_ << "/" << in_max_ << " in, " + ss << outActive_ << "/" << outMax_ << " out, " << inActive_ << "/" << inMax_ << " in, " << connectCount() << " connecting, " << closingCount() << " closing"; return ss.str(); } @@ -262,16 +262,16 @@ private: case Slot::State::Active: if (s.fixed()) - adjustCounter(fixed_active_, dir); + adjustCounter(fixedActive_, dir); if (!s.fixed() && !s.reserved()) { if (s.inbound()) { - adjustCounter(in_active_, dir); + adjustCounter(inActive_, dir); } else { - adjustCounter(out_active_, dir); + adjustCounter(outActive_, dir); } } adjustCounter(active_, dir); @@ -297,22 +297,22 @@ private: std::size_t active_{0}; /** Total number of inbound slots. */ - std::size_t in_max_{0}; + std::size_t inMax_{0}; /** Number of inbound slots assigned to active peers. */ - std::size_t in_active_{0}; + std::size_t inActive_{0}; /** Maximum desired outbound slots. */ - std::size_t out_max_{0}; + std::size_t outMax_{0}; /** Active outbound slots. */ - std::size_t out_active_{0}; + std::size_t outActive_{0}; /** Fixed connections. */ std::size_t fixed_{0}; /** Active fixed connections. */ - std::size_t fixed_active_{0}; + std::size_t fixedActive_{0}; /** Reserved connections. */ std::size_t reserved_{0}; diff --git a/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp b/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp index bbaeeff8e6..fcf30fa4f4 100644 --- a/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp +++ b/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp @@ -63,15 +63,15 @@ Config::makeConfig( { PeerFinder::Config config; - config.peerPrivate = cfg.PEER_PRIVATE; + config.peerPrivate = cfg.peerPrivate; // Servers with peer privacy don't want to allow incoming connections config.wantIncoming = (!config.peerPrivate) && (port != 0); - if ((cfg.PEERS_OUT_MAX == 0u) && (cfg.PEERS_IN_MAX == 0u)) + if ((cfg.peersOutMax == 0u) && (cfg.peersInMax == 0u)) { - if (cfg.PEERS_MAX != 0) - config.maxPeers = cfg.PEERS_MAX; + if (cfg.peersMax != 0) + config.maxPeers = cfg.peersMax; config.maxPeers = std::max(config.maxPeers, Tuning::kMinOutCount); config.outPeers = config.calcOutPeers(); @@ -94,8 +94,8 @@ Config::makeConfig( } else { - config.outPeers = cfg.PEERS_OUT_MAX; - config.inPeers = cfg.PEERS_IN_MAX; + config.outPeers = cfg.peersOutMax; + config.inPeers = cfg.peersInMax; config.maxPeers = 0; } @@ -108,7 +108,7 @@ Config::makeConfig( // if it's a private peer or we are running as standalone // automatic connections would defeat the purpose. - config.autoConnect = !cfg.standalone() && !cfg.PEER_PRIVATE; + config.autoConnect = !cfg.standalone() && !cfg.peerPrivate; config.listeningPort = port; config.features = ""; config.ipLimit = ipLimit; diff --git a/src/xrpld/peerfinder/detail/SlotImp.cpp b/src/xrpld/peerfinder/detail/SlotImp.cpp index f6ff24e6a6..a54ddb56e7 100644 --- a/src/xrpld/peerfinder/detail/SlotImp.cpp +++ b/src/xrpld/peerfinder/detail/SlotImp.cpp @@ -23,9 +23,9 @@ SlotImp::SlotImp( , fixed_(fixed) , reserved_(false) , state_(State::Accept) - , remote_endpoint_(std::move(remoteEndpoint)) - , local_endpoint_(localEndpoint) - , listening_port_(kUnknownPort) + , remoteEndpoint_(std::move(remoteEndpoint)) + , localEndpoint_(localEndpoint) + , listeningPort_(kUnknownPort) , checked(false) , canAccept(false) , connectivityCheckInProgress(false) @@ -38,8 +38,8 @@ SlotImp::SlotImp(beast::IP::Endpoint remoteEndpoint, bool fixed, clock_type& clo , fixed_(fixed) , reserved_(false) , state_(State::Connect) - , remote_endpoint_(std::move(remoteEndpoint)) - , listening_port_(kUnknownPort) + , remoteEndpoint_(std::move(remoteEndpoint)) + , listeningPort_(kUnknownPort) , checked(true) , canAccept(true) , connectivityCheckInProgress(false) diff --git a/src/xrpld/peerfinder/detail/SlotImp.h b/src/xrpld/peerfinder/detail/SlotImp.h index 0d09a37539..0cf4d7a3c8 100644 --- a/src/xrpld/peerfinder/detail/SlotImp.h +++ b/src/xrpld/peerfinder/detail/SlotImp.h @@ -52,19 +52,19 @@ public: beast::IP::Endpoint const& remoteEndpoint() const override { - return remote_endpoint_; + return remoteEndpoint_; } std::optional const& localEndpoint() const override { - return local_endpoint_; + return localEndpoint_; } std::optional const& publicKey() const override { - return public_key_; + return publicKey_; } std::string @@ -76,7 +76,7 @@ public: std::optional listeningPort() const override { - std::uint32_t const value = listening_port_; + std::uint32_t const value = listeningPort_; if (value == kUnknownPort) return std::nullopt; return value; @@ -85,25 +85,25 @@ public: void setListeningPort(std::uint16_t port) { - listening_port_ = port; + listeningPort_ = port; } void localEndpoint(beast::IP::Endpoint const& endpoint) { - local_endpoint_ = endpoint; + localEndpoint_ = endpoint; } void remoteEndpoint(beast::IP::Endpoint const& endpoint) { - remote_endpoint_ = endpoint; + remoteEndpoint_ = endpoint; } void publicKey(PublicKey const& key) { - public_key_ = key; + publicKey_ = key; } void @@ -160,12 +160,12 @@ private: bool const fixed_; bool reserved_; State state_; - beast::IP::Endpoint remote_endpoint_; - std::optional local_endpoint_; - std::optional public_key_; + beast::IP::Endpoint remoteEndpoint_; + std::optional localEndpoint_; + std::optional publicKey_; static constexpr std::int32_t kUnknownPort = -1; - std::atomic listening_port_; + std::atomic listeningPort_; public: // DEPRECATED public data members diff --git a/src/xrpld/rpc/ServerHandler.h b/src/xrpld/rpc/ServerHandler.h index 6b4cc34cc5..0aac05ea44 100644 --- a/src/xrpld/rpc/ServerHandler.h +++ b/src/xrpld/rpc/ServerHandler.h @@ -46,8 +46,8 @@ public: std::uint16_t port = 0; std::string user; std::string password; - std::string admin_user; - std::string admin_password; + std::string adminUser; + std::string adminPassword; }; // Configuration when acting in client role @@ -72,9 +72,9 @@ private: Setup setup_; Endpoints endpoints_; JobQueue& jobQueue_; - beast::insight::Counter rpc_requests_; - beast::insight::Event rpc_size_; - beast::insight::Event rpc_time_; + beast::insight::Counter rpcRequests_; + beast::insight::Event rpcSize_; + beast::insight::Event rpcTime_; std::mutex mutex_; std::condition_variable condition_; bool stopped_{false}; diff --git a/src/xrpld/rpc/detail/AssetCache.h b/src/xrpld/rpc/detail/AssetCache.h index 9112d78715..dd53620cdf 100644 --- a/src/xrpld/rpc/detail/AssetCache.h +++ b/src/xrpld/rpc/detail/AssetCache.h @@ -56,10 +56,10 @@ private: { AccountID account; LineDirection direction; - std::size_t hash_value; + std::size_t hashValue; AccountKey(AccountID const& account, LineDirection direction, std::size_t hash) - : account(account), direction(direction), hash_value(hash) + : account(account), direction(direction), hashValue(hash) { } @@ -71,14 +71,14 @@ private: bool operator==(AccountKey const& lhs) const { - return hash_value == lhs.hash_value && account == lhs.account && + return hashValue == lhs.hashValue && account == lhs.account && direction == lhs.direction; } [[nodiscard]] std::size_t getHash() const { - return hash_value; + return hashValue; } struct Hash diff --git a/src/xrpld/rpc/detail/PathRequest.cpp b/src/xrpld/rpc/detail/PathRequest.cpp index 6207b3393f..06507319f7 100644 --- a/src/xrpld/rpc/detail/PathRequest.cpp +++ b/src/xrpld/rpc/detail/PathRequest.cpp @@ -101,16 +101,16 @@ PathRequest::~PathRequest() return; std::string fast, full; - if (quick_reply_ != steady_clock::time_point{}) + if (quickReply_ != steady_clock::time_point{}) { fast = " fast:"; - fast += std::to_string(duration_cast(quick_reply_ - created_).count()); + fast += std::to_string(duration_cast(quickReply_ - created_).count()); fast += "ms"; } - if (full_reply_ != steady_clock::time_point{}) + if (fullReply_ != steady_clock::time_point{}) { full = " full:"; - full += std::to_string(duration_cast(full_reply_ - created_).count()); + full += std::to_string(duration_cast(fullReply_ - created_).count()); full += "ms"; } stream << iIdentifier_ << " complete:" << fast << full @@ -180,7 +180,7 @@ PathRequest::isValid(std::shared_ptr const& crCache) if (!raSrcAccount_ || !raDstAccount_) return false; - if (!convert_all_ && (saSendMax_ || saDstAmount_ <= beast::kZero)) + if (!convertAll_ && (saSendMax_ || saDstAmount_ <= beast::kZero)) { // If send max specified, dst amt must be -1. jvStatus_ = rpcError(RpcDstAmtMalformed); @@ -210,7 +210,7 @@ PathRequest::isValid(std::shared_ptr const& crCache) return false; } - if (!convert_all_ && saDstAmount_ < STAmount(lrLedger->fees().reserve)) + if (!convertAll_ && saDstAmount_ < STAmount(lrLedger->fees().reserve)) { // Payment must meet reserve. jvStatus_ = rpcError(RpcDstAmtMalformed); @@ -312,9 +312,9 @@ PathRequest::parseJson(json::Value const& jvParams) return PFR_PJ_INVALID; } - convert_all_ = saDstAmount_ == STAmount(saDstAmount_.asset(), 1u, 0, true); + convertAll_ = saDstAmount_ == STAmount(saDstAmount_.asset(), 1u, 0, true); - if (!validAsset(saDstAmount_.asset()) || (!convert_all_ && saDstAmount_ <= beast::kZero)) + if (!validAsset(saDstAmount_.asset()) || (!convertAll_ && saDstAmount_ <= beast::kZero)) { jvStatus_ = rpcError(RpcDstAmtMalformed); return PFR_PJ_INVALID; @@ -323,7 +323,7 @@ PathRequest::parseJson(json::Value const& jvParams) if (jvParams.isMember(jss::send_max)) { // Send_max requires destination amount to be -1. - if (!convert_all_) + if (!convertAll_) { jvStatus_ = rpcError(RpcDstAmtMalformed); return PFR_PJ_INVALID; @@ -577,7 +577,7 @@ PathRequest::findPaths( } } - auto const dstAmount = convertAmount(saDstAmount_, convert_all_); + auto const dstAmount = convertAmount(saDstAmount_, convertAll_); hash_map> currencyMap; for (auto const& asset : sourceAssets) { @@ -622,7 +622,7 @@ PathRequest::findPaths( JLOG(journal_.debug()) << iIdentifier_ << " Paths found, calling rippleCalc"; path::RippleCalc::Input rcInput; - if (convert_all_) + if (convertAll_) rcInput.partialPaymentAllowed = true; auto sandbox = std::make_unique(&*cache->getLedger(), TapNone); auto rc = path::RippleCalc::rippleCalculate( @@ -639,7 +639,7 @@ PathRequest::findPaths( app_, &rcInput); - if (!convert_all_ && !fullLiquidityPath.empty() && + if (!convertAll_ && !fullLiquidityPath.empty() && (rc.result() == terNO_LINE || rc.result() == tecPATH_PARTIAL)) { JLOG(journal_.debug()) << iIdentifier_ << " Trying with an extra path element"; @@ -679,7 +679,7 @@ PathRequest::findPaths( jvEntry[jss::source_amount] = rc.actualAmountIn.getJson(JsonOptions::Values::None); jvEntry[jss::paths_computed] = ps.getJson(JsonOptions::Values::None); - if (convert_all_) + if (convertAll_) { jvEntry[jss::destination_amount] = rc.actualAmountOut.getJson(JsonOptions::Values::None); @@ -754,33 +754,33 @@ PathRequest::doUpdate( // first pass if (loaded || fast) { - iLevel_ = app_.config().PATH_SEARCH_FAST; + iLevel_ = app_.config().pathSearchFast; } else { - iLevel_ = app_.config().PATH_SEARCH; + iLevel_ = app_.config().pathSearch; } } - else if ((iLevel_ == app_.config().PATH_SEARCH_FAST) && !fast) + else if ((iLevel_ == app_.config().pathSearchFast) && !fast) { // leaving fast pathfinding - iLevel_ = app_.config().PATH_SEARCH; - if (loaded && (iLevel_ > app_.config().PATH_SEARCH_FAST)) + iLevel_ = app_.config().pathSearch; + if (loaded && (iLevel_ > app_.config().pathSearchFast)) --iLevel_; } else if (bLastSuccess_) { // decrement, if possible - if (iLevel_ > app_.config().PATH_SEARCH || - (loaded && (iLevel_ > app_.config().PATH_SEARCH_FAST))) + if (iLevel_ > app_.config().pathSearch || + (loaded && (iLevel_ > app_.config().pathSearchFast))) --iLevel_; } else { // adjust as needed - if (!loaded && (iLevel_ < app_.config().PATH_SEARCH_MAX)) + if (!loaded && (iLevel_ < app_.config().pathSearchMax)) ++iLevel_; - if (loaded && (iLevel_ > app_.config().PATH_SEARCH_FAST)) + if (loaded && (iLevel_ > app_.config().pathSearchFast)) --iLevel_; } @@ -798,15 +798,15 @@ PathRequest::doUpdate( newStatus = rpcError(RpcInternal); } - if (fast && quick_reply_ == steady_clock::time_point{}) + if (fast && quickReply_ == steady_clock::time_point{}) { - quick_reply_ = steady_clock::now(); - owner_.reportFast(duration_cast(quick_reply_ - created_)); + quickReply_ = steady_clock::now(); + owner_.reportFast(duration_cast(quickReply_ - created_)); } - else if (!fast && full_reply_ == steady_clock::time_point{}) + else if (!fast && fullReply_ == steady_clock::time_point{}) { - full_reply_ = steady_clock::now(); - owner_.reportFull(duration_cast(full_reply_ - created_)); + fullReply_ = steady_clock::now(); + owner_.reportFull(duration_cast(fullReply_ - created_)); } { diff --git a/src/xrpld/rpc/detail/PathRequest.h b/src/xrpld/rpc/detail/PathRequest.h index 17381c5ea5..de8c10de0e 100644 --- a/src/xrpld/rpc/detail/PathRequest.h +++ b/src/xrpld/rpc/detail/PathRequest.h @@ -140,7 +140,7 @@ private: std::optional domain_; - bool convert_all_{}; + bool convertAll_{}; std::recursive_mutex indexLock_; LedgerIndex lastIndex_; @@ -152,8 +152,8 @@ private: int const iIdentifier_; std::chrono::steady_clock::time_point const created_; - std::chrono::steady_clock::time_point quick_reply_; - std::chrono::steady_clock::time_point full_reply_; + std::chrono::steady_clock::time_point quickReply_; + std::chrono::steady_clock::time_point fullReply_; static unsigned int const kMaxPaths = 4; }; diff --git a/src/xrpld/rpc/detail/Pathfinder.cpp b/src/xrpld/rpc/detail/Pathfinder.cpp index e949c9b6ff..daa50cfb07 100644 --- a/src/xrpld/rpc/detail/Pathfinder.cpp +++ b/src/xrpld/rpc/detail/Pathfinder.cpp @@ -226,7 +226,7 @@ Pathfinder::Pathfinder( , srcPathAsset_(uSrcPathAsset) , srcIssuer_(uSrcIssuer) , srcAmount_(amountFromPathAsset(uSrcPathAsset, uSrcIssuer, uSrcAccount)) - , convert_all_(convertAllCheck(dstAmount_)) + , convertAll_(convertAllCheck(dstAmount_)) , domain_(domain) , ledger_(cache->getLedger()) , rLCache_(cache) @@ -398,7 +398,7 @@ Pathfinder::getPathLiquidity( try { // Compute a path that provides at least the minimum liquidity. - if (convert_all_) + if (convertAll_) rcInput.partialPaymentAllowed = true; auto rc = path::RippleCalc::rippleCalculate( @@ -418,7 +418,7 @@ Pathfinder::getPathLiquidity( qualityOut = getRate(rc.actualAmountOut, rc.actualAmountIn); amountOut = rc.actualAmountOut; - if (!convert_all_) + if (!convertAll_) { // Now try to compute the remaining liquidity. rcInput.partialPaymentAllowed = true; @@ -451,7 +451,7 @@ Pathfinder::getPathLiquidity( void Pathfinder::computePathRanks(int maxPaths, std::function const& continueCallback) { - remainingAmount_ = convertAmount(dstAmount_, convert_all_); + remainingAmount_ = convertAmount(dstAmount_, convertAll_); // Must subtract liquidity in default path from remaining amount. try @@ -536,7 +536,7 @@ Pathfinder::rankPaths( rankedPaths.reserve(paths.size()); auto const saMinDstAmount = [&]() -> STAmount { - if (!convert_all_) + if (!convertAll_) { // Ignore paths that move only very small amounts. return smallestUsefulAmount(dstAmount_, maxPaths); @@ -581,7 +581,7 @@ Pathfinder::rankPaths( std::ranges::sort( rankedPaths, [&](Pathfinder::PathRank const& a, Pathfinder::PathRank const& b) { // 1) Higher quality (lower cost) is better - if (!convert_all_ && a.quality != b.quality) + if (!convertAll_ && a.quality != b.quality) return a.quality < b.quality; // 2) More liquidity (higher volume) is better diff --git a/src/xrpld/rpc/detail/Pathfinder.h b/src/xrpld/rpc/detail/Pathfinder.h index de2c2d83e6..36caab308e 100644 --- a/src/xrpld/rpc/detail/Pathfinder.h +++ b/src/xrpld/rpc/detail/Pathfinder.h @@ -178,7 +178,7 @@ private: /** The amount remaining from srcAccount_ after the default liquidity has been removed. */ STAmount remainingAmount_; - bool convert_all_; + bool convertAll_; std::optional domain_; std::shared_ptr ledger_; diff --git a/src/xrpld/rpc/detail/RPCCall.cpp b/src/xrpld/rpc/detail/RPCCall.cpp index 24fcc652df..f405ffe4de 100644 --- a/src/xrpld/rpc/detail/RPCCall.cpp +++ b/src/xrpld/rpc/detail/RPCCall.cpp @@ -1702,19 +1702,19 @@ rpcClient( // line client works without a config file } - if (config.rpc_ip) + if (config.rpcIp) { - setup.client.ip = config.rpc_ip->address().to_string(); - setup.client.port = config.rpc_ip->port(); + setup.client.ip = config.rpcIp->address().to_string(); + setup.client.port = config.rpcIp->port(); } json::Value jvParams(json::ValueType::Array); - if (!setup.client.admin_user.empty()) - jvRequest["admin_user"] = setup.client.admin_user; + if (!setup.client.adminUser.empty()) + jvRequest["admin_user"] = setup.client.adminUser; - if (!setup.client.admin_password.empty()) - jvRequest["admin_password"] = setup.client.admin_password; + if (!setup.client.adminPassword.empty()) + jvRequest["admin_password"] = setup.client.adminPassword; if (jvRequest.isObject()) { diff --git a/src/xrpld/rpc/detail/RPCHandler.cpp b/src/xrpld/rpc/detail/RPCHandler.cpp index 6b032317d3..7e30b81fa6 100644 --- a/src/xrpld/rpc/detail/RPCHandler.cpp +++ b/src/xrpld/rpc/detail/RPCHandler.cpp @@ -135,7 +135,7 @@ fillHandler(JsonContext& context, Handler const*& result) JLOG(context.j.trace()) << "COMMAND:" << strCommand; JLOG(context.j.trace()) << "REQUEST:" << context.params; - auto handler = getHandler(context.apiVersion, context.app.config().BETA_RPC_API, strCommand); + auto handler = getHandler(context.apiVersion, context.app.config().betaRpcApi, strCommand); if (handler == nullptr) return RpcUnknownCommand; diff --git a/src/xrpld/rpc/detail/RPCSub.cpp b/src/xrpld/rpc/detail/RPCSub.cpp index 56291ab34b..92e9849939 100644 --- a/src/xrpld/rpc/detail/RPCSub.cpp +++ b/src/xrpld/rpc/detail/RPCSub.cpp @@ -39,7 +39,7 @@ public: std::string strPassword, ServiceRegistry& registry) : RPCSub(source) - , io_context_(ioContext) + , ioContext_(ioContext) , jobQueue_(jobQueue) , url_(strUrl) , username_(std::move(strUsername)) @@ -159,7 +159,7 @@ private: JLOG(j_.info()) << "RPCCall::fromNetwork: " << ip_; RPCCall::fromNetwork( - io_context_, + ioContext_, ip_, port_, username_, @@ -180,7 +180,7 @@ private: } private: - boost::asio::io_context& io_context_; + boost::asio::io_context& ioContext_; JobQueue& jobQueue_; std::string url_; diff --git a/src/xrpld/rpc/detail/Role.cpp b/src/xrpld/rpc/detail/Role.cpp index dcc6965d58..68c5fcc484 100644 --- a/src/xrpld/rpc/detail/Role.cpp +++ b/src/xrpld/rpc/detail/Role.cpp @@ -28,14 +28,14 @@ bool passwordUnrequiredOrSentCorrect(Port const& port, json::Value const& params) { XRPL_ASSERT( - !(port.admin_nets_v4.empty() && port.admin_nets_v6.empty()), + !(port.adminNetsV4.empty() && port.adminNetsV6.empty()), "xrpl::passwordUnrequiredOrSentCorrect : non-empty admin nets"); - bool const passwordRequired = (!port.admin_user.empty() || !port.admin_password.empty()); + bool const passwordRequired = (!port.adminUser.empty() || !port.adminPassword.empty()); return !passwordRequired || ((params["admin_password"].isString() && - params["admin_password"].asString() == port.admin_password) && - (params["admin_user"].isString() && params["admin_user"].asString() == port.admin_user)); + params["admin_password"].asString() == port.adminPassword) && + (params["admin_user"].isString() && params["admin_user"].asString() == port.adminUser)); } bool @@ -80,7 +80,7 @@ ipAllowed( bool isAdmin(Port const& port, json::Value const& params, beast::IP::Address const& remoteIp) { - return ipAllowed(remoteIp, port.admin_nets_v4, port.admin_nets_v6) && + return ipAllowed(remoteIp, port.adminNetsV4, port.adminNetsV6) && passwordUnrequiredOrSentCorrect(port, params); } @@ -98,7 +98,7 @@ requestRole( if (required == Role::ADMIN) return Role::FORBID; - if (ipAllowed(remoteIp.address(), port.secure_gateway_nets_v4, port.secure_gateway_nets_v6)) + if (ipAllowed(remoteIp.address(), port.secureGatewayNetsV4, port.secureGatewayNetsV6)) { if (!user.empty()) return Role::IDENTIFIED; diff --git a/src/xrpld/rpc/detail/ServerHandler.cpp b/src/xrpld/rpc/detail/ServerHandler.cpp index 2ed6630af2..0bdece3ec3 100644 --- a/src/xrpld/rpc/detail/ServerHandler.cpp +++ b/src/xrpld/rpc/detail/ServerHandler.cpp @@ -139,9 +139,9 @@ ServerHandler::ServerHandler( , jobQueue_(jobQueue) { auto const& group(cm.group("rpc")); - rpc_requests_ = group->makeCounter("requests"); - rpc_size_ = group->makeEvent("size"); - rpc_time_ = group->makeEvent("time"); + rpcRequests_ = group->makeCounter("requests"); + rpcSize_ = group->makeEvent("size"); + rpcTime_ = group->makeEvent("time"); } ServerHandler::~ServerHandler() @@ -429,7 +429,7 @@ ServerHandler::processSession( Resource::Charge loadType = Resource::kFeeReferenceRpc; try { - auto apiVersion = RPC::getAPIVersionNumber(jv, app_.config().BETA_RPC_API); + auto apiVersion = RPC::getAPIVersionNumber(jv, app_.config().betaRpcApi); if (apiVersion == RPC::kApiInvalidVersion || (!jv.isMember(jss::command) && !jv.isMember(jss::method)) || (jv.isMember(jss::command) && !jv[jss::command].isString()) || @@ -457,7 +457,7 @@ ServerHandler::processSession( auto required = RPC::roleRequired( apiVersion, - app_.config().BETA_RPC_API, + app_.config().betaRpcApi, jv.isMember(jss::command) ? jv[jss::command].asString() : jv[jss::method].asString()); auto role = requestRole( required, @@ -657,13 +657,13 @@ ServerHandler::processRequest( jsonRPC[jss::params].size() > 0 && jsonRPC[jss::params][0u].isObject()) { apiVersion = RPC::getAPIVersionNumber( - jsonRPC[jss::params][json::UInt(0)], app_.config().BETA_RPC_API); + jsonRPC[jss::params][json::UInt(0)], app_.config().betaRpcApi); } if (apiVersion == RPC::kApiVersionIfUnspecified && batch) { // for batch request, api_version may be at a different level - apiVersion = RPC::getAPIVersionNumber(jsonRPC, app_.config().BETA_RPC_API); + apiVersion = RPC::getAPIVersionNumber(jsonRPC, app_.config().betaRpcApi); } if (apiVersion == RPC::kApiInvalidVersion) @@ -686,7 +686,7 @@ ServerHandler::processRequest( if (jsonRPC.isMember(jss::method) && jsonRPC[jss::method].isString()) { required = RPC::roleRequired( - apiVersion, app_.config().BETA_RPC_API, jsonRPC[jss::method].asString()); + apiVersion, app_.config().betaRpcApi, jsonRPC[jss::method].asString()); } if (jsonRPC.isMember(jss::params) && jsonRPC[jss::params].isArray() && @@ -993,11 +993,11 @@ ServerHandler::processRequest( auto response = to_string(reply); - rpc_time_.notify( + rpcTime_.notify( std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - start)); - ++rpc_requests_; - rpc_size_.notify(beast::insight::Event::value_type{response.size()}); + ++rpcRequests_; + rpcSize_.notify(beast::insight::Event::value_type{response.size()}); response += '\n'; @@ -1060,13 +1060,13 @@ ServerHandler::Setup::makeContexts() { if (p.secure()) { - if (p.ssl_key.empty() && p.ssl_cert.empty() && p.ssl_chain.empty()) + if (p.sslKey.empty() && p.sslCert.empty() && p.sslChain.empty()) { - p.context = makeSslContext(p.ssl_ciphers); + p.context = makeSslContext(p.sslCiphers); } else { - p.context = makeSslContextAuthed(p.ssl_key, p.ssl_cert, p.ssl_chain, p.ssl_ciphers); + p.context = makeSslContextAuthed(p.sslKey, p.sslCert, p.sslChain, p.sslCiphers); } } else @@ -1106,19 +1106,19 @@ toPort(ParsedPort const& parsed, std::ostream& log) p.user = parsed.user; p.password = parsed.password; - p.admin_user = parsed.admin_user; - p.admin_password = parsed.admin_password; - p.ssl_key = parsed.ssl_key; - p.ssl_cert = parsed.ssl_cert; - p.ssl_chain = parsed.ssl_chain; - p.ssl_ciphers = parsed.ssl_ciphers; - p.pmd_options = parsed.pmd_options; - p.ws_queue_limit = parsed.ws_queue_limit; + p.adminUser = parsed.adminUser; + p.adminPassword = parsed.adminPassword; + p.sslKey = parsed.sslKey; + p.sslCert = parsed.sslCert; + p.sslChain = parsed.sslChain; + p.sslCiphers = parsed.sslCiphers; + p.pmdOptions = parsed.pmdOptions; + p.wsQueueLimit = parsed.wsQueueLimit; p.limit = parsed.limit; - p.admin_nets_v4 = parsed.admin_nets_v4; - p.admin_nets_v6 = parsed.admin_nets_v6; - p.secure_gateway_nets_v4 = parsed.secure_gateway_nets_v4; - p.secure_gateway_nets_v6 = parsed.secure_gateway_nets_v6; + p.adminNetsV4 = parsed.adminNetsV4; + p.adminNetsV6 = parsed.adminNetsV6; + p.secureGatewayNetsV4 = parsed.secureGatewayNetsV4; + p.secureGatewayNetsV6 = parsed.secureGatewayNetsV6; return p; } @@ -1221,8 +1221,8 @@ setupClient(ServerHandler::Setup& setup) setup.client.port = iter->port; setup.client.user = iter->user; setup.client.password = iter->password; - setup.client.admin_user = iter->admin_user; - setup.client.admin_password = iter->admin_password; + setup.client.adminUser = iter->adminUser; + setup.client.adminPassword = iter->adminPassword; } // Fill out the overlay portion of the Setup diff --git a/src/xrpld/rpc/detail/TransactionSign.cpp b/src/xrpld/rpc/detail/TransactionSign.cpp index 9faa82dfe8..dd0e78c178 100644 --- a/src/xrpld/rpc/detail/TransactionSign.cpp +++ b/src/xrpld/rpc/detail/TransactionSign.cpp @@ -312,7 +312,7 @@ checkPayment( std::nullopt, domain, app); - if (pf.findPaths(app.config().PATH_SEARCH_OLD)) + if (pf.findPaths(app.config().pathSearchOld)) { // 4 is the maximum paths pf.computePathRanks(4); @@ -508,7 +508,7 @@ transactionPreProcessImpl( validatedLedgerAge, app.config(), app.getFeeTrack(), - getAPIVersionNumber(params, app.config().BETA_RPC_API)); + getAPIVersionNumber(params, app.config().betaRpcApi)); if (RPC::containsError(txJsonResult)) return std::move(txJsonResult); @@ -846,16 +846,16 @@ getTxFee(Application const& app, Config const& config, json::Value tx) if (tx.isMember(jss::Signers)) { if (!tx[jss::Signers].isArray()) - return config.FEES.reference_fee; + return config.fees.referenceFee; if (tx[jss::Signers].size() > STTx::kMaxMultiSigners) - return config.FEES.reference_fee; + return config.fees.referenceFee; // check multi-signed signers for (auto& signer : tx[jss::Signers]) { if (!signer.isMember(jss::Signer) || !signer[jss::Signer].isObject()) - return config.FEES.reference_fee; + return config.fees.referenceFee; if (!signer[jss::Signer].isMember(jss::SigningPubKey)) { // autofill SigningPubKey @@ -872,7 +872,7 @@ getTxFee(Application const& app, Config const& config, json::Value tx) STParsedJSONObject parsed(std::string(jss::tx_json), tx); if (!parsed.object.has_value()) { - return config.FEES.reference_fee; + return config.fees.referenceFee; } try @@ -880,13 +880,13 @@ getTxFee(Application const& app, Config const& config, json::Value tx) STTx const& stTx = STTx(std::move(parsed.object.value())); std::string reason; if (!passesLocalChecks(stTx, reason)) - return config.FEES.reference_fee; + return config.fees.referenceFee; return calculateBaseFee(*app.getOpenLedger().current(), stTx); } catch (std::exception& e) { - return config.FEES.reference_fee; + return config.fees.referenceFee; } } @@ -1305,7 +1305,7 @@ transactionSubmitMultiSigned( validatedLedgerAge, app.config(), app.getFeeTrack(), - getAPIVersionNumber(jvRequest, app.config().BETA_RPC_API)); + getAPIVersionNumber(jvRequest, app.config().betaRpcApi)); if (RPC::containsError(txJsonResult)) return std::move(txJsonResult); diff --git a/src/xrpld/rpc/detail/WSInfoSub.h b/src/xrpld/rpc/detail/WSInfoSub.h index 336f35957f..c224b93e0e 100644 --- a/src/xrpld/rpc/detail/WSInfoSub.h +++ b/src/xrpld/rpc/detail/WSInfoSub.h @@ -24,8 +24,8 @@ public: auto const& h = ws->request(); if (ipAllowed( beast::IPAddressConversion::fromAsio(ws->remoteEndpoint()).address(), - ws->port().secure_gateway_nets_v4, - ws->port().secure_gateway_nets_v6)) + ws->port().secureGatewayNetsV4, + ws->port().secureGatewayNetsV6)) { auto it = h.find("X-User"); if (it != h.end()) diff --git a/src/xrpld/rpc/handlers/orderbook/PathFind.cpp b/src/xrpld/rpc/handlers/orderbook/PathFind.cpp index 7955a93eb4..3a6b52ee98 100644 --- a/src/xrpld/rpc/handlers/orderbook/PathFind.cpp +++ b/src/xrpld/rpc/handlers/orderbook/PathFind.cpp @@ -15,7 +15,7 @@ namespace xrpl { json::Value doPathFind(RPC::JsonContext& context) { - if (context.app.config().PATH_SEARCH_MAX == 0) + if (context.app.config().pathSearchMax == 0) return rpcError(RpcNotSupported); auto lpLedger = context.ledgerMaster.getClosedLedger(); diff --git a/src/xrpld/rpc/handlers/orderbook/RipplePathFind.cpp b/src/xrpld/rpc/handlers/orderbook/RipplePathFind.cpp index 956d50add7..b7edfb6dbe 100644 --- a/src/xrpld/rpc/handlers/orderbook/RipplePathFind.cpp +++ b/src/xrpld/rpc/handlers/orderbook/RipplePathFind.cpp @@ -23,7 +23,7 @@ namespace xrpl { json::Value doRipplePathFind(RPC::JsonContext& context) { - if (context.app.config().PATH_SEARCH_MAX == 0) + if (context.app.config().pathSearchMax == 0) return rpcError(RpcNotSupported); context.loadType = Resource::kFeeHeavyBurdenRpc; diff --git a/src/xrpld/rpc/handlers/server_info/Version.h b/src/xrpld/rpc/handlers/server_info/Version.h index 64c150162e..1868df5d40 100644 --- a/src/xrpld/rpc/handlers/server_info/Version.h +++ b/src/xrpld/rpc/handlers/server_info/Version.h @@ -8,7 +8,7 @@ class VersionHandler { public: explicit VersionHandler(JsonContext& c) - : apiVersion_(c.apiVersion), betaEnabled_(c.app.config().BETA_RPC_API) + : apiVersion_(c.apiVersion), betaEnabled_(c.app.config().betaRpcApi) { } diff --git a/src/xrpld/rpc/json_body.h b/src/xrpld/rpc/json_body.h index 9f881cacbc..4520a8bf39 100644 --- a/src/xrpld/rpc/json_body.h +++ b/src/xrpld/rpc/json_body.h @@ -56,7 +56,7 @@ struct JsonBody class writer // NOLINT(readability-identifier-naming) -- Boost.Beast body concept name { - std::string body_string_; + std::string bodyString_; public: using const_buffers_type = boost::asio::const_buffer; @@ -65,7 +65,7 @@ struct JsonBody explicit writer( boost::beast::http::header const& fields, value_type const& value) - : body_string_(to_string(value)) + : bodyString_(to_string(value)) { } @@ -81,7 +81,7 @@ struct JsonBody get(boost::beast::error_code& ec) { ec.assign(0, ec.category()); - return {{const_buffers_type{body_string_.data(), body_string_.size()}, false}}; + return {{const_buffers_type{bodyString_.data(), bodyString_.size()}, false}}; } }; }; From 28cc20c81679fd8bb9aa3ac85e450d1daf387816 Mon Sep 17 00:00:00 2001 From: Shawn Xie <35279399+shawnxie999@users.noreply.github.com> Date: Thu, 21 May 2026 02:19:04 -0400 Subject: [PATCH 007/135] fix: Fix wrong hybrid offer orderbook placement and update `LedgerStateFix` to amend `ExchangeRate` meta (#7087) Co-authored-by: Peter Chen --- .../xrpl/protocol/detail/transactions.macro | 1 + .../transactions/LedgerStateFix.h | 37 +++ .../xrpl/tx/invariants/DirectoryInvariant.h | 27 ++ include/xrpl/tx/invariants/InvariantCheck.h | 2 + include/xrpl/tx/transactors/dex/OfferCreate.h | 1 + .../tx/transactors/system/LedgerStateFix.h | 1 + .../tx/invariants/DirectoryInvariant.cpp | 96 +++++++ .../tx/transactors/dex/OfferCreate.cpp | 13 +- .../tx/transactors/system/LedgerStateFix.cpp | 86 +++++- src/test/app/FixNFTokenPageLinks_test.cpp | 7 + src/test/app/Invariants_test.cpp | 103 +++++++ src/test/app/PermissionedDEX_test.cpp | 262 ++++++++++++++++++ src/test/jtx/impl/ledgerStateFixes.cpp | 13 + src/test/jtx/ledgerStateFix.h | 4 + .../transactions/LedgerStateFixTests.cpp | 21 ++ 15 files changed, 669 insertions(+), 5 deletions(-) create mode 100644 include/xrpl/tx/invariants/DirectoryInvariant.h create mode 100644 src/libxrpl/tx/invariants/DirectoryInvariant.cpp diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 9bac9ef7db..450e2558cc 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -688,6 +688,7 @@ TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, ({ {sfLedgerFixType, SoeRequired}, {sfOwner, SoeOptional}, + {sfBookDirectory, SoeOptional}, })) /** This transaction type creates a MPTokensIssuance instance */ diff --git a/include/xrpl/protocol_autogen/transactions/LedgerStateFix.h b/include/xrpl/protocol_autogen/transactions/LedgerStateFix.h index 3c58815a02..52723ad5eb 100644 --- a/include/xrpl/protocol_autogen/transactions/LedgerStateFix.h +++ b/include/xrpl/protocol_autogen/transactions/LedgerStateFix.h @@ -83,6 +83,32 @@ public: { return this->tx_->isFieldPresent(sfOwner); } + + /** + * @brief Get sfBookDirectory (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getBookDirectory() const + { + if (hasBookDirectory()) + { + return this->tx_->at(sfBookDirectory); + } + return std::nullopt; + } + + /** + * @brief Check if sfBookDirectory is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasBookDirectory() const + { + return this->tx_->isFieldPresent(sfBookDirectory); + } }; /** @@ -149,6 +175,17 @@ public: return *this; } + /** + * @brief Set sfBookDirectory (SoeOptional) + * @return Reference to this builder for method chaining. + */ + LedgerStateFixBuilder& + setBookDirectory(std::decay_t const& value) + { + object_[sfBookDirectory] = value; + return *this; + } + /** * @brief Build and return the LedgerStateFix wrapper. * @param publicKey The public key for signing. diff --git a/include/xrpl/tx/invariants/DirectoryInvariant.h b/include/xrpl/tx/invariants/DirectoryInvariant.h new file mode 100644 index 0000000000..96643ea465 --- /dev/null +++ b/include/xrpl/tx/invariants/DirectoryInvariant.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +class ValidBookDirectory +{ + bool badBookDirectory_ = false; + hash_set rootIndexes_; + +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&); +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/invariants/InvariantCheck.h b/include/xrpl/tx/invariants/InvariantCheck.h index bc9608fd56..a58ac0df50 100644 --- a/include/xrpl/tx/invariants/InvariantCheck.h +++ b/include/xrpl/tx/invariants/InvariantCheck.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -393,6 +394,7 @@ using InvariantChecks = std::tuple< ValidMPTIssuance, ValidPermissionedDomain, ValidPermissionedDEX, + ValidBookDirectory, ValidAMM, NoModifiedUnmodifiableFields, ValidPseudoAccounts, diff --git a/include/xrpl/tx/transactors/dex/OfferCreate.h b/include/xrpl/tx/transactors/dex/OfferCreate.h index d1ac8b5626..efae5312e2 100644 --- a/include/xrpl/tx/transactors/dex/OfferCreate.h +++ b/include/xrpl/tx/transactors/dex/OfferCreate.h @@ -85,6 +85,7 @@ private: Keylet const& offerIndex, STAmount const& saTakerPays, STAmount const& saTakerGets, + std::uint64_t openRate, std::function)> const& setDir); }; diff --git a/include/xrpl/tx/transactors/system/LedgerStateFix.h b/include/xrpl/tx/transactors/system/LedgerStateFix.h index 8d704467eb..6fbae6fc6a 100644 --- a/include/xrpl/tx/transactors/system/LedgerStateFix.h +++ b/include/xrpl/tx/transactors/system/LedgerStateFix.h @@ -9,6 +9,7 @@ class LedgerStateFix : public Transactor public: enum class FixType : std::uint16_t { NfTokenPageLink = 1, + BookExchangeRate = 2, }; static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal; diff --git a/src/libxrpl/tx/invariants/DirectoryInvariant.cpp b/src/libxrpl/tx/invariants/DirectoryInvariant.cpp new file mode 100644 index 0000000000..2a4fac07d0 --- /dev/null +++ b/src/libxrpl/tx/invariants/DirectoryInvariant.cpp @@ -0,0 +1,96 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +namespace { + +[[nodiscard]] bool +isRootBookDirectory(SLE const& dir) +{ + // Child page keys do not encode book quality. + return dir.isFieldPresent(sfExchangeRate) || dir.isFieldPresent(sfTakerPaysCurrency) || + dir.isFieldPresent(sfTakerPaysIssuer) || dir.isFieldPresent(sfTakerPaysMPT) || + dir.isFieldPresent(sfTakerGetsCurrency) || dir.isFieldPresent(sfTakerGetsIssuer) || + dir.isFieldPresent(sfTakerGetsMPT) || dir.isFieldPresent(sfDomainID); +} + +[[nodiscard]] bool +badExchangeRate(SLE const& dir) +{ + return isRootBookDirectory(dir) && + (!dir.isFieldPresent(sfExchangeRate) || + dir.getFieldU64(sfExchangeRate) != getQuality(dir.key())); +} + +} // namespace + +void +ValidBookDirectory::visitEntry( + bool, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + // New root directories must have matching exchange-rate metadata. New + // child directories must point to an existing root. + + // Only validate newly-created directories; LedgerStateFix handles legacy + // bad exchange-rate metadata. + if (badBookDirectory_ || before || !after || after->getType() != ltDIR_NODE) + return; + + auto const rootIndex = after->getFieldH256(sfRootIndex); + if (after->key() == rootIndex && !badBookDirectory_) + { + badBookDirectory_ = badBookDirectory_ || badExchangeRate(*after); + return; + } + + rootIndexes_.insert(rootIndex); +} + +bool +ValidBookDirectory::finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + if (!view.rules().enabled(fixCleanup3_2_0)) + return true; + + if (badBookDirectory_) + { + JLOG(j.fatal()) << "Invariant failed: book directory exchange rate " + "does not match directory quality"; + return false; + } + + for (auto const& rootIndex : rootIndexes_) + { + auto const root = view.read(Keylet(ltDIR_NODE, rootIndex)); + if (!root) + { + JLOG(j.fatal()) << "Invariant failed: book directory root missing"; + return false; + } + } + + return true; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp index 69c101644c..ed9eb1255d 100644 --- a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp @@ -547,6 +547,7 @@ OfferCreate::applyHybrid( Keylet const& offerKey, STAmount const& saTakerPays, STAmount const& saTakerGets, + std::uint64_t openRate, std::function)> const& setDir) { if (!sleOffer->isFieldPresent(sfDomainID)) @@ -558,7 +559,7 @@ OfferCreate::applyHybrid( // if offer is hybrid, need to also place into open offer dir Book const book{saTakerPays.asset(), saTakerGets.asset(), std::nullopt}; - auto dir = keylet::quality(keylet::kBook(book), getRate(saTakerGets, saTakerPays)); + auto dir = keylet::quality(keylet::kBook(book), openRate); bool const bookExists = sb.exists(dir); auto const bookNode = sb.dirAppend(dir, offerKey, [&](SLE::ref sle) { @@ -924,8 +925,16 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel) // if it's a hybrid offer, set hybrid flag, and create an open dir if (bHybrid) { + // Pre-fixCleanup3_2_0: the open-book directory quality was computed + // from post-crossing amounts, which may differ from the original rate + // due to rounding in rate preservation. Post-fixCleanup3_2_0: use the + // original placement rate so the open-book directory quality matches + // the domain-book directory. + auto const openRate = ctx_.view().rules().enabled(fixCleanup3_2_0) + ? uRate + : getRate(saTakerGets, saTakerPays); auto const res = - applyHybrid(sb, sleOffer, offerIndex, saTakerPays, saTakerGets, setBookDir); + applyHybrid(sb, sleOffer, offerIndex, saTakerPays, saTakerGets, openRate, setBookDir); if (!isTesSuccess(res)) return {res, true}; // LCOV_EXCL_LINE } diff --git a/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp b/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp index 2f30311e6f..222e5dce7a 100644 --- a/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp +++ b/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp @@ -4,7 +4,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -12,24 +14,72 @@ #include #include +#include +#include #include +#include namespace xrpl { +namespace { + +using FixType = LedgerStateFix::FixType; + +std::array, 2> const kLedgerFixFields = {{ + {FixType::NfTokenPageLink, &sfOwner}, + {FixType::BookExchangeRate, &sfBookDirectory}, +}}; + +[[nodiscard]] SField const* +fixField(FixType const fixType) +{ + auto const iter = std::ranges::find_if( + kLedgerFixFields, [fixType](auto const& entry) { return entry.first == fixType; }); + + if (iter == kLedgerFixFields.end()) + return nullptr; // LCOV_EXCL_LINE + + return iter->second; +} + +[[nodiscard]] bool +hasUnexpectedFixField(STTx const& tx, SField const& expected) +{ + return std::ranges::any_of(kLedgerFixFields, [&tx, &expected](auto const& entry) { + auto const field = entry.second; + return field != &expected && tx.isFieldPresent(*field); + }); +} + +} // namespace + NotTEC LedgerStateFix::preflight(PreflightContext const& ctx) { - switch (static_cast(ctx.tx[sfLedgerFixType])) + auto const fixType = static_cast(ctx.tx[sfLedgerFixType]); + + switch (fixType) { case FixType::NfTokenPageLink: - if (!ctx.tx.isFieldPresent(sfOwner)) - return temINVALID; + break; + + case FixType::BookExchangeRate: + if (!ctx.rules.enabled(fixCleanup3_2_0)) + return temDISABLED; break; default: return tefINVALID_LEDGER_FIX_TYPE; } + auto const expectedField = fixField(fixType); + if (expectedField == nullptr) + return tefINVALID_LEDGER_FIX_TYPE; // LCOV_EXCL_LINE + + // Each fix type allows exactly one fix-specific field. + if (!ctx.tx.isFieldPresent(*expectedField) || hasUnexpectedFixField(ctx.tx, *expectedField)) + return temINVALID; + return tesSUCCESS; } @@ -53,6 +103,24 @@ LedgerStateFix::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } + if (static_cast(ctx.tx[sfLedgerFixType]) == FixType::BookExchangeRate) + { + auto const dirKey = ctx.tx.getFieldH256(sfBookDirectory); + auto const sle = ctx.view.read(Keylet(ltDIR_NODE, dirKey)); + if (!sle) + return tecOBJECT_NOT_FOUND; + + // Must be the first page of a book directory (has sfExchangeRate). + if (!sle->isFieldPresent(sfExchangeRate)) + return tecNO_PERMISSION; + + // ExchangeRate is already correct, nothing to fix. + if (getQuality(sle->key()) == sle->getFieldU64(sfExchangeRate)) + return tecNO_PERMISSION; + + return tesSUCCESS; + } + // preflight is supposed to verify that only valid FixTypes get to preclaim. return tecINTERNAL; // LCOV_EXCL_LINE } @@ -68,6 +136,18 @@ LedgerStateFix::doApply() return tesSUCCESS; } + if (static_cast(ctx_.tx[sfLedgerFixType]) == FixType::BookExchangeRate) + { + auto const dirKey = ctx_.tx.getFieldH256(sfBookDirectory); + auto sle = view().peek(Keylet(ltDIR_NODE, dirKey)); + if (!sle) + return tecINTERNAL; // LCOV_EXCL_LINE + + sle->setFieldU64(sfExchangeRate, getQuality(sle->key())); + view().update(sle); + return tesSUCCESS; + } + // preflight is supposed to verify that only valid FixTypes get to doApply. return tecINTERNAL; // LCOV_EXCL_LINE } diff --git a/src/test/app/FixNFTokenPageLinks_test.cpp b/src/test/app/FixNFTokenPageLinks_test.cpp index f77d924c94..cfe2fd1808 100644 --- a/src/test/app/FixNFTokenPageLinks_test.cpp +++ b/src/test/app/FixNFTokenPageLinks_test.cpp @@ -173,6 +173,13 @@ class FixNFTokenPageLinks_test : public beast::unit_test::Suite tx.removeMember(sfOwner.jsonName); env(tx, Fee(linkFixFee), Ter(temINVALID)); } + { + // NFTokenPageLink fixes require sfOwner and reject fields that + // belong to other LedgerStateFix types. + json::Value tx = ledgerStateFix::nftPageLinks(alice, alice); + tx[sfBookDirectory.jsonName] = to_string(uint256{1}); + env(tx, Fee(linkFixFee), Ter(temINVALID)); + } { // Invalid LedgerFixType codes. json::Value tx = ledgerStateFix::nftPageLinks(alice, alice); diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 21094e0142..5cb872f291 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,7 @@ #include #include #include +#include #include #include @@ -2037,6 +2039,106 @@ class Invariants_test : public beast::unit_test::Suite } } + void + testBookDirectoryExchangeRate() + { + using namespace test::jtx; + testcase << "book directory exchange rate"; + + auto const getBookRootKey = [](Account const& account, std::uint64_t quality) { + Book const book{xrpIssue(), account["USD"], std::nullopt}; + return keylet::quality(keylet::kBook(book), quality); + }; + + // Root book-directory pages carry exchange-rate metadata that must + // match the quality encoded in the directory key. + auto const makeRootPage = [](Keylet const& dir, std::uint64_t exchangeRate) { + auto sleDir = std::make_shared(dir); + sleDir->setFieldH256(sfRootIndex, dir.key); + STVector256 indexes; + indexes.pushBack(uint256{1}); + sleDir->setFieldV256(sfIndexes, indexes); + sleDir->setFieldU64(sfExchangeRate, exchangeRate); + return sleDir; + }; + + // Child pages do not carry quality metadata; they only point back to + // the root directory. + auto const makeChildPage = [](Keylet const& rootDir) { + auto sleDir = std::make_shared(keylet::page(rootDir, 1)); + sleDir->setFieldH256(sfRootIndex, rootDir.key); + STVector256 indexes; + indexes.pushBack(uint256{2}); + sleDir->setFieldV256(sfIndexes, indexes); + return sleDir; + }; + + auto const makeOfferCreateTx = [] { + return STTx{ttOFFER_CREATE, [](STObject& tx) { + Account const account{"A1"}; + tx.setFieldAmount(sfTakerPays, XRP(1)); + tx.setFieldAmount(sfTakerGets, account["USD"](1)); + }}; + }; + std::initializer_list const failTers = {tecINVARIANT_FAILED, tefINVARIANT_FAILED}; + + // Creating a root book directory with mismatched exchange-rate + // metadata violates the invariant. + doInvariantCheck( + {{"book directory exchange rate does not match directory quality"}}, + [&](Account const& a1, Account const&, ApplyContext& ac) { + auto const directoryQuality = STAmount::kURateOne; + auto const dir = getBookRootKey(a1, directoryQuality); + ac.view().insert(makeRootPage(dir, directoryQuality + 1)); + return true; + }, + XRPAmount{}, + makeOfferCreateTx(), + failTers); + + // A new child page must point to an existing root page. + doInvariantCheck( + {{"book directory root missing"}}, + [&](Account const& a1, Account const&, ApplyContext& ac) { + auto const directoryQuality = STAmount::kURateOne; + auto const rootDir = getBookRootKey(a1, directoryQuality); + // Insert only the child page. It points at rootDir, but the + // corresponding root page is intentionally missing. + ac.view().insert(makeChildPage(rootDir)); + return true; + }, + XRPAmount{}, + makeOfferCreateTx(), + failTers); + + // Legacy bad-root tolerance: + // - The view contains a pre-existing root page with bad sfExchangeRate + // metadata. + // - The simulated transaction only creates a child page pointing to + // that root. + // - The invariant must pass because this transaction did not create + // the bad root, only adding a child page. + { + Env env{*this, defaultAmendments()}; + Account const a1{"A1"}; + env.fund(XRP(1000), a1); + env.close(); + + OpenView view{*env.current()}; + auto const directoryQuality = STAmount::kURateOne; + auto const rootDir = getBookRootKey(a1, directoryQuality); + view.rawInsert(makeRootPage(rootDir, directoryQuality + 1)); + + ValidBookDirectory invariant; + invariant.visitEntry(false, nullptr, makeChildPage(rootDir)); + + test::StreamSink sink{beast::Severity::Warning}; + beast::Journal const jlog{sink}; + BEAST_EXPECT( + invariant.finalize(makeOfferCreateTx(), tesSUCCESS, XRPAmount{}, view, jlog)); + } + } + Keylet createLoanBroker(jtx::Account const& a, jtx::Env& env, jtx::PrettyAsset const& asset) { @@ -4489,6 +4591,7 @@ public: testPermissionedDomainInvariants(defaultAmendments() - fixCleanup3_1_3); testPermissionedDEX(defaultAmendments() | fixCleanup3_1_3); testPermissionedDEX(defaultAmendments() - fixCleanup3_1_3); + testBookDirectoryExchangeRate(); testNoModifiedUnmodifiableFields(); testValidPseudoAccounts(); testValidLoanBroker(); diff --git a/src/test/app/PermissionedDEX_test.cpp b/src/test/app/PermissionedDEX_test.cpp index 3a95fb2f92..c6e94d7994 100644 --- a/src/test/app/PermissionedDEX_test.cpp +++ b/src/test/app/PermissionedDEX_test.cpp @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include #include #include // IWYU pragma: keep #include @@ -1457,6 +1459,263 @@ class PermissionedDEX_test : public beast::unit_test::Suite } } + void + testHybridOfferCrossingQuality(FeatureBitset features) + { + bool const fixEnabled = features[fixCleanup3_2_0]; + testcase << "Hybrid offer crossing quality" + << (fixEnabled ? " (fixCleanup3_2_0)" : " (pre-fix)"); + + // Partially-crossed hybrid offer should have consistent quality + // across both book directories. + // + // Steps: + // - Bob places a hybrid offer. + // - Alice places an opposing hybrid offer that partially crosses. + // + // Verify: + // - Domain-book key quality == its sfExchangeRate. + // - Post-fix: open-book key quality == domain-book key quality. + // - Pre-fix: open-book key quality != domain-book key quality + // (key used post-crossing rate, sfExchangeRate used pre-crossing). + + Env env(*this, features); + auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + PermissionedDEX(env); + + // Bob places a hybrid offer: TakerPays = XRP(100), TakerGets = USD(40) + auto const bobOfferSeq{env.seq(bob_)}; + env(offer(bob_, XRP(100), USD(40)), Txflags(tfHybrid), Domain(domainID)); + env.close(); + BEAST_EXPECT(offerExists(env, bob_, bobOfferSeq)); + + // Alice places a hybrid offer in the opposite direction that + // partially crosses Bob's offer. + // Alice: TakerPays = USD(100), TakerGets = XRP(300) (rate = 3 XRP/USD) + // Bob's offer is at a better rate (2.5 XRP/USD) so crossing occurs. + auto const aliceOfferSeq{env.seq(alice_)}; + env(offer(alice_, USD(100), XRP(300)), Txflags(tfHybrid), Domain(domainID)); + env.close(); + + // After crossing, Alice's remaining offer should be placed. + auto const sle = env.le(keylet::offer(alice_.id(), aliceOfferSeq)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->isFieldPresent(sfAdditionalBooks)); + BEAST_EXPECT(sle->getFieldArray(sfAdditionalBooks).size() == 1); + + auto const domainDirKey = sle->getFieldH256(sfBookDirectory); + auto const openDirKey = + sle->getFieldArray(sfAdditionalBooks)[0].getFieldH256(sfBookDirectory); + + auto const domainQuality = getQuality(domainDirKey); + auto const openQuality = getQuality(openDirKey); + + // Read the directory SLEs and check sfExchangeRate vs key quality. + auto const domainDirSle = env.le(Keylet(ltDIR_NODE, domainDirKey)); + auto const openDirSle = env.le(Keylet(ltDIR_NODE, openDirKey)); + BEAST_EXPECT(domainDirSle); + BEAST_EXPECT(openDirSle); + + auto const domainExRate = domainDirSle->getFieldU64(sfExchangeRate); + auto const openExRate = openDirSle->getFieldU64(sfExchangeRate); + auto const preCrossingQuality = std::uint64_t{5623825668291712342ULL}; + auto const postCrossingQuality = std::uint64_t{5623825668291712341ULL}; + + // Domain directory: sfExchangeRate should always match key quality + // (both use the pre-crossing rate). Correct behavior. + BEAST_EXPECT(domainQuality == preCrossingQuality); + BEAST_EXPECT(domainExRate == preCrossingQuality); + BEAST_EXPECT(domainExRate == domainQuality); + + if (fixEnabled) + { + // Correct behavior: both directory keys use the pre-crossing rate. + BEAST_EXPECT(openQuality == preCrossingQuality); + BEAST_EXPECT(domainQuality == openQuality); + + // sfExchangeRate matches key quality on both directories. + BEAST_EXPECT(openExRate == preCrossingQuality); + BEAST_EXPECT(openExRate == openQuality); + } + else + { + // Wrong legacy behavior: the open-book directory key uses the + // post-crossing rate instead of the domain-book rate. + BEAST_EXPECT(openQuality == postCrossingQuality); + BEAST_EXPECT(domainQuality != openQuality); + + // The open-book sfExchangeRate still uses the pre-crossing rate, + // so it no longer matches the actual quality encoded in the + // open-book directory key. + BEAST_EXPECT(openExRate == preCrossingQuality); + BEAST_EXPECT(openExRate != openQuality); + BEAST_EXPECT(openExRate == domainQuality); + } + } + + void + testBookExchangeRateFix(FeatureBitset features) + { + testcase("LedgerStateFix BookExchangeRate"); + + // Use the pre-fix path to create a hybrid offer with a mismatched + // sfExchangeRate, then apply LedgerStateFix to correct it. + // + // Steps: + // - Create a partially-crossed hybrid offer (pre-fixCleanup3_2_0) + // so the open-book directory has wrong sfExchangeRate. + // - Re-enable fixCleanup3_2_0 and submit a LedgerStateFix to + // repair the open-book directory's sfExchangeRate. + // + // Verify: + // - Before fix: sfExchangeRate != getQuality(key). + // - After fix: sfExchangeRate == getQuality(key). + + { + // Amendment gate: BookExchangeRate fixes require fixCleanup3_2_0. + Env env(*this, features - fixCleanup3_2_0); + Account const carol{"carol"}; + + env.fund(XRP(1000), carol); + env.close(); + + env(ledgerStateFix::bookExchangeRate(carol, uint256{1}), Ter(temDISABLED)); + } + + { + // Preflight check: BookExchangeRate fixes only accept their + // required fix-specific field. + Env env(*this, features); + Account const carol{"carol"}; + + env.fund(XRP(1000), carol); + env.close(); + + // BookExchangeRate fixes require sfBookDirectory. + auto missingBookDirectory = ledgerStateFix::bookExchangeRate(carol, uint256{1}); + missingBookDirectory.removeMember(sfBookDirectory.jsonName); + env(missingBookDirectory, Ter(temINVALID)); + + // BookExchangeRate fixes reject fields that belong to other + // LedgerStateFix types. + auto extraOwner = ledgerStateFix::bookExchangeRate(carol, uint256{1}); + extraOwner[sfOwner.jsonName] = carol.human(); + env(extraOwner, Ter(temINVALID)); + } + + { + Env env(*this, features); + auto const setup = PermissionedDEX(env); + auto const fixFee = drops(env.current()->fees().increment); + + { + // Preclaim check: the target directory must exist. + env(ledgerStateFix::bookExchangeRate(setup.carol, uint256{1}), + Fee(fixFee), + Ter(tecOBJECT_NOT_FOUND)); + } + + { + // Preclaim check: the target directory must be a book root + // page. Owner directories are ltDIR_NODE entries, but they do + // not carry sfExchangeRate. + auto const ownerDir = keylet::ownerDir(setup.bob.id()); + auto const ownerDirSle = env.le(ownerDir); + BEAST_EXPECT(ownerDirSle); + BEAST_EXPECT(!ownerDirSle->isFieldPresent(sfExchangeRate)); + + env(ledgerStateFix::bookExchangeRate(setup.carol, ownerDir.key), + Fee(fixFee), + Ter(tecNO_PERMISSION)); + } + + { + // Preclaim check: a correct sfExchangeRate leaves nothing to + // repair. + auto const bobOfferSeq{env.seq(setup.bob)}; + env(offer(setup.bob, XRP(100), setup.usd(40))); + env.close(); + + auto const sle = env.le(keylet::offer(setup.bob.id(), bobOfferSeq)); + BEAST_EXPECT(sle); + + auto const dirKey = sle->getFieldH256(sfBookDirectory); + { + auto const dirSle = env.le(Keylet(ltDIR_NODE, dirKey)); + BEAST_EXPECT(dirSle); + auto const exchangeRate = dirSle->getFieldU64(sfExchangeRate); + auto const quality = getQuality(dirKey); + BEAST_EXPECT(exchangeRate == quality); + } + + env(ledgerStateFix::bookExchangeRate(setup.carol, dirKey), + Fee(fixFee), + Ter(tecNO_PERMISSION)); + } + } + + { + // Repair path: start without fixCleanup3_2_0 to produce the + // mismatch, then enable the amendment and fix it. + Env env(*this, features - fixCleanup3_2_0); + auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + PermissionedDEX(env); + + // Bob places a hybrid offer. + env(offer(bob_, XRP(100), USD(40)), Txflags(tfHybrid), Domain(domainID)); + env.close(); + + // Alice partially crosses Bob. + auto const aliceOfferSeq{env.seq(alice_)}; + env(offer(alice_, USD(100), XRP(300)), Txflags(tfHybrid), Domain(domainID)); + env.close(); + + auto const sle = env.le(keylet::offer(alice_.id(), aliceOfferSeq)); + BEAST_EXPECT(sle); + + auto const openDirKey = + sle->getFieldArray(sfAdditionalBooks)[0].getFieldH256(sfBookDirectory); + + auto const preCrossingQuality = std::uint64_t{5623825668291712342ULL}; + auto const postCrossingQuality = std::uint64_t{5623825668291712341ULL}; + + // Confirm mismatch exists. + { + auto const dirSle = env.le(Keylet(ltDIR_NODE, openDirKey)); + BEAST_EXPECT(dirSle); + auto const exchangeRate = dirSle->getFieldU64(sfExchangeRate); + auto const quality = getQuality(openDirKey); + BEAST_EXPECT(exchangeRate == preCrossingQuality); + BEAST_EXPECT(quality == postCrossingQuality); + BEAST_EXPECT(exchangeRate != quality); + } + + // Enable fixCleanup3_2_0 and apply the LedgerStateFix. + env.enableFeature(fixCleanup3_2_0); + env.close(); + + auto const fixFee = drops(env.current()->fees().increment); + env(ledgerStateFix::bookExchangeRate(carol_, openDirKey), Fee(fixFee)); + env.close(); + + // Confirm sfExchangeRate now matches the key quality. + { + auto const dirSle = env.le(Keylet(ltDIR_NODE, openDirKey)); + BEAST_EXPECT(dirSle); + auto const exchangeRate = dirSle->getFieldU64(sfExchangeRate); + auto const quality = getQuality(openDirKey); + BEAST_EXPECT(exchangeRate == postCrossingQuality); + BEAST_EXPECT(quality == postCrossingQuality); + BEAST_EXPECT(exchangeRate == quality); + } + + // Submitting again should fail — nothing to fix. + env(ledgerStateFix::bookExchangeRate(carol_, openDirKey), + Fee(fixFee), + Ter(tecNO_PERMISSION)); + } + } + void testCancelRegularOfferWithDomainCreate(FeatureBitset features) { @@ -1528,6 +1787,9 @@ public: testHybridOfferDirectories(all); testHybridMalformedOffer(all); testHybridMalformedOffer(all - fixCleanup3_1_3); + testHybridOfferCrossingQuality(all); + testHybridOfferCrossingQuality(all - fixCleanup3_2_0); + testBookExchangeRateFix(all); // Cancelling a regular offer in a domain OfferCreate is allowed // only after fixCleanup3_2_0. diff --git a/src/test/jtx/impl/ledgerStateFixes.cpp b/src/test/jtx/impl/ledgerStateFixes.cpp index a4b6d9b986..30c6659124 100644 --- a/src/test/jtx/impl/ledgerStateFixes.cpp +++ b/src/test/jtx/impl/ledgerStateFixes.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -22,4 +23,16 @@ nftPageLinks(jtx::Account const& acct, jtx::Account const& owner) return jv; } +// Fix sfExchangeRate on a book directory. acct pays fee. +json::Value +bookExchangeRate(jtx::Account const& acct, uint256 const& bookDir) +{ + json::Value jv; + jv[sfAccount.jsonName] = acct.human(); + jv[sfLedgerFixType.jsonName] = static_cast(LedgerStateFix::FixType::BookExchangeRate); + jv[sfBookDirectory.jsonName] = to_string(bookDir); + jv[sfTransactionType.jsonName] = jss::LedgerStateFix; + return jv; +} + } // namespace xrpl::test::jtx::ledgerStateFix diff --git a/src/test/jtx/ledgerStateFix.h b/src/test/jtx/ledgerStateFix.h index 769a28fb08..71f3f76101 100644 --- a/src/test/jtx/ledgerStateFix.h +++ b/src/test/jtx/ledgerStateFix.h @@ -10,4 +10,8 @@ namespace xrpl::test::jtx::ledgerStateFix { json::Value nftPageLinks(jtx::Account const& acct, jtx::Account const& owner); +/** Repair sfExchangeRate on a book directory's first page. */ +json::Value +bookExchangeRate(jtx::Account const& acct, uint256 const& bookDir); + } // namespace xrpl::test::jtx::ledgerStateFix diff --git a/src/tests/libxrpl/protocol_autogen/transactions/LedgerStateFixTests.cpp b/src/tests/libxrpl/protocol_autogen/transactions/LedgerStateFixTests.cpp index 60034255c7..5b78f0cfdd 100644 --- a/src/tests/libxrpl/protocol_autogen/transactions/LedgerStateFixTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/transactions/LedgerStateFixTests.cpp @@ -31,6 +31,7 @@ TEST(TransactionsLedgerStateFixTests, BuilderSettersRoundTrip) // Transaction-specific field values auto const ledgerFixTypeValue = canonical_UINT16(); auto const ownerValue = canonical_ACCOUNT(); + auto const bookDirectoryValue = canonical_UINT256(); LedgerStateFixBuilder builder{ accountValue, @@ -41,6 +42,7 @@ TEST(TransactionsLedgerStateFixTests, BuilderSettersRoundTrip) // Set optional fields builder.setOwner(ownerValue); + builder.setBookDirectory(bookDirectoryValue); auto tx = builder.build(publicKey, secretKey); @@ -72,6 +74,14 @@ TEST(TransactionsLedgerStateFixTests, BuilderSettersRoundTrip) EXPECT_TRUE(tx.hasOwner()); } + { + auto const& expected = bookDirectoryValue; + auto const actualOpt = tx.getBookDirectory(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfBookDirectory should be present"; + expectEqualField(expected, *actualOpt, "sfBookDirectory"); + EXPECT_TRUE(tx.hasBookDirectory()); + } + } // 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper, @@ -90,6 +100,7 @@ TEST(TransactionsLedgerStateFixTests, BuilderFromStTxRoundTrip) // Transaction-specific field values auto const ledgerFixTypeValue = canonical_UINT16(); auto const ownerValue = canonical_ACCOUNT(); + auto const bookDirectoryValue = canonical_UINT256(); // Build an initial transaction LedgerStateFixBuilder initialBuilder{ @@ -100,6 +111,7 @@ TEST(TransactionsLedgerStateFixTests, BuilderFromStTxRoundTrip) }; initialBuilder.setOwner(ownerValue); + initialBuilder.setBookDirectory(bookDirectoryValue); auto initialTx = initialBuilder.build(publicKey, secretKey); @@ -131,6 +143,13 @@ TEST(TransactionsLedgerStateFixTests, BuilderFromStTxRoundTrip) expectEqualField(expected, *actualOpt, "sfOwner"); } + { + auto const& expected = bookDirectoryValue; + auto const actualOpt = rebuiltTx.getBookDirectory(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfBookDirectory should be present"; + expectEqualField(expected, *actualOpt, "sfBookDirectory"); + } + } // 3) Verify wrapper throws when constructed from wrong transaction type. @@ -190,6 +209,8 @@ TEST(TransactionsLedgerStateFixTests, OptionalFieldsReturnNullopt) // Verify optional fields are not present EXPECT_FALSE(tx.hasOwner()); EXPECT_FALSE(tx.getOwner().has_value()); + EXPECT_FALSE(tx.hasBookDirectory()); + EXPECT_FALSE(tx.getBookDirectory().has_value()); } } From afcf6fbcdcdaaccb3dfa66b7c0a0c3d570bed24a Mon Sep 17 00:00:00 2001 From: Rithvik Reddygari <67663680+ricky122-5@users.noreply.github.com> Date: Thu, 21 May 2026 02:33:19 -0400 Subject: [PATCH 008/135] docs: Add --parallel flag to cmake build commands in BUILD.md (#7302) --- BUILD.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/BUILD.md b/BUILD.md index 632832c002..a1163dbfcc 100644 --- a/BUILD.md +++ b/BUILD.md @@ -427,16 +427,19 @@ install ccache --version 4.11.3 --allow-downgrade`. Single-config generators: ``` - cmake --build . + cmake --build . --parallel N ``` Multi-config generators: ``` - cmake --build . --config Release - cmake --build . --config Debug + cmake --build . --config Release --parallel N + cmake --build . --config Debug --parallel N ``` + Replace the `--parallel` parameter N with the desired number of parallel jobs. A common starting point is half of the number of available CPU + cores. + 5. Test xrpld. Single-config generators: From f6fd5ddb0a08120f7d56c4e56ac603111ee85ba9 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Thu, 21 May 2026 14:24:04 +0100 Subject: [PATCH 009/135] fix: Add null check (#7305) Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 --- src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp b/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp index e6c7a160ad..ae551de1ab 100644 --- a/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp +++ b/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp @@ -51,7 +51,6 @@ iteratePriceData( std::shared_ptr const& sle, std::function const& f) { - using Meta = std::shared_ptr; static constexpr std::uint8_t kMaxHistory = 3; bool isNew = false; std::uint8_t history = 0; @@ -73,7 +72,7 @@ iteratePriceData( // for the Oracle is not found in the inner loop STObject const* prevChain = nullptr; - Meta meta = nullptr; + std::shared_ptr meta = nullptr; while (true) { if (prevChain == chain) @@ -93,6 +92,8 @@ iteratePriceData( return; // LCOV_EXCL_LINE meta = ledger->txRead(prevTx).second; + if (!meta) + return; prevChain = chain; for (STObject const& node : meta->getFieldArray(sfAffectedNodes)) From 795dc5e3643b4d66314f1240dc6728cfa71f11c3 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Thu, 21 May 2026 16:46:26 +0200 Subject: [PATCH 010/135] fix: Avoid principal-zeroing in non-final loan payments at coarse scale (#7050) Co-authored-by: Ed Hennis --- src/libxrpl/ledger/helpers/LendingHelpers.cpp | 17 +- src/test/app/Loan_test.cpp | 388 ++++++++++++------ 2 files changed, 278 insertions(+), 127 deletions(-) diff --git a/src/libxrpl/ledger/helpers/LendingHelpers.cpp b/src/libxrpl/ledger/helpers/LendingHelpers.cpp index 26e320c15d..2c756c2877 100644 --- a/src/libxrpl/ledger/helpers/LendingHelpers.cpp +++ b/src/libxrpl/ledger/helpers/LendingHelpers.cpp @@ -1058,11 +1058,22 @@ computePaymentComponents( rules, periodicPayment, periodicRate, paymentRemaining - 1, managementFeeRate); // Round the target to the loan's scale to match how actual loan values - // are stored. + // are stored. With fixCleanup3_2_0 enabled, principal is rounded upward + // and interest downward so that at coarse scale principal sticks at the + // floor (until the final payment clears it) while interest absorbs each + // periodic payment. Without the amendment the pre-existing round-to- + // nearest behavior is preserved (which can hit the "Partial principal + // payment" assertion on degenerate integer-scale loans). + bool const fixCleanup320Enabled = rules.enabled(fixCleanup3_2_0); + Number::RoundingMode const principalRounding = + fixCleanup320Enabled ? Number::RoundingMode::Upward : Number::getround(); + Number::RoundingMode const interestRounding = + fixCleanup320Enabled ? Number::RoundingMode::Downward : Number::getround(); LoanState const roundedTarget = LoanState{ .valueOutstanding = roundToAsset(asset, trueTarget.valueOutstanding, scale), - .principalOutstanding = roundToAsset(asset, trueTarget.principalOutstanding, scale), - .interestDue = roundToAsset(asset, trueTarget.interestDue, scale), + .principalOutstanding = + roundToAsset(asset, trueTarget.principalOutstanding, scale, principalRounding), + .interestDue = roundToAsset(asset, trueTarget.interestDue, scale, interestRounding), .managementFeeDue = roundToAsset(asset, trueTarget.managementFeeDue, scale)}; // Get the current actual loan state from the ledger values diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index 201bb6942a..b5687265ab 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -69,6 +69,7 @@ #include #include #include +#include #include #include #include @@ -90,8 +91,25 @@ class Loan_test : public beast::unit_test::Suite protected: // Ensure that all the features needed for Lending Protocol are included, // even if they are set to unsupported. + FeatureBitset const all_{jtx::testableAmendments()}; + // All 2^N permutations of `all_` with each subset of the given features + // excluded. The first entry is always `all_` itself (empty exclusion); + // the last excludes every feature in the list. + std::vector + amendmentCombinations(std::initializer_list features) const + { + std::vector result{all_}; + for (auto const& f : features) + { + auto const n = result.size(); + for (std::size_t i = 0; i < n; ++i) + result.push_back(result[i] - f); + } + return result; + } + std::string const iouCurrency_{"IOU"}; void @@ -1201,7 +1219,8 @@ protected: runLoan( AssetType assetType, BrokerParameters const& brokerParams, - LoanParameters const& loanParams) + LoanParameters const& loanParams, + FeatureBitset features) { using namespace jtx; @@ -1209,7 +1228,7 @@ protected: Account const lender("lender"); Account const borrower("borrower"); - Env env(*this, all_); + Env env(*this, features); auto loanResult = createLoan(env, assetType, brokerParams, loanParams, issuer, lender, borrower); @@ -2896,7 +2915,7 @@ protected: } void - testLoanSet() + testLoanSet(FeatureBitset features) { using namespace jtx; @@ -2915,7 +2934,7 @@ protected: std::function mptTest, std::function iouTest, CaseArgs args = {}) { - Env env(*this, all_); + Env env(*this, features); env.fund(XRP(args.initialXRP), issuer, lender, borrower); env.close(); if (args.requireAuth) @@ -3453,14 +3472,14 @@ protected: } void - testLifecycle() + testLifecycle(FeatureBitset features) { testcase("Lifecycle"); using namespace jtx; // Create 3 loan brokers: one for XRP, one for an IOU, and one for // an MPT. That'll require three corresponding SAVs. - Env env(*this, all_); + Env env(*this, features); Account const issuer{"issuer"}; // For simplicity, lender will be the sole actor for the vault & @@ -3547,7 +3566,7 @@ protected: } void - testSelfLoan() + testSelfLoan(FeatureBitset features) { testcase << "Self Loan"; @@ -3555,7 +3574,7 @@ protected: using namespace std::chrono_literals; // Create 3 loan brokers: one for XRP, one for an IOU, and one for // an MPT. That'll require three corresponding SAVs. - Env env(*this, all_); + Env env(*this, features); Account const issuer{"issuer"}; // For simplicity, lender will be the sole actor for the vault & @@ -3682,7 +3701,7 @@ protected: } void - testBatchBypassCounterparty() + testBatchBypassCounterparty(FeatureBitset features) { // From FIND-001 testcase << "Batch Bypass Counterparty"; @@ -3693,7 +3712,7 @@ protected: using namespace jtx; using namespace std::chrono_literals; - Env env(*this, all_); + Env env(*this, features); Account const lender{"lender"}; Account const borrower{"borrower"}; @@ -3749,14 +3768,14 @@ protected: } void - testWrongMaxDebtBehavior() + testWrongMaxDebtBehavior(FeatureBitset features) { // From FIND-003 testcase << "Wrong Max Debt Behavior"; using namespace jtx; using namespace std::chrono_literals; - Env env(*this, all_); + Env env(*this, features); Account const issuer{"issuer"}; Account const lender{"lender"}; @@ -3795,7 +3814,7 @@ protected: } void - testLoanPayComputePeriodicPaymentValidRateInvariant() + testLoanPayComputePeriodicPaymentValidRateInvariant(FeatureBitset features) { // From FIND-012 testcase << "LoanPay xrpl::detail::computePeriodicPayment : " @@ -3803,7 +3822,7 @@ protected: using namespace jtx; using namespace std::chrono_literals; - Env env(*this, all_); + Env env(*this, features); Account const issuer{"issuer"}; Account const lender{"lender"}; @@ -3863,7 +3882,7 @@ protected: } void - testRPC() + testRPC(FeatureBitset features) { // This will expand as more test cases are added. Some functionality // is tested in other test functions. @@ -3871,7 +3890,7 @@ protected: using namespace jtx; - Env env(*this, all_); + Env env(*this, features); auto lowerFee = [&]() { // Run the local fee back down. @@ -4542,7 +4561,7 @@ protected: } void - testAccountSendMptMinAmountInvariant() + testAccountSendMptMinAmountInvariant(FeatureBitset features) { // (From FIND-006) testcase << "LoanSet trigger xrpl::accountSendMPT : minimum amount " @@ -4550,7 +4569,7 @@ protected: using namespace jtx; using namespace std::chrono_literals; - Env env(*this, all_); + Env env(*this, features); Account const issuer{"issuer"}; Account const lender{"lender"}; @@ -4602,7 +4621,7 @@ protected: } void - testLoanPayDebtDecreaseInvariant() + testLoanPayDebtDecreaseInvariant(FeatureBitset features) { // From FIND-007 testcase << "LoanPay xrpl::LoanPay::doApply : debtDecrease " @@ -4611,7 +4630,7 @@ protected: using namespace jtx; using namespace std::chrono_literals; using namespace Lending; - Env env(*this, all_); + Env env(*this, features); Account const issuer{"issuer"}; Account const lender{"lender"}; @@ -4695,14 +4714,14 @@ protected: } void - testLoanPayComputePeriodicPaymentValidTotalInterestInvariant() + testLoanPayComputePeriodicPaymentValidTotalInterestInvariant(FeatureBitset features) { // From FIND-010 testcase << "xrpl::loanComputePaymentParts : valid total interest"; using namespace jtx; using namespace std::chrono_literals; - Env env(*this, all_); + Env env(*this, features); Account const issuer{"issuer"}; Account const lender{"lender"}; @@ -4902,7 +4921,7 @@ protected: } void - testLoanPayComputePeriodicPaymentValidTotalPrincipalPaidInvariant() + testLoanPayComputePeriodicPaymentValidTotalPrincipalPaidInvariant(FeatureBitset features) { // From FIND-009 testcase << "xrpl::loanComputePaymentParts : totalPrincipalPaid " @@ -4911,7 +4930,7 @@ protected: using namespace jtx; using namespace std::chrono_literals; using namespace Lending; - Env env(*this, all_); + Env env(*this, features); Account const issuer{"issuer"}; Account const lender{"lender"}; @@ -5004,7 +5023,7 @@ protected: } void - testLoanPayComputePeriodicPaymentValidTotalInterestPaidInvariant() + testLoanPayComputePeriodicPaymentValidTotalInterestPaidInvariant(FeatureBitset features) { // From FIND-008 testcase << "xrpl::loanComputePaymentParts : loanValueChange rounded"; @@ -5012,7 +5031,7 @@ protected: using namespace jtx; using namespace std::chrono_literals; using namespace Lending; - Env env(*this, all_); + Env env(*this, features); Account const issuer{"issuer"}; Account const lender{"lender"}; @@ -5089,7 +5108,7 @@ protected: } void - testLoanNextPaymentDueDateOverflow() + testLoanNextPaymentDueDateOverflow(FeatureBitset features) { // For FIND-013 testcase << "Prevent nextPaymentDueDate overflow"; @@ -5097,7 +5116,7 @@ protected: using namespace jtx; using namespace std::chrono_literals; using namespace Lending; - Env env(*this, all_); + Env env{*this, features}; Account const issuer{"issuer"}; Account const lender{"lender"}; @@ -5621,14 +5640,14 @@ protected: #if LOAN_TODO void - testLoanPayLateFullPaymentBypassesPenalties() + testLoanPayLateFullPaymentBypassesPenalties(FeatureBitset features) { testcase("LoanPay full payment skips late penalties"); using namespace jtx; using namespace loan; using namespace std::chrono_literals; - Env env(*this, all); + Env env(*this, features); Account const issuer{"issuer"}; Account const lender{"lender"}; @@ -5774,7 +5793,7 @@ protected: } void - testLoanCoverMinimumRoundingExploit() + testLoanCoverMinimumRoundingExploit(FeatureBitset features) { auto testLoanCoverMinimumRoundingExploit = [&, this](Number const& principalRequest) { testcase << "LoanBrokerCoverClawback drains cover via rounding" @@ -5784,7 +5803,7 @@ protected: using namespace loan; using namespace loanBroker; - Env env(*this, all); + Env env(*this, features); Account const issuer{"issuer"}; Account const lender{"lender"}; @@ -5860,7 +5879,7 @@ protected: #endif void - testPoCUnsignedUnderflowOnFullPayAfterEarlyPeriodic() + testPoCUnsignedUnderflowOnFullPayAfterEarlyPeriodic(FeatureBitset features) { // --- PoC Summary ---------------------------------------------------- // Scenario: Borrower makes one periodic payment early (before next due) @@ -5882,7 +5901,7 @@ protected: using namespace loan; using namespace std::chrono_literals; - Env env(*this, all_); + Env env{*this, features}; Account const lender{"poc_lender4"}; Account const borrower{"poc_borrower4"}; @@ -6085,13 +6104,13 @@ protected: } void - testDustManipulation() + testDustManipulation(FeatureBitset features) { testcase("Dust manipulation"); using namespace jtx; using namespace std::chrono_literals; - Env env(*this, all_); + Env env{*this, features}; // Setup: Create accounts Account const issuer{"issuer"}; @@ -6225,7 +6244,7 @@ protected: } void - testRIPD3831() + testRIPD3831(FeatureBitset features) { using namespace jtx; @@ -6252,7 +6271,7 @@ protected: auto const assetType = AssetType::XRP; - Env env(*this, all_); + Env env{*this, features}; auto loanResult = createLoan(env, assetType, brokerParams, loanParams, issuer, lender, borrower); @@ -6298,7 +6317,7 @@ protected: } void - testRIPD3459() + testRIPD3459(FeatureBitset features) { testcase("RIPD-3459 - LoanBroker incorrect debt total"); @@ -6323,7 +6342,7 @@ protected: auto const assetType = AssetType::MPT; - Env env(*this, all_); + Env env{*this, features}; auto loanResult = createLoan(env, assetType, brokerParams, loanParams, issuer, lender, borrower); @@ -6423,14 +6442,14 @@ protected: } void - testRoundingAllowsUndercoverage() + testRoundingAllowsUndercoverage(FeatureBitset features) { testcase("Minimum cover rounding allows undercoverage (XRP)"); using namespace jtx; using namespace loanBroker; - Env env(*this, all_); + Env env{*this, features}; Account const lender{"lender"}; Account const borrower{"borrower"}; @@ -6502,7 +6521,7 @@ protected: } void - testRIPD3902() + testRIPD3902(FeatureBitset features) { testcase("RIPD-3902 - 1 IOU loan payments"); @@ -6529,7 +6548,7 @@ protected: auto const assetType = AssetType::IOU; - Env env(*this, all_); + Env env{*this, features}; auto loanResult = createLoan(env, assetType, brokerParams, loanParams, issuer, lender, borrower); @@ -6673,7 +6692,7 @@ protected: } void - testIssuerIsBorrower() + testIssuerIsBorrower(FeatureBitset features) { testcase("RIPD-4096 - Issuer as borrower"); @@ -6693,7 +6712,7 @@ protected: auto const assetType = AssetType::IOU; - Env env(*this, all_); + Env env{*this, features}; auto loanResult = createLoan(env, assetType, brokerParams, loanParams, issuer, lender, issuer); @@ -6790,14 +6809,14 @@ protected: } void - testOverpaymentManagementFee() + testOverpaymentManagementFee(FeatureBitset features) { testcase("testOverpaymentManagementFee"); using namespace jtx; using namespace loan; - Env env(*this, all_); + Env env{*this, features}; Account const lender{"lender"}, borrower{"borrower"}; @@ -6843,7 +6862,7 @@ protected: } void - testLoanPayBrokerOwnerMissingTrustline() + testLoanPayBrokerOwnerMissingTrustline(FeatureBitset features) { testcase << "LoanPay Broker Owner Missing Trustline (PoC)"; using namespace jtx; @@ -6852,7 +6871,7 @@ protected: Account const borrower("borrower"); Account const broker("broker"); auto const iou = issuer["IOU"]; - Env env(*this, all_); + Env env(*this, features); env.fund(XRP(20'000), issuer, broker, borrower); env.close(); // Set up trustlines and fund accounts @@ -6911,7 +6930,7 @@ protected: } void - testLoanPayBrokerOwnerUnauthorizedMPT() + testLoanPayBrokerOwnerUnauthorizedMPT(FeatureBitset features) { testcase << "LoanPay Broker Owner MPT unauthorized"; using namespace jtx; @@ -6921,7 +6940,7 @@ protected: Account const borrower("borrower"); Account const broker("broker"); - Env env(*this, all_); + Env env{*this, features}; env.fund(XRP(20'000), issuer, broker, borrower); env.close(); @@ -6992,7 +7011,7 @@ protected: } void - testLoanPayBrokerOwnerNoPermissionedDomainMPT() + testLoanPayBrokerOwnerNoPermissionedDomainMPT(FeatureBitset features) { testcase << "LoanPay Broker Owner without permissioned domain of the MPT"; using namespace jtx; @@ -7002,7 +7021,7 @@ protected: Account const borrower("borrower"); Account const broker("broker"); - Env env(*this, all_); + Env env{*this, features}; env.fund(XRP(20'000), issuer, broker, borrower); env.close(); @@ -7095,7 +7114,7 @@ protected: } void - testLoanSetBrokerOwnerNoPermissionedDomainMPT() + testLoanSetBrokerOwnerNoPermissionedDomainMPT(FeatureBitset features) { testcase << "LoanSet Broker Owner without permissioned domain of the MPT"; using namespace jtx; @@ -7105,7 +7124,7 @@ protected: Account const borrower("borrower"); Account const broker("broker"); - Env env(*this, all_); + Env env{*this, features}; env.fund(XRP(20'000), issuer, broker, borrower); env.close(); @@ -7167,7 +7186,7 @@ protected: } void - testSequentialFLCDepletion() + testSequentialFLCDepletion(FeatureBitset features) { testcase << "First-Loss Capital Depletion on Sequential Defaults"; @@ -7175,7 +7194,7 @@ protected: using namespace loan; using namespace loanBroker; - Env env(*this, all_); + Env env{*this, features}; Account const issuer{"issuer"}; Account const lender{"lender"}; @@ -7427,7 +7446,7 @@ protected: // loss from an impaired loan, ensuring the invariant check properly // accounts for the loss. void - testWithdrawReflectsUnrealizedLoss() + testWithdrawReflectsUnrealizedLoss(FeatureBitset features) { using namespace jtx; using namespace loan; @@ -7446,7 +7465,7 @@ protected: static constexpr std::uint32_t kLocalPaymentInterval = 600; static constexpr std::uint32_t kLocalPaymentTotal = 2; - Env env(*this, all_); + Env env{*this, features}; // Setup accounts Account const issuer{"issuer"}; @@ -7553,6 +7572,110 @@ protected: attemptWithdrawShares(depositorB, sharesLpB, tesSUCCESS); } + // Regression for the dual-rounding fix at coarse (integer-MPT) scale. + // + // Loan: P=1, r=50% (50000 tenth-bips), n=3, yearly interval. The + // amortization schedule produces a fractional principal + // (~0.47) which under round-to-nearest collapses to 0 in a single + // step, causing `doPayment`'s strict `>` assertion on principal to + // fire mid-loan. With fixCleanup3_2_0 enabled, principal is rounded + // upward (sticks at 1 across the first two periods) and only clears + // in the final payment. + // + // The test pays one period at a time across three LoanPay + // transactions and verifies the loan completes (paymentRemaining=0) + // with totals matching the loan's economics (1 principal + 2 interest). + void + testIntegerScalePrincipalSticks(FeatureBitset features) + { + // Without fixCleanup3_2_0, this behavior will abort the server, so + // don't run without it. + if (!features[fixCleanup3_2_0]) + return; + + testcase("edge: integer MPT principal stuck mid-loan completes via final"); + + using namespace jtx; + Env env(*this, features); + + Account const issuer{"issuer"}; + Account const lender{"lender"}; + Account const borrower{"borrower"}; + + env.fund(XRP(100'000), issuer, lender, borrower); + env.close(); + + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create({.maxAmt = 100'000, .flags = tfMPTCanTransfer}); + PrettyAsset const asset{mptt.issuanceID()}; + + mptt.authorize({.account = lender}); + mptt.authorize({.account = borrower}); + + env(pay(issuer, lender, asset(10'000))); + env(pay(issuer, borrower, asset(10'000))); + env.close(); + + Vault const vault{env}; + auto [vaultTx, vaultKeylet] = vault.create({.owner = lender, .asset = asset}); + env(vaultTx); + env.close(); + + env(vault.deposit({.depositor = lender, .id = vaultKeylet.key, .amount = asset(5'000)})); + env.close(); + + auto const brokerKeylet = keylet::loanbroker(lender.id(), env.seq(lender)); + env(loanBroker::set(lender, vaultKeylet.key), + loanBroker::kDebtMaximum(Number{100}), + Fee(env.current()->fees().base * 2)); + env.close(); + + auto const brokerStateBefore = env.le(brokerKeylet); + if (!BEAST_EXPECT(brokerStateBefore)) + return; + auto const loanSequence = brokerStateBefore->at(sfLoanSequence); + auto const loanKeylet = keylet::loan(brokerKeylet.key, loanSequence); + + env(loan::set(borrower, brokerKeylet.key, Number{1}), + Sig(sfCounterpartySignature, lender), + loan::kInterestRate(TenthBips32{50'000}), + loan::kPaymentTotal(3), + loan::kPaymentInterval(31'536'000), + Fee(env.current()->fees().base * 2)); + env.close(); + + auto const borrowerStart = env.balance(borrower, asset).value(); + + // Three separate periodic payments of 1 each. Expected per-period + // evolution at integer MPT scale (TVO = PO + interestDue + + // managementFeeDue): + // start: PO=1, TVO=3, paymentRemaining=3 + // after pay #1: PO=1, TVO=2, paymentRemaining=2 (principal sticks) + // after pay #2: PO=1, TVO=1, paymentRemaining=1 (principal sticks) + // after pay #3: PO=0, TVO=0, paymentRemaining=0 (final clears) + std::array const expectedPO{Number{1}, Number{1}, Number{0}}; + std::array const expectedTVO{Number{2}, Number{1}, Number{0}}; + std::array const expectedRemaining{2, 1, 0}; + + for (int i = 0; i < 3; ++i) + { + env(loan::pay(borrower, loanKeylet.key, asset(1)), Ter(tesSUCCESS)); + env.close(); + + auto const sle = env.le(loanKeylet); + if (!BEAST_EXPECT(sle)) + return; + BEAST_EXPECT(sle->at(sfPrincipalOutstanding) == expectedPO[i]); + BEAST_EXPECT(sle->at(sfTotalValueOutstanding) == expectedTVO[i]); + BEAST_EXPECT(sle->at(sfPaymentRemaining) == expectedRemaining[i]); + } + + // Borrower paid 3 total regardless of fee split (1 principal + 2 + // interest+fee, matching loan economics). + auto const borrowerEnd = env.balance(borrower, asset).value(); + BEAST_EXPECT(borrowerStart - borrowerEnd == asset(3).value()); + } + // A near-zero interest rate on a 100 USD loan // produces total interest of ~6 units at loanScale -9. Numerical error // in the amortization formula pushes the theoretical principal above @@ -7735,74 +7858,91 @@ protected: to_string(tolerance)); } + void + runAmendmentIndependent() + { + testDisabled(); + testInvalidLoanSet(); + testInvalidLoanDelete(); + testInvalidLoanManage(); + testInvalidLoanPay(); + testIssuerLoan(); + testServiceFeeOnBrokerDeepFreeze(); + testRequireAuth(); + testRIPD3901(); + testBorrowerIsBroker(); + testLimitExceeded(); + + for (auto const flags : {0u, tfLoanOverpayment}) + testYieldTheftRounding(flags); + testBugInterestDueDeltaCrash(); + testFullLifecycleVaultPnLNearZeroRate(); + } + + // Tests run under each entry in amendmentCombinations(). + void + runAmendmentSensitive(FeatureBitset features) + { +#if LOAN_TODO + testLoanPayLateFullPaymentBypassesPenalties(features); + testLoanCoverMinimumRoundingExploit(features); +#endif + + // Lifecycle + testSelfLoan(features); + testLoanSet(features); + testLifecycle(features); + + // Payment paths + testWithdrawReflectsUnrealizedLoss(features); + testPoCUnsignedUnderflowOnFullPayAfterEarlyPeriodic(features); + testBatchBypassCounterparty(features); + testLoanNextPaymentDueDateOverflow(features); + testCoverDepositWithdrawNonTransferableMPT(features); + testSequentialFLCDepletion(features); + + // Invariants + testLoanPayComputePeriodicPaymentValidRateInvariant(features); + testAccountSendMptMinAmountInvariant(features); + testLoanPayDebtDecreaseInvariant(features); + testWrongMaxDebtBehavior(features); + testLoanPayComputePeriodicPaymentValidTotalInterestInvariant(features); + testLoanPayComputePeriodicPaymentValidTotalPrincipalPaidInvariant(features); + testLoanPayComputePeriodicPaymentValidTotalInterestPaidInvariant(features); + + // RPC + testRPC(features); + + // Edge / rounding + testDustManipulation(features); + testRoundingAllowsUndercoverage(features); + testOverpaymentManagementFee(features); + testIssuerIsBorrower(features); + testIntegerScalePrincipalSticks(features); + + // RIPD regressions + testRIPD3831(features); + testRIPD3459(features); + testRIPD3902(features); + + // Broker-owner permissions + testLoanPayBrokerOwnerMissingTrustline(features); + testLoanPayBrokerOwnerUnauthorizedMPT(features); + testLoanPayBrokerOwnerNoPermissionedDomainMPT(features); + testLoanSetBrokerOwnerNoPermissionedDomainMPT(features); + } + public: void run() override { -#if LOAN_TODO - testLoanPayLateFullPaymentBypassesPenalties(); - testLoanCoverMinimumRoundingExploit(); -#endif - for (auto const flags : {0u, tfLoanOverpayment}) - { - testYieldTheftRounding(flags); - } - - testBugInterestDueDeltaCrash(); - testFullLifecycleVaultPnLNearZeroRate(); - - testWithdrawReflectsUnrealizedLoss(); - testInvalidLoanSet(); - - auto const all = jtx::testableAmendments(); - testCoverDepositWithdrawNonTransferableMPT(all); - testCoverDepositWithdrawNonTransferableMPT(all - featureMPTokensV2); - testCoverDepositWithdrawNonTransferableMPT(all - fixCleanup3_2_0); + runAmendmentIndependent(); testLoanSetBlockedLoanPayAllowedWhenCanTransferCleared(); testLendingCanTradeClearedNoImpact(); - testPoCUnsignedUnderflowOnFullPayAfterEarlyPeriodic(); - - testDisabled(); - testSelfLoan(); - testIssuerLoan(); - testLoanSet(); - testLifecycle(); - testServiceFeeOnBrokerDeepFreeze(); - - testRPC(); - testInvalidLoanDelete(); - testInvalidLoanManage(); - testInvalidLoanPay(); - - testBatchBypassCounterparty(); - testLoanPayComputePeriodicPaymentValidRateInvariant(); - testAccountSendMptMinAmountInvariant(); - testLoanPayDebtDecreaseInvariant(); - testWrongMaxDebtBehavior(); - testLoanPayComputePeriodicPaymentValidTotalInterestInvariant(); - testDosLoanPay(all | fixCleanup3_1_3); - testDosLoanPay(all - fixCleanup3_1_3); - testLoanPayComputePeriodicPaymentValidTotalPrincipalPaidInvariant(); - testLoanPayComputePeriodicPaymentValidTotalInterestPaidInvariant(); - testLoanNextPaymentDueDateOverflow(); - - testRequireAuth(); - testDustManipulation(); - - testRIPD3831(); - testRIPD3459(); - testRIPD3901(); - testRIPD3902(); - testRoundingAllowsUndercoverage(); - testBorrowerIsBroker(); - testIssuerIsBorrower(); - testLimitExceeded(); - testOverpaymentManagementFee(); - testLoanPayBrokerOwnerMissingTrustline(); - testLoanPayBrokerOwnerUnauthorizedMPT(); - testLoanPayBrokerOwnerNoPermissionedDomainMPT(); - testLoanSetBrokerOwnerNoPermissionedDomainMPT(); - testSequentialFLCDepletion(); + testDosLoanPay(all_ | fixCleanup3_1_3); + testDosLoanPay(all_ - fixCleanup3_1_3); + for (auto const& features : amendmentCombinations({fixCleanup3_2_0, featureMPTokensV2})) + runAmendmentSensitive(features); } }; @@ -7867,7 +8007,7 @@ protected: .payInterval = payInterval, }; - runLoan(assetType, brokerParams, loanParams); + runLoan(assetType, brokerParams, loanParams, all_); } public: @@ -7926,7 +8066,7 @@ class LoanArbitrary_test : public LoanBatch_test .payTotal = 2, .payInterval = 200}; - runLoan(AssetType::XRP, brokerParams, loanParams); + runLoan(AssetType::XRP, brokerParams, loanParams, all_); } }; From 7fdaa0a5efdd3a11615790cb5c14af50887969d2 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Thu, 21 May 2026 16:51:58 +0200 Subject: [PATCH 011/135] fix: Fix IOU precision issues in LoanBrokerCover transactions (#7274) --- include/xrpl/ledger/helpers/LendingHelpers.h | 30 +++ include/xrpl/protocol/STAmount.h | 18 ++ src/libxrpl/ledger/helpers/LendingHelpers.cpp | 33 +++ src/libxrpl/protocol/STAmount.cpp | 5 + .../lending/LoanBrokerCoverClawback.cpp | 4 + .../lending/LoanBrokerCoverDeposit.cpp | 54 ++++- .../lending/LoanBrokerCoverWithdraw.cpp | 5 + src/test/app/LendingHelpers_test.cpp | 101 ++++++++- src/test/app/LoanBroker_test.cpp | 212 ++++++++++++++++++ src/test/protocol/STAmount_test.cpp | 95 ++++++++ 10 files changed, 546 insertions(+), 11 deletions(-) diff --git a/include/xrpl/ledger/helpers/LendingHelpers.h b/include/xrpl/ledger/helpers/LendingHelpers.h index a6ab42254b..cce41a38c5 100644 --- a/include/xrpl/ledger/helpers/LendingHelpers.h +++ b/include/xrpl/ledger/helpers/LendingHelpers.h @@ -4,8 +4,38 @@ #include #include +#include + namespace xrpl { +/** + * Broker cover preclaim precision guard (fixCleanup3_2_0). + * + * Prevents a "silent sub-ULP no-op" where a deposit, withdrawal, or clawback + * amount is so small that it rounds to zero at `sfCoverAvailable`'s scale. + * Without this guard, both the pseudo trust-line and `sfCoverAvailable` would + * identically absorb the rounded zero, resulting in a successful transaction + * (tesSUCCESS) where no funds actually moved. + * + * @param view Read view (rules used for amendment gating). + * @param sleBroker The loan broker SLE (read-only). + * @param vaultAsset The underlying vault asset (the broker's cover asset). + * @param amount The effective subtraction/addition amount. + * @param j Journal for logging. + * @param logPrefix Transactor name for log diagnostics. + * + * @return `tecPRECISION_LOSS` if the request rounds to zero at cover scale. + * `tesSUCCESS` if the amendment is disabled or the request is safely supra-ULP. + */ +[[nodiscard]] TER +canApplyToBrokerCover( + ReadView const& view, + SLE::const_ref sleBroker, + Asset const& vaultAsset, + STAmount const& amount, + beast::Journal j, + std::string_view logPrefix); + // Lending protocol has dependencies, so capture them here. bool checkLendingProtocolDependencies(Rules const& rules, STTx const& tx); diff --git a/include/xrpl/protocol/STAmount.h b/include/xrpl/protocol/STAmount.h index 01511c23ea..c576c0da31 100644 --- a/include/xrpl/protocol/STAmount.h +++ b/include/xrpl/protocol/STAmount.h @@ -184,6 +184,24 @@ public: [[nodiscard]] STAmount const& value() const noexcept; + /** + * Checks if this amount evaluates to zero when constrained to a specific + * accounting scale. + * + * For XRP and MPT `roundToScale` is a no-op, returns true only when the amount itself is zero. + * The `scale` argument is ignored in that case. + * For IOU, the amount is rounded to the given scale using Number::RoundingMode::ToNearest mode + * and the result is checked for zero; if `scale <= exponent()`, `roundToScale` short-circuits + * and returns the value unchanged, so this returns false for any non-zero amount. + * + * @param scale The target accounting scale to evaluate against. + * @return `true` if this amount rounds to zero at the given scale, `false` otherwise. + * + * @see roundToScale + */ + [[nodiscard]] bool + isZeroAtScale(int scale) const; + //-------------------------------------------------------------------------- // // Operators diff --git a/src/libxrpl/ledger/helpers/LendingHelpers.cpp b/src/libxrpl/ledger/helpers/LendingHelpers.cpp index 2c756c2877..1fedbb5f13 100644 --- a/src/libxrpl/ledger/helpers/LendingHelpers.cpp +++ b/src/libxrpl/ledger/helpers/LendingHelpers.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -24,10 +25,42 @@ #include #include #include +#include #include namespace xrpl { +[[nodiscard]] TER +canApplyToBrokerCover( + ReadView const& view, + SLE::const_ref sleBroker, + Asset const& vaultAsset, + STAmount const& amount, + beast::Journal j, + std::string_view logPrefix) +{ + XRPL_ASSERT( + sleBroker && sleBroker->getType() == ltLOAN_BROKER, + "xrpl::canApplyToBrokerCover : valid LoanBroker sle"); + XRPL_ASSERT(vaultAsset == amount.asset(), "xrpl::canApplyToBrokerCover : valid asset"); + + if (!view.rules().enabled(fixCleanup3_2_0)) + return tesSUCCESS; + + if (amount == beast::kZero) + return tecPRECISION_LOSS; + + int const coverScale = scale(sleBroker->at(sfCoverAvailable), vaultAsset); + if (amount.isZeroAtScale(coverScale)) + { + JLOG(j.warn()) << logPrefix << ": amount " << amount.getFullText() + << " rounds to zero at cover scale " << coverScale; + return tecPRECISION_LOSS; + } + + return tesSUCCESS; +} + bool checkLendingProtocolDependencies(Rules const& rules, STTx const& tx) { diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index 2d051722bf..c40beabf12 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -1738,4 +1738,9 @@ divRoundStrict(STAmount const& num, STAmount const& den, Asset const& asset, boo return divRoundImpl(num, den, asset, roundUp); } +[[nodiscard]] bool +STAmount::isZeroAtScale(int scale) const +{ + return roundToScale(*this, scale, Number::RoundingMode::ToNearest).signum() == 0; +} } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp index ab26353a1c..48cb6b90aa 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp @@ -291,6 +291,10 @@ LoanBrokerCoverClawback::preclaim(PreclaimContext const& ctx) } STAmount const& clawAmount = *findClawAmount; + if (auto const ret = canApplyToBrokerCover( + ctx.view, sleBroker, vaultAsset, clawAmount, ctx.j, "LoanBrokerCoverClawback")) + return ret; + // Explicitly check the balance of the trust line / MPT to make sure the // balance is actually there. It should always match `sfCoverAvailable`, so // if there isn't, this is an internal error. diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp index 04905d5ea3..e84f277f5b 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp @@ -1,9 +1,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -87,6 +89,29 @@ LoanBrokerCoverDeposit::preclaim(PreclaimContext const& ctx) if (auto const ret = requireAuth(ctx.view, vaultAsset, account, AuthType::StrongAuth)) return ret; + // Deposit must round the amount Downward to cover scale and then reuse that rounded + // value for the actual transfer in doApply — otherwise implicit round-to-nearest during + // `sfCoverAvailable +=` could credit the broker more than the depositor paid Computing it + // here in preclaim lets us reject sub-cover-scale dust early with tecPRECISION_LOSS instead of + // failing only in doApply. + bool const fix320Enabled = ctx.view.rules().enabled(fixCleanup3_2_0); + auto const roundedAmount = [&]() -> STAmount { + if (!fix320Enabled) + return tx[sfAmount]; + + return roundToScale( + tx[sfAmount], + scale(sleBroker->at(sfCoverAvailable), vaultAsset), + Number::RoundingMode::Downward); + }(); + + if (fix320Enabled && roundedAmount == beast::kZero) + { + JLOG(ctx.j.warn()) << "LoanBrokerCoverDeposit: deposit amount: " << tx[sfAmount] + << " is zero at loan broker scale"; + return tecPRECISION_LOSS; + } + if (accountHolds( ctx.view, account, @@ -94,7 +119,7 @@ LoanBrokerCoverDeposit::preclaim(PreclaimContext const& ctx) FreezeHandling::ZeroIfFrozen, AuthHandling::ZeroIfUnauthorized, ctx.j, - SpendableHandling::FullBalance) < amount) + SpendableHandling::FullBalance) < roundedAmount) return tecINSUFFICIENT_FUNDS; return tesSUCCESS; @@ -106,8 +131,6 @@ LoanBrokerCoverDeposit::doApply() auto const& tx = ctx_.tx; auto const brokerID = tx[sfLoanBrokerID]; - auto const amount = tx[sfAmount]; - auto broker = view().peek(keylet::loanbroker(brokerID)); if (!broker) return tecINTERNAL; // LCOV_EXCL_LINE @@ -117,9 +140,32 @@ LoanBrokerCoverDeposit::doApply() return tecINTERNAL; // LCOV_EXCL_LINE auto const vaultAsset = vault->at(sfAsset); - auto const brokerPseudoID = broker->at(sfAccount); + // Re-round here (matches preclaim) so the same cover-scale-quantized + // value drives both the trustline transfer and the cover increment; + // see the rationale comment in preclaim. + bool const fix320Enabled = view().rules().enabled(fixCleanup3_2_0); + auto const amount = [&]() -> STAmount { + if (!fix320Enabled) + return tx[sfAmount]; + + return roundToScale( + tx[sfAmount], + scale(broker->at(sfCoverAvailable), vaultAsset), + Number::RoundingMode::Downward); + }(); + + // We validated zero-amount in preclaim, if we ended up with zero now, fail hard. + if (amount == beast::kZero) + { + // LCOV_EXCL_START + JLOG(j_.error()) << "LoanBrokerCoverDeposit: deposit amount: " << tx[sfAmount] + << " is zero"; + return tecINTERNAL; + // LCOV_EXCL_STOP + } + // Transfer assets from depositor to pseudo-account. if (auto ter = accountSend(view(), accountID_, brokerPseudoID, amount, j_, WaiveTransferFee::Yes)) diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp index 331c44b1e8..dbe2de2100 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp @@ -94,6 +94,11 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx) if (amount.asset() != vaultAsset) return tecWRONG_ASSET; + // Helper handles both IOU and MPT correctly without explicit branching. + if (auto const ret = canApplyToBrokerCover( + ctx.view, sleBroker, vaultAsset, amount, ctx.j, "LoanBrokerCoverWithdraw")) + return ret; + // The broker's pseudo-account is the source of funds. auto const pseudoAccountID = sleBroker->at(sfAccount); // Post-fixCleanup3_2_0: cover withdraw is a recovery path that bypasses diff --git a/src/test/app/LendingHelpers_test.cpp b/src/test/app/LendingHelpers_test.cpp index 96d0722732..af46dd2e0f 100644 --- a/src/test/app/LendingHelpers_test.cpp +++ b/src/test/app/LendingHelpers_test.cpp @@ -8,9 +8,15 @@ #include #include #include +#include +#include +#include +#include +#include #include #include +#include #include #include @@ -287,9 +293,9 @@ class LendingHelpers_test : public beast::unit_test::Suite std::uint32_t n; }; auto const cases = std::vector{ - {"r=5%, n=3", Number{5, -2}, 3}, - {"r=0.1%, n=1000", Number{1, -3}, 1'000}, - {"r=1e-7, n=100 (above threshold by 10x)", Number{1, -7}, 100}, + {.name = "r=5%, n=3", .r = Number{5, -2}, .n = 3}, + {.name = "r=0.1%, n=1000", .r = Number{1, -3}, .n = 1'000}, + {.name = "r=1e-7, n=100 (above threshold by 10x)", .r = Number{1, -7}, .n = 100}, }; for (auto const& tc : cases) { @@ -318,8 +324,10 @@ class LendingHelpers_test : public beast::unit_test::Suite auto const cases = std::vector{ // bug regime: r = 1 TenthBips32 over 600s payment interval // → r ≈ 1.9e-10, r*n ≈ 3.8e-10 < 1e-9. - {"bug regime: r~1.9e-10, n=2", loanPeriodicRate(TenthBips32{1}, 600), 2}, - {"r=1e-12, n=100", Number{1, -12}, 100}, + {.name = "bug regime: r~1.9e-10, n=2", + .r = loanPeriodicRate(TenthBips32{1}, 600), + .n = 2}, + {.name = "r=1e-12, n=100", .r = Number{1, -12}, .n = 100}, }; for (auto const& tc : cases) { @@ -356,8 +364,8 @@ class LendingHelpers_test : public beast::unit_test::Suite std::uint32_t n; }; auto const cases = std::vector{ - {"r=1e-9, n=1", Number{1, -9}, 1}, - {"r=1e-12, n=1000", Number{1, -12}, 1'000}, + {.name = "r=1e-9, n=1", .r = Number{1, -9}, .n = 1}, + {.name = "r=1e-12, n=1000", .r = Number{1, -12}, .n = 1'000}, }; for (auto const& tc : cases) @@ -1439,6 +1447,84 @@ class LendingHelpers_test : public beast::unit_test::Suite } public: + void + testCanApplyToBrokerCover() + { + using namespace jtx; + + Account const issuer{"issuer"}; + PrettyAsset const iou = issuer["IOU"]; + + // sfCoverAvailable = Number{10} on an IOU → STAmount exponent = -14, + // so coverScale = -14. The ULP boundary is 5e-15; anything below + // that rounds to zero at cover scale. Number{1,-16} = 1e-16 is our + // representative sub-ULP probe. + struct TestCase + { + std::string name; + Number coverAvailable; + STAmount amount; + TER expected; + }; + + auto const testCases = std::vector{ + { + .name = "Zero amount", + .coverAvailable = Number{10}, + .amount = STAmount{iou, Number{0}}, + .expected = tecPRECISION_LOSS, + }, + { + .name = "Rounds to zero at cover scale", + .coverAvailable = Number{10}, + .amount = STAmount{iou, Number{1, -16}}, + .expected = tecPRECISION_LOSS, + }, + { + .name = "Zero coverAvailable, whole-unit amount", + // coverScale = 0 (zero STAmount exponent); 1 IOU is not + // zero at integer scale → tesSUCCESS. + .coverAvailable = Number{0}, + .amount = STAmount{iou, Number{1}}, + .expected = tesSUCCESS, + }, + { + .name = "Supra-ULP amount", + .coverAvailable = Number{10}, + .amount = STAmount{iou, Number{1, -13}}, + .expected = tesSUCCESS, + }, + }; + + Env const env{*this}; + + for (auto const& tc : testCases) + { + testcase("canApplyToBrokerCover: " + tc.name); + auto sle = std::make_shared(ltLOAN_BROKER, uint256{1u}); + sle->at(sfCoverAvailable) = tc.coverAvailable; + BEAST_EXPECT( + canApplyToBrokerCover(*env.current(), sle, iou, tc.amount, env.journal, "test") == + tc.expected); + } + + // Amendment off → guard is bypassed regardless of amount. + { + testcase("canApplyToBrokerCover: amendment disabled"); + Env const envOff{*this, testableAmendments() - fixCleanup3_2_0}; + auto sle = std::make_shared(ltLOAN_BROKER, uint256{1u}); + sle->at(sfCoverAvailable) = Number{10}; + BEAST_EXPECT( + canApplyToBrokerCover( + *envOff.current(), + sle, + iou, + STAmount{iou, Number{0}}, + envOff.journal, + "test") == tesSUCCESS); + } + } + void run() override { @@ -1462,6 +1548,7 @@ public: testComputePaymentFactorNearZeroRate(); testComputeOverpaymentComponents(); testComputeInterestAndFeeParts(); + testCanApplyToBrokerCover(); } }; diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index a7e036a621..c899778391 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -55,6 +55,7 @@ #include #include #include +#include #include namespace xrpl::test { @@ -1823,10 +1824,221 @@ class LoanBroker_test : public beast::unit_test::Suite testRIPD4274MPT(); } + // Exercises canApplyToBrokerCover (fixCleanup3_2_0): a deposit, withdraw, + // or clawback whose amount rounds to zero at sfCoverAvailable's precision + // scale must be rejected with tecPRECISION_LOSS once the amendment is on, + // and must silently succeed without changing sfCoverAvailable when off. + void + testCoverPrecisionGuard() + { + using namespace jtx; + using namespace loanBroker; + + Account const issuer{"issuer"}; + Account const alice{"alice"}; + + // sfCoverAvailable = 10 IOU → STAmount exponent = -14. + // Anything < 5e-15 rounds to zero at that scale. + // 1e-16 is the representative sub-ULP probe amount. + + // Shared setup: funds accounts, creates a vault + broker with 10 IOU + // cover, and returns {brokerKeylet, iou}. + auto const setup = [&](Env& env) -> std::pair { + Vault const vault{env}; + + env.fund(XRP(100'000), issuer, alice); + env.close(); + env(fset(issuer, asfAllowTrustLineClawback)); + env.close(); + + PrettyAsset const iou = issuer["IOU"]; + env(trust(alice, iou(1'000'000))); + env.close(); + env(pay(issuer, alice, iou(1'000))); + env.close(); + + auto [createTx, vaultKeylet] = vault.create({.owner = alice, .asset = iou}); + env(createTx); + env.close(); + + auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice)); + env(set(alice, vaultKeylet.key)); + env.close(); + + env(coverDeposit(alice, brokerKeylet.key, iou(10))); + env.close(); + + return {brokerKeylet, iou}; + }; + + auto runTestCases = [&](FeatureBitset features) { + TER const expected = + features[fixCleanup3_2_0] ? TER{tecPRECISION_LOSS} : TER{tesSUCCESS}; + + { + testcase("Cover precision guard: Deposit zero-at-scale"); + Env env{*this, features}; + auto const [brokerKeylet, iou] = setup(env); + PrettyAmount const subUlpAmt = iou(Number{1, -16}); + auto const coverBefore = env.le(brokerKeylet)->at(sfCoverAvailable); + env(coverDeposit(alice, brokerKeylet.key, subUlpAmt), Ter(expected)); + env.close(); + if (expected == tesSUCCESS) + { + if (auto const broker = env.le(brokerKeylet); BEAST_EXPECT(broker)) + BEAST_EXPECT(broker->at(sfCoverAvailable) == coverBefore); + } + } + + { + testcase("Cover precision guard: Deposit rounds down"); + // Both cases succeed; post-fix the amount is rounded DOWN to + // cover scale first, so the delta differs from pre-fix + // Input: 1.8e-14 IOU (sub-scale at cover scale -14) + // Pre-fix: 10 + 1.8e-14 → round-to-nearest → + // 10.00000000000002 → delta 2e-14 + // Post-fix: roundToScale(1.8e-14, -14, Downward) = 1e-14; + // 10 + 1e-14 = 10.00000000000001 → delta 1e-14 + Env env{*this, features}; + auto const [brokerKeylet, iou] = setup(env); + PrettyAmount const subUlpAmt = iou(Number{18, -15}); + auto const coverBefore = env.le(brokerKeylet)->at(sfCoverAvailable); + env(coverDeposit(alice, brokerKeylet.key, subUlpAmt), Ter(tesSUCCESS)); + env.close(); + auto const brokerAfter = env.le(brokerKeylet); + if (!BEAST_EXPECT(brokerAfter)) + return; + + Number const delta = features[fixCleanup3_2_0] ? Number{1, -14} : Number{2, -14}; + BEAST_EXPECT(brokerAfter->at(sfCoverAvailable) - coverBefore == delta); + } + + // Property: post-fix, when the user deposits `x` and cover + // gains `x'`, we always have 0 <= x - x' < 1 ULP at cover + // scale (cover holds 10 IOU → ULP = 1e-14). Pre-fix uses + // STAmount's default round-to-nearest during `+=`, which can + // over-deposit (x' > x), so the property only holds with + // fixCleanup3_2_0 enabled. + if (features[fixCleanup3_2_0]) + { + testcase("Cover precision guard: Deposit rounding bound"); + Env env{*this, features}; + auto const [brokerKeylet, iou] = setup(env); + Number const oneUlp{1, -14}; + // Each requested amount lies strictly between 1·ULP and + // 2·ULP at cover scale; post-fix `roundDown` credits + // exactly `oneUlp` and leaves a strictly-positive, + // strictly-sub-ULP residual. + for (Number const requested : {Number{11, -15}, Number{15, -15}, Number{19, -15}}) + { + auto const broker = env.le(brokerKeylet); + if (!BEAST_EXPECT(broker)) + return; + Number const coverBefore = broker->at(sfCoverAvailable); + env(coverDeposit(alice, brokerKeylet.key, iou(requested)), Ter(tesSUCCESS)); + env.close(); + auto const brokerAfter = env.le(brokerKeylet); + if (!BEAST_EXPECT(brokerAfter)) + return; + Number const coverAfter = brokerAfter->at(sfCoverAvailable); + Number const actual = coverAfter - coverBefore; + Number const lost = requested - actual; + BEAST_EXPECT(lost >= Number{0}); + BEAST_EXPECT(lost < oneUlp); + } + } + + { + testcase("Cover precision guard: Withdraw"); + Env env{*this, features}; + auto const [brokerKeylet, iou] = setup(env); + PrettyAmount const subUlpAmt = iou(Number{1, -16}); + auto const coverBefore = env.le(brokerKeylet)->at(sfCoverAvailable); + auto const aliceBalanceBefore = env.balance(alice, iou); + env(coverWithdraw(alice, brokerKeylet.key, subUlpAmt), Ter(expected)); + env.close(); + if (expected == tesSUCCESS) + { + if (auto const broker = env.le(brokerKeylet); BEAST_EXPECT(broker)) + BEAST_EXPECT(broker->at(sfCoverAvailable) == coverBefore); + BEAST_EXPECT(env.balance(alice, iou) == aliceBalanceBefore); + } + } + + { + testcase("Cover precision guard: Clawback"); + Env env{*this, features}; + auto const [brokerKeylet, iou] = setup(env); + PrettyAmount const subUlpAmt = iou(Number{1, -16}); + auto const coverBefore = env.le(brokerKeylet)->at(sfCoverAvailable); + env(coverClawback(issuer), + kLoanBrokerId(brokerKeylet.key), + kAmount(subUlpAmt), + Ter(expected)); + env.close(); + if (expected == tesSUCCESS) + { + if (auto const broker = env.le(brokerKeylet); BEAST_EXPECT(broker)) + BEAST_EXPECT(broker->at(sfCoverAvailable) == coverBefore); + } + } + + // MPT amounts are integers; scale is 0; the guard never rejects a + // positive integer amount. Verify all three callsites pass with amendment on. + { + testcase("Cover precision guard: MPT min amount passes"); + Env env{*this, all_}; + + env.fund(XRP(100'000), issuer, alice); + env.close(); + + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock}); + env.close(); + + PrettyAsset const mptAsset = mptt["MPT"]; + mptt.authorize({.account = alice}); + env.close(); + + env(pay(issuer, alice, mptAsset(100))); + env.close(); + + Vault const vault{env}; + auto [createTx, vaultKeylet] = vault.create({.owner = alice, .asset = mptAsset}); + env(createTx); + env.close(); + + auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice)); + env(set(alice, vaultKeylet.key)); + env.close(); + + env(coverDeposit(alice, brokerKeylet.key, mptAsset(10))); + env.close(); + + env(coverDeposit(alice, brokerKeylet.key, mptAsset(1)), Ter(tesSUCCESS)); + env.close(); + + env(coverWithdraw(alice, brokerKeylet.key, mptAsset(1)), Ter(tesSUCCESS)); + env.close(); + + env(coverClawback(issuer), + kLoanBrokerId(brokerKeylet.key), + kAmount(mptAsset(1)), + Ter(tesSUCCESS)); + env.close(); + } + }; + + runTestCases(all_); + runTestCases(all_ - fixCleanup3_2_0); + } + public: void run() override { + testCoverPrecisionGuard(); + testLoanBrokerSetDebtMaximum(); testLoanBrokerCoverDepositNullVault(); diff --git a/src/test/protocol/STAmount_test.cpp b/src/test/protocol/STAmount_test.cpp index 322d51f84d..c7207589ff 100644 --- a/src/test/protocol/STAmount_test.cpp +++ b/src/test/protocol/STAmount_test.cpp @@ -1205,6 +1205,100 @@ public: //-------------------------------------------------------------------------- + void + testIsZeroAtScale() + { + testcase("isZeroAtScale"); + + Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)}; + + // IOU: 10 IOU — mantissa = kMinValue (10^15), exponent = -14. + // One ULP at this scale is 10^-14; half-ULP is 5*10^-15. + { + STAmount const ref{usd, STAmount::kMinValue, -14}; + int const refScale = ref.exponent(); // -14 + BEAST_EXPECT(refScale == -14); + + // Zero rounds to zero at any scale. + STAmount const iouZero{usd, 0}; + BEAST_EXPECT(iouZero.isZeroAtScale(refScale)); + + // Sub-ULP: 1e-16 IOU (mantissa = kMinValue, exponent = -31). + // Far below half-ULP → rounds to zero. + STAmount const subUlp{usd, STAmount::kMinValue, -31}; + BEAST_EXPECT(subUlp.isZeroAtScale(refScale)); + + // One ULP: 1e-14 IOU (mantissa = kMinValue, exponent = -29). + // Exactly the smallest representable unit at refScale → not zero. + STAmount const oneUlp{usd, STAmount::kMinValue, -29}; + BEAST_EXPECT(!oneUlp.isZeroAtScale(refScale)); + + // The reference value itself: exponent == scale → returned + // unchanged → not zero. + BEAST_EXPECT(!ref.isZeroAtScale(refScale)); + + // A much larger value: certainly not zero at this scale. + STAmount const large{usd, STAmount::kMinValue, 0}; // 1e15 IOU + BEAST_EXPECT(!large.isZeroAtScale(refScale)); + + // When scale equals the value's own exponent, roundToScale + // short-circuits and returns the value unchanged. + BEAST_EXPECT(!subUlp.isZeroAtScale(subUlp.exponent())); + BEAST_EXPECT(!oneUlp.isZeroAtScale(oneUlp.exponent())); + + // Half-ULP boundary. roundToScale forms (value + ref) - ref + // where ref = 10 IOU has mantissa 1e15 (LSB 0, even). + // Number's default rounding is to-nearest-even, so an exact + // half-ULP tie rounds toward the even-LSB neighbour — the + // reference itself — and the round-trip result is zero. + // Just below half-ULP rounds the same way; just above + // clears half-ULP and bumps the mantissa to 1e15 + 1. + STAmount const justBelowHalf{usd, STAmount::kMinValue * 4, -30}; + BEAST_EXPECT(justBelowHalf.isZeroAtScale(refScale)); + + STAmount const halfUlp{usd, STAmount::kMinValue * 5, -30}; + BEAST_EXPECT(halfUlp.isZeroAtScale(refScale)); + + STAmount const justAboveHalf{usd, STAmount::kMinValue * 6, -30}; + BEAST_EXPECT(!justAboveHalf.isZeroAtScale(refScale)); + + // Large magnitude gap: dust value far below an enormous scale. + // 1e-80 with scale +15 — the value vanishes utterly. + STAmount const dust{usd, STAmount::kMinValue, -95}; + BEAST_EXPECT(dust.isZeroAtScale(15)); + + // Negative values mirror positive behaviour. + STAmount const negSubUlp{usd, STAmount::kMinValue, -31, true}; + BEAST_EXPECT(negSubUlp.isZeroAtScale(refScale)); + + STAmount const negOneUlp{usd, STAmount::kMinValue, -29, true}; + BEAST_EXPECT(!negOneUlp.isZeroAtScale(refScale)); + } + + // XRP is integral — roundToScale short-circuits, value is preserved. + { + STAmount const xrp{XRPAmount{1}}; + BEAST_EXPECT(!xrp.isZeroAtScale(-14)); + BEAST_EXPECT(!xrp.isZeroAtScale(0)); + + STAmount const xrpZero{XRPAmount{0}}; + BEAST_EXPECT(xrpZero.isZeroAtScale(-14)); + } + + // MPT is integral — same short-circuit behaviour as XRP. + { + MPTIssue const mpt{makeMptID(1, AccountID(0x4985601))}; + STAmount const mptAmt{mpt, 1}; + BEAST_EXPECT(!mptAmt.isZeroAtScale(0)); + BEAST_EXPECT(!mptAmt.isZeroAtScale(-14)); + + STAmount const mptZero{mpt, 0}; + BEAST_EXPECT(mptZero.isZeroAtScale(0)); + } + } + + //-------------------------------------------------------------------------- + void run() override { @@ -1223,6 +1317,7 @@ public: testCanSubtractXRP(); testCanSubtractIOU(); testCanSubtractMPT(); + testIsZeroAtScale(); } }; From e24de65f42afc48e8f6743a159dcf24d5db2be0a Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Thu, 21 May 2026 18:13:41 +0200 Subject: [PATCH 012/135] chore: Revert graceful peer disconnection and follow-up fix (#7296) --- include/xrpl/server/detail/Door.h | 8 +- src/xrpld/overlay/detail/ConnectAttempt.cpp | 422 ++++++-------------- src/xrpld/overlay/detail/ConnectAttempt.h | 204 +--------- src/xrpld/overlay/detail/PeerImp.cpp | 302 +++++--------- src/xrpld/overlay/detail/PeerImp.h | 205 +--------- 5 files changed, 246 insertions(+), 895 deletions(-) diff --git a/include/xrpl/server/detail/Door.h b/include/xrpl/server/detail/Door.h index e273ca791f..b72bc9bdea 100644 --- a/include/xrpl/server/detail/Door.h +++ b/include/xrpl/server/detail/Door.h @@ -90,11 +90,11 @@ private: acceptor_type acceptor_; boost::asio::strand strand_; bool ssl_{ - port_.protocol.count("https") > 0 || port_.protocol.count("wss") > 0 || - port_.protocol.count("wss2") > 0 || port_.protocol.count("peer") > 0}; + port_.protocol.contains("https") || port_.protocol.contains("wss") || + port_.protocol.contains("wss2") || port_.protocol.contains("peer")}; bool plain_{ - port_.protocol.count("http") > 0 || port_.protocol.count("ws") > 0 || - (port_.protocol.count("ws2") != 0u)}; + port_.protocol.contains("http") || port_.protocol.contains("ws") || + (port_.protocol.contains("ws2"))}; static constexpr std::chrono::milliseconds kInitialAcceptDelay{50}; static constexpr std::chrono::milliseconds kMaxAcceptDelay{2000}; std::chrono::milliseconds acceptDelay_{kInitialAcceptDelay}; diff --git a/src/xrpld/overlay/detail/ConnectAttempt.cpp b/src/xrpld/overlay/detail/ConnectAttempt.cpp index 1167e19ca3..295f4f5497 100644 --- a/src/xrpld/overlay/detail/ConnectAttempt.cpp +++ b/src/xrpld/overlay/detail/ConnectAttempt.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -28,19 +29,17 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include #include #include -#include +#include #include #include @@ -52,7 +51,7 @@ ConnectAttempt::ConnectAttempt( endpoint_type remoteEndpoint, 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) @@ -65,7 +64,6 @@ ConnectAttempt::ConnectAttempt( , usage_(usage) , strand_(boost::asio::make_strand(ioContext)) , timer_(ioContext) - , stepTimer_(ioContext) , streamPtr_( std::make_unique( socket_type(std::forward(ioContext)), @@ -78,10 +76,9 @@ ConnectAttempt::ConnectAttempt( ConnectAttempt::~ConnectAttempt() { - // slot_ will be null if we successfully connected - // and transferred ownership to a PeerImp if (slot_ != nullptr) overlay_.peerFinder().onClosed(slot_); + JLOG(journal_.trace()) << "~ConnectAttempt"; } void @@ -92,30 +89,17 @@ ConnectAttempt::stop() boost::asio::post(strand_, std::bind(&ConnectAttempt::stop, shared_from_this())); return; } - - if (!socket_.is_open()) - return; - - JLOG(journal_.debug()) << "stop: Stop"; - - shutdown(); + if (socket_.is_open()) + { + JLOG(journal_.debug()) << "Stop"; + } + close(); } void ConnectAttempt::run() { - if (!strand_.running_in_this_thread()) - { - boost::asio::post(strand_, std::bind(&ConnectAttempt::run, shared_from_this())); - return; - } - - JLOG(journal_.debug()) << "run: connecting to " << remoteEndpoint_; - - ioPending_ = true; - - // Allow up to connectTimeout_ seconds to establish remote peer connection - setTimer(ConnectionStep::TcpConnect); + setTimer(); stream_.next_layer().async_connect( remoteEndpoint_, @@ -126,73 +110,6 @@ ConnectAttempt::run() //------------------------------------------------------------------------------ -void -ConnectAttempt::shutdown() -{ - XRPL_ASSERT( - strand_.running_in_this_thread(), "xrpl::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(), - "xrpl::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); - stream_.async_shutdown(bind_executor( - strand_, - std::bind(&ConnectAttempt::onShutdown, shared_from_this(), std::placeholders::_1))); - return; - } - - 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 const 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() { @@ -201,93 +118,50 @@ ConnectAttempt::close() if (!socket_.is_open()) return; - cancelTimer(); + try + { + timer_.cancel(); + socket_.close(); + } + catch (boost::system::system_error const&) // NOLINT(bugprone-empty-catch) + { + // ignored + } - error_code ec; - socket_.close(ec); // NOLINT(bugprone-unused-return-value) + JLOG(journal_.debug()) << "Closed"; } void ConnectAttempt::fail(std::string const& reason) { JLOG(journal_.debug()) << reason; - shutdown(); + close(); } void ConnectAttempt::fail(std::string const& name, error_code ec) { JLOG(journal_.debug()) << name << ": " << ec.message(); - shutdown(); + close(); } void -ConnectAttempt::setTimer(ConnectionStep step) +ConnectAttempt::setTimer() { - currentStep_ = step; - - // Set global timer (only if not already set) - if (timer_.expiry() == std::chrono::steady_clock::time_point{}) - { - try - { - timer_.expires_after(kConnectTimeout); - 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(); - close(); - return; - } - } - - // Set step-specific timer try { - std::chrono::seconds stepTimeout; - switch (step) - { - case ConnectionStep::TcpConnect: - stepTimeout = StepTimeouts::kTcpConnect; - break; - case ConnectionStep::TlsHandshake: - stepTimeout = StepTimeouts::kTlsHandshake; - break; - case ConnectionStep::HttpWrite: - stepTimeout = StepTimeouts::kHttpWrite; - break; - case ConnectionStep::HttpRead: - stepTimeout = StepTimeouts::kHttpRead; - break; - case ConnectionStep::ShutdownStarted: - stepTimeout = StepTimeouts::kTlsShutdown; - 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"; + timer_.expires_after(std::chrono::seconds(15)); } - catch (std::exception const& ex) + catch (boost::system::system_error const& e) { - JLOG(journal_.error()) << "setTimer (step " << stepToString(step) << "): " << ex.what(); - close(); + JLOG(journal_.error()) << "setTimer: " << e.code(); return; } + + timer_.async_wait( + boost::asio::bind_executor( + strand_, + std::bind(&ConnectAttempt::onTimer, shared_from_this(), std::placeholders::_1))); } void @@ -296,7 +170,6 @@ ConnectAttempt::cancelTimer() try { timer_.cancel(); - stepTimer_.cancel(); } catch (boost::system::system_error const&) // NOLINT(bugprone-empty-catch) { @@ -321,40 +194,18 @@ ConnectAttempt::onTimer(error_code ec) close(); return; } - - // Determine which timer expired by checking their expiry times - auto const now = std::chrono::steady_clock::now(); - bool const globalExpired = (timer_.expiry() <= now); - bool const 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(); + fail("Timeout"); } void ConnectAttempt::onConnect(error_code ec) { - ioPending_ = false; + cancelTimer(); if (ec) { if (ec == boost::asio::error::operation_aborted) - { - tryAsyncShutdown(); return; - } fail("onConnect", ec); return; @@ -371,15 +222,7 @@ ConnectAttempt::onConnect(error_code ec) return; } - if (shutdown_) - { - tryAsyncShutdown(); - return; - } - - ioPending_ = true; - - setTimer(ConnectionStep::TlsHandshake); + setTimer(); stream_.set_verify_mode(boost::asio::ssl::verify_none); stream_.async_handshake( @@ -392,15 +235,14 @@ ConnectAttempt::onConnect(error_code ec) void ConnectAttempt::onHandshake(error_code ec) { - ioPending_ = false; + cancelTimer(); + if (!socket_.is_open()) + return; if (ec) { if (ec == boost::asio::error::operation_aborted) - { - tryAsyncShutdown(); return; - } fail("onHandshake", ec); return; @@ -413,21 +255,18 @@ ConnectAttempt::onHandshake(error_code ec) return; } - setTimer(ConnectionStep::HttpWrite); - - // check if we connected to ourselves if (!overlay_.peerFinder().onConnected( slot_, beast::IPAddressConversion::fromAsio(localEndpoint))) { - fail("Self connection"); + fail("Duplicate connection"); return; } auto const sharedValue = makeSharedValue(*streamPtr_, journal_); if (!sharedValue) { - shutdown(); - return; // makeSharedValue logs + close(); // makeSharedValue logs + return; } req_ = makeRequest( @@ -445,14 +284,7 @@ ConnectAttempt::onHandshake(error_code ec) remoteEndpoint_.address(), app_); - if (shutdown_) - { - tryAsyncShutdown(); - return; - } - - ioPending_ = true; - + setTimer(); boost::beast::http::async_write( stream_, req_, @@ -464,30 +296,20 @@ ConnectAttempt::onHandshake(error_code ec) void ConnectAttempt::onWrite(error_code ec) { - ioPending_ = false; + cancelTimer(); + + if (!socket_.is_open()) + return; if (ec) { if (ec == boost::asio::error::operation_aborted) - { - tryAsyncShutdown(); return; - } fail("onWrite", ec); return; } - if (shutdown_) - { - tryAsyncShutdown(); - return; - } - - ioPending_ = true; - - setTimer(ConnectionStep::HttpRead); - boost::beast::http::async_read( stream_, readBuf_, @@ -501,21 +323,24 @@ void ConnectAttempt::onRead(error_code ec) { cancelTimer(); - ioPending_ = false; - currentStep_ = ConnectionStep::Complete; + + if (!socket_.is_open()) + return; if (ec) { + if (ec == boost::asio::error::operation_aborted) + return; + if (ec == boost::asio::error::eof) { JLOG(journal_.debug()) << "EOF"; - shutdown(); - return; - } - - if (ec == boost::asio::error::operation_aborted) - { - tryAsyncShutdown(); + setTimer(); + stream_.async_shutdown( + boost::asio::bind_executor( + strand_, + std::bind( + &ConnectAttempt::onShutdown, shared_from_this(), std::placeholders::_1))); return; } @@ -523,13 +348,25 @@ ConnectAttempt::onRead(error_code ec) return; } - if (shutdown_) + processResponse(); +} + +void +ConnectAttempt::onShutdown(error_code ec) +{ + cancelTimer(); + if (!ec) { - tryAsyncShutdown(); + close(); return; } - processResponse(); + if (ec != boost::asio::error::eof) + { + fail("onShutdown", ec); + return; + } + close(); } //-------------------------------------------------------------------------- @@ -537,71 +374,47 @@ ConnectAttempt::onRead(error_code ec) void ConnectAttempt::processResponse() { - if (!OverlayImpl::isPeerUpgrade(response_)) + if (response_.result() == boost::beast::http::status::service_unavailable) { - // 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() << ")"; - shutdown(); - return; - } - - // 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())); + 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()) { - responseBody.append( - static_cast(buffer.data()), boost::asio::buffer_size(buffer)); + s.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) + auto const success = r.parse(s, json); + if (success) { - JLOG(journal_.warn()) << "processResponse: " << remoteEndpoint_ - << " failed to upgrade to peer protocol: " << response_.result() - << " (" << response_.reason() << ")"; - - shutdown(); - return; + 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 = parseEndpoint(v.asString(), ec); + if (!ec) + eps.push_back(ep); + } + } + overlay_.peerFinder().onRedirects(remoteEndpoint_, eps); + } + } } + } - json::Value const& peerIps = json["peer-ips"]; - if (!peerIps.isArray()) - { - fail("processResponse: invalid peer-ips format"); - return; - } - - // 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 = parseEndpoint(ipValue.asString(), ec); - if (!ec) - redirectEndpoints.push_back(endpoint); - } - - // Notify PeerFinder about the redirect redirectEndpoints may be empty - overlay_.peerFinder().onRedirects(remoteEndpoint_, redirectEndpoints); - - fail("processResponse: failed to connect to peer: redirected"); + if (!OverlayImpl::isPeerUpgrade(response_)) + { + JLOG(journal_.info()) << "Unable to upgrade to peer protocol: " << response_.result() + << " (" << response_.reason() << ")"; + close(); return; } @@ -625,8 +438,8 @@ ConnectAttempt::processResponse() auto const sharedValue = makeSharedValue(*streamPtr_, journal_); if (!sharedValue) { - shutdown(); - return; // makeSharedValue logs + close(); // makeSharedValue logs + return; } try @@ -641,30 +454,21 @@ ConnectAttempt::processResponse() usage_.setPublicKey(publicKey); - JLOG(journal_.debug()) << "Protocol: " << to_string(*negotiatedProtocol); JLOG(journal_.info()) << "Public Key: " << toBase58(TokenType::NodePublic, publicKey); + JLOG(journal_.debug()) << "Protocol: " << to_string(*negotiatedProtocol); + auto const member = app_.getCluster().member(publicKey); if (member) { JLOG(journal_.info()) << "Cluster name: " << *member; } - auto const result = overlay_.peerFinder().activate(slot_, publicKey, member.has_value()); + auto const result = + overlay_.peerFinder().activate(slot_, publicKey, static_cast(member)); if (result != PeerFinder::Result::Success) { - std::stringstream ss; - ss << "Outbound Connect Attempt " << remoteEndpoint_ << " " << to_string(result); - fail(ss.str()); - return; - } - - if (!socket_.is_open()) - return; - - if (shutdown_) - { - tryAsyncShutdown(); + fail("Outbound " + std::string(to_string(result))); return; } diff --git a/src/xrpld/overlay/detail/ConnectAttempt.h b/src/xrpld/overlay/detail/ConnectAttempt.h index 949e6accbf..aba224d5c7 100644 --- a/src/xrpld/overlay/detail/ConnectAttempt.h +++ b/src/xrpld/overlay/detail/ConnectAttempt.h @@ -1,42 +1,13 @@ #pragma once +#include #include -#include +#include namespace xrpl { -/** - * @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. - * - */ +/** Manages an outbound connection attempt. */ class ConnectAttempt : public OverlayImpl::Child, public std::enable_shared_from_this { @@ -45,95 +16,29 @@ private: 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 kConnectTimeout{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 kTcpConnect{8}; - // SSL handshake timeout - static constexpr std::chrono::seconds kTlsHandshake{8}; - // HTTP write timeout - static constexpr std::chrono::seconds kHttpWrite{3}; - // HTTP read timeout - static constexpr std::chrono::seconds kHttpRead{3}; - // SSL shutdown timeout - static constexpr std::chrono::seconds kTlsShutdown{2}; - }; - - // Core application and networking components Application& app_; - Peer::id_t const id_; + std::uint32_t const id_; beast::WrappedSink sink_; beast::Journal const journal_; endpoint_type remoteEndpoint_; Resource::Consumer usage_; - boost::asio::strand strand_; boost::asio::basic_waitable_timer timer_; - boost::asio::basic_waitable_timer stepTimer_; - - std::unique_ptr streamPtr_; // SSL stream (owned) + std::unique_ptr streamPtr_; socket_type& socket_; stream_type& stream_; boost::beast::multi_buffer readBuf_; - 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 ioContext ASIO I/O context for async operations - * @param remoteEndpoint 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& ioContext, @@ -147,111 +52,38 @@ public: ~ConnectAttempt() override; - /** - * @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 - 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. - */ + close(); + void + fail(std::string const& reason); + void + fail(std::string const& name, error_code ec); + void + setTimer(); 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); // TCP connection completion handler + onConnect(error_code ec); void - onHandshake(error_code ec); // TLS handshake completion handler + onHandshake(error_code ec); void - onWrite(error_code ec); // HTTP write completion handler + onWrite(error_code ec); void - onRead(error_code ec); // HTTP read completion handler - - // Error and cleanup handlers + onRead(error_code ec); void - 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. - */ + onShutdown(error_code ec); 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 parseEndpoint(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 23e1785f6b..325f8ba038 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -74,7 +74,7 @@ #include #include #include -#include +#include #include @@ -110,9 +110,6 @@ constexpr std::chrono::milliseconds kPeerHighLatency{300}; /** How often we PING the peer to check for latency and sendq probe */ constexpr std::chrono::seconds kPeerTimerInterval{60}; -/** The timeout for a shutdown timer */ -constexpr std::chrono::seconds kShutdownTimerInterval{5}; - } // namespace // TODO: Remove this exclusion once unit tests are added after the hotfix @@ -270,13 +267,7 @@ PeerImp::stop() 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(); + close(); } //------------------------------------------------------------------------------ @@ -289,17 +280,13 @@ PeerImp::send(std::shared_ptr const& m) post(strand_, std::bind(&PeerImp::send, shared_from_this(), m)); return; } - + if (gracefulClose_) + return; + if (detaching_) + return; if (!socket_.is_open()) return; - // we are in progress of closing the connection - if (shutdown_) - { - tryAsyncShutdown(); - return; - } - auto validator = m->getValidatorKey(); if (validator && !squelch_.expireSquelch(*validator)) { @@ -338,7 +325,6 @@ PeerImp::send(std::shared_ptr const& m) if (sendqSize != 0) return; - writePending_ = true; boost::asio::async_write( stream_, boost::asio::buffer(sendQueue_.front()->getBuffer(compressionEnabled_)), @@ -621,16 +607,20 @@ PeerImp::hasRange(std::uint32_t uMin, std::uint32_t uMax) //------------------------------------------------------------------------------ void -PeerImp::fail(std::string const& name, error_code ec) +PeerImp::close() { - XRPL_ASSERT(strand_.running_in_this_thread(), "xrpl::PeerImp::fail : strand in this thread"); - + XRPL_ASSERT(strand_.running_in_this_thread(), "xrpl::PeerImp::close : strand in this thread"); if (!socket_.is_open()) return; - JLOG(journal_.warn()) << name << ": " << ec.message(); + detaching_ = true; // DEPRECATED - shutdown(); + cancelTimer(); + error_code ec; + socket_.close(ec); // NOLINT(bugprone-unused-return-value) + + overlay_.incPeerDisconnect(); + JLOG((inbound_ ? journal_.debug() : journal_.info())) << "close: Closed"; } void @@ -644,123 +634,71 @@ PeerImp::fail(std::string const& reason) (void (Peer::*)(std::string const&))&PeerImp::fail, shared_from_this(), reason)); return; } - - if (!socket_.is_open()) - return; - - // Call to name() locks, log only if the message will be outputted - if (journal_.active(beast::Severity::Warning)) + if (journal_.active(beast::Severity::Warning) && socket_.is_open()) { std::string const n = name(); JLOG(journal_.warn()) << n << " failed: " << reason; } - - shutdown(); + close(); } void -PeerImp::tryAsyncShutdown() +PeerImp::fail(std::string const& name, error_code ec) { - XRPL_ASSERT( - strand_.running_in_this_thread(), - "xrpl::PeerImp::tryAsyncShutdown : strand in this thread"); - - if (!shutdown_ || shutdownStarted_) + XRPL_ASSERT(strand_.running_in_this_thread(), "xrpl::PeerImp::fail : strand in this thread"); + if (!socket_.is_open()) return; - if (readPending_ || writePending_) - return; - - shutdownStarted_ = true; - - setTimer(kShutdownTimerInterval); - - // gracefully shutdown the SSL socket, performing a shutdown handshake - stream_.async_shutdown(bind_executor( - strand_, std::bind(&PeerImp::onShutdown, shared_from_this(), std::placeholders::_1))); -} - -void -PeerImp::shutdown() -{ - XRPL_ASSERT(strand_.running_in_this_thread(), "xrpl::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 const 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(); - } - } + JLOG(journal_.warn()) << name << ": " << ec.message(); close(); } void -PeerImp::close() +PeerImp::gracefulClose() { - XRPL_ASSERT(strand_.running_in_this_thread(), "xrpl::PeerImp::close : strand in this thread"); - - if (!socket_.is_open()) + XRPL_ASSERT( + strand_.running_in_this_thread(), "xrpl::PeerImp::gracefulClose : strand in this thread"); + XRPL_ASSERT(socket_.is_open(), "xrpl::PeerImp::gracefulClose : socket is open"); + XRPL_ASSERT(!gracefulClose_, "xrpl::PeerImp::gracefulClose : socket is not closing"); + gracefulClose_ = true; + if (!sendQueue_.empty()) return; - - cancelTimer(); - - error_code ec; - socket_.close(ec); // NOLINT(bugprone-unused-return-value) - - 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"; + setTimer(); + stream_.async_shutdown(bind_executor( + strand_, std::bind(&PeerImp::onShutdown, shared_from_this(), std::placeholders::_1))); } -//------------------------------------------------------------------------------ - void -PeerImp::setTimer(std::chrono::seconds interval) +PeerImp::setTimer() { try { - timer_.expires_after(interval); + timer_.expires_after(kPeerTimerInterval); } - catch (std::exception const& ex) + catch (boost::system::system_error const& e) { - JLOG(journal_.error()) << "setTimer: " << ex.what(); - shutdown(); + JLOG(journal_.error()) << "setTimer: " << e.code(); return; } - 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() noexcept +{ + try + { + timer_.cancel(); + } + catch (boost::system::system_error const&) // NOLINT(bugprone-empty-catch) + { + // ignored + } +} + //------------------------------------------------------------------------------ std::string @@ -774,14 +712,11 @@ PeerImp::makePrefix(std::string const& fingerprint) void PeerImp::onTimer(error_code const& ec) { - XRPL_ASSERT(strand_.running_in_this_thread(), "xrpl::PeerImp::onTimer : strand in this thread"); - if (!socket_.is_open()) return; if (ec) { - // do not initiate shutdown, timers are frequently cancelled if (ec == boost::asio::error::operation_aborted) return; @@ -791,15 +726,6 @@ PeerImp::onTimer(error_code const& ec) return; } - // the timer expired before the shutdown completed - // force close the connection - if (shutdown_) - { - JLOG(journal_.debug()) << "onTimer: shutdown timer expired"; - close(); - return; - } - if (largeSendq_++ >= Tuning::kSendqIntervals) { fail("Large send queue"); @@ -840,20 +766,32 @@ PeerImp::onTimer(error_code const& ec) send(std::make_shared(message, protocol::mtPING)); - setTimer(kPeerTimerInterval); + setTimer(); } void -PeerImp::cancelTimer() noexcept +PeerImp::onShutdown(error_code ec) { - try + cancelTimer(); + + if (ec) { - timer_.cancel(); - } - catch (std::exception const& ex) - { - JLOG(journal_.error()) << "cancelTimer: " << ex.what(); + // - 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 const 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(); } //------------------------------------------------------------------------------ @@ -862,15 +800,6 @@ PeerImp::doAccept() { XRPL_ASSERT(readBuffer_.size() == 0, "xrpl::PeerImp::doAccept : empty read buffer"); - JLOG(journal_.debug()) << "doAccept"; - - // a shutdown was initiated before the handshake, there is nothing to do - if (shutdown_) - { - tryAsyncShutdown(); - return; - } - auto const sharedValue = makeSharedValue(*streamPtr_, journal_); // This shouldn't fail since we already computed @@ -881,7 +810,7 @@ PeerImp::doAccept() return; } - JLOG(journal_.debug()) << "Protocol: " << to_string(protocol_); + JLOG(journal_.info()) << "Protocol: " << to_string(protocol_); if (auto member = app_.getCluster().member(publicKey_)) { @@ -921,16 +850,15 @@ PeerImp::doAccept() error_code ec, std::size_t bytesTransferred) { if (!socket_.is_open()) return; - if (ec == boost::asio::error::operation_aborted) - { - tryAsyncShutdown(); - return; - } if (ec) { + if (ec == boost::asio::error::operation_aborted) + return; + fail("onWriteResponse", ec); return; } + if (writeBuffer->size() == bytesTransferred) { doProtocolStart(); @@ -961,13 +889,6 @@ PeerImp::domain() const void PeerImp::doProtocolStart() { - // a shutdown was initiated before the handshare, there is nothing to do - if (shutdown_) - { - tryAsyncShutdown(); - return; - } - onReadMessage(error_code(), 0); // Send all the validator lists that have been loaded @@ -999,45 +920,31 @@ PeerImp::doProtocolStart() if (auto m = overlay_.getManifestsMessage()) send(m); - setTimer(kPeerTimerInterval); + setTimer(); } // Called repeatedly with protocol message data void PeerImp::onReadMessage(error_code ec, std::size_t bytesTransferred) { - XRPL_ASSERT( - strand_.running_in_this_thread(), "xrpl::PeerImp::onReadMessage : strand in this thread"); - - readPending_ = false; - if (!socket_.is_open()) return; if (ec) { + if (ec == boost::asio::error::operation_aborted) + return; + if (ec == boost::asio::error::eof) { - JLOG(journal_.debug()) << "EOF"; - shutdown(); - return; - } - - if (ec == boost::asio::error::operation_aborted) - { - tryAsyncShutdown(); + JLOG(journal_.info()) << "EOF"; + gracefulClose(); return; } fail("onReadMessage", ec); return; } - // we started shutdown, no reason to process further data - if (shutdown_) - { - tryAsyncShutdown(); - return; - } if (auto stream = journal_.trace()) { @@ -1062,34 +969,23 @@ PeerImp::onReadMessage(error_code ec, std::size_t bytesTransferred) 350ms, journal_); - if (!socket_.is_open()) - return; - - // the error_code is produced by invokeProtocolMessage - // it could be due to a bad message if (ec) { fail("onReadMessage", ec); return; } + if (!socket_.is_open()) + return; + + if (gracefulClose_) + return; + if (bytesConsumed == 0) break; - readBuffer_.consume(bytesConsumed); } - // check if a shutdown was initiated while processing messages - if (shutdown_) - { - tryAsyncShutdown(); - return; - } - - readPending_ = true; - - XRPL_ASSERT(!shutdownStarted_, "xrpl::PeerImp::onReadMessage : shutdown started"); - // Timeout on writes only stream_.async_read_some( readBuffer_.prepare(std::max(Tuning::kReadBufferBytes, hint)), @@ -1105,26 +1001,17 @@ PeerImp::onReadMessage(error_code ec, std::size_t bytesTransferred) void PeerImp::onWriteMessage(error_code ec, std::size_t bytesTransferred) { - XRPL_ASSERT( - strand_.running_in_this_thread(), "xrpl::PeerImp::onWriteMessage : strand in this thread"); - - writePending_ = false; - if (!socket_.is_open()) return; if (ec) { if (ec == boost::asio::error::operation_aborted) - { - tryAsyncShutdown(); return; - } fail("onWriteMessage", ec); return; } - if (auto stream = journal_.trace()) { stream << "onWriteMessage: " @@ -1135,18 +1022,8 @@ PeerImp::onWriteMessage(error_code ec, std::size_t bytesTransferred) XRPL_ASSERT(!sendQueue_.empty(), "xrpl::PeerImp::onWriteMessage : non-empty send buffer"); sendQueue_.pop(); - - if (shutdown_) - { - tryAsyncShutdown(); - return; - } - if (!sendQueue_.empty()) { - writePending_ = true; - XRPL_ASSERT(!shutdownStarted_, "xrpl::PeerImp::onWriteMessage : shutdown started"); - // Timeout on writes only boost::asio::async_write( stream_, @@ -1160,6 +1037,13 @@ PeerImp::onWriteMessage(error_code ec, std::size_t bytesTransferred) std::placeholders::_2))); return; } + + if (gracefulClose_) + { + stream_.async_shutdown(bind_executor( + strand_, std::bind(&PeerImp::onShutdown, shared_from_this(), std::placeholders::_1))); + return; + } } //------------------------------------------------------------------------------ diff --git a/src/xrpld/overlay/detail/PeerImp.h b/src/xrpld/overlay/detail/PeerImp.h index dca70d88bd..f5d87371be 100644 --- a/src/xrpld/overlay/detail/PeerImp.h +++ b/src/xrpld/overlay/detail/PeerImp.h @@ -30,68 +30,6 @@ namespace xrpl { 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 { public: @@ -121,8 +59,6 @@ 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 @@ -139,6 +75,7 @@ private: std::atomic tracking_; clock_type::time_point trackingTime_; + bool detaching_ = false; // Node public key of peer. PublicKey const publicKey_; std::string name_; @@ -216,19 +153,7 @@ private: http_response_type response_; boost::beast::http::fields const& headers_; std::queue> sendQueue_; - - // 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; - + bool gracefulClose_ = false; int largeSendq_ = 0; std::unique_ptr loadEvent_; // The highest sequence of each PublisherList that has @@ -476,6 +401,9 @@ public: bool isHighLatency() const override; + void + fail(std::string const& reason); + bool compressionEnabled() const override { @@ -489,129 +417,32 @@ public: } private: - /** - * @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 - 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 - 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 - 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); + fail(std::string const& name, error_code ec); - /** - * @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); + gracefulClose(); + + void + setTimer(); - /** - * @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(std::string const& fingerprint); + // 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 79308705c5f90440b81a3a371807a40d357df95e Mon Sep 17 00:00:00 2001 From: Bart Date: Thu, 21 May 2026 18:50:59 +0100 Subject: [PATCH 013/135] release: Bump version to 3.2.0-b6 (#7311) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- 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 820936a22d..3d4fff9f04 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -23,7 +23,7 @@ namespace { //------------------------------------------------------------------------------ // clang-format off // NOLINTNEXTLINE(readability-identifier-naming) -char const* const versionString = "3.3.0-b0" +char const* const versionString = "3.2.0-b6" // clang-format on ; From 1a98182e23ec17b508fa6a2de6f4aef0600218c9 Mon Sep 17 00:00:00 2001 From: Bart Date: Thu, 21 May 2026 18:52:41 +0100 Subject: [PATCH 014/135] refactor: Remove dead `fetchBatch` code (#7309) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- include/xrpl/nodestore/Backend.h | 4 --- .../xrpl/nodestore/detail/DatabaseNodeImp.h | 3 -- src/libxrpl/nodestore/DatabaseNodeImp.cpp | 34 ------------------- .../nodestore/backend/MemoryFactory.cpp | 23 ------------- src/libxrpl/nodestore/backend/NuDBFactory.cpp | 23 ------------- src/libxrpl/nodestore/backend/NullFactory.cpp | 8 ----- .../nodestore/backend/RocksDBFactory.cpp | 24 ------------- 7 files changed, 119 deletions(-) diff --git a/include/xrpl/nodestore/Backend.h b/include/xrpl/nodestore/Backend.h index 124cd99db5..0061890237 100644 --- a/include/xrpl/nodestore/Backend.h +++ b/include/xrpl/nodestore/Backend.h @@ -83,10 +83,6 @@ public: virtual Status fetch(uint256 const& hash, std::shared_ptr* pObject) = 0; - /** Fetch a batch synchronously. */ - virtual std::pair>, Status> - fetchBatch(std::vector const& hashes) = 0; - /** Store a single object. Depending on the implementation this may happen immediately or deferred using a scheduled task. diff --git a/include/xrpl/nodestore/detail/DatabaseNodeImp.h b/include/xrpl/nodestore/detail/DatabaseNodeImp.h index add66c51a7..dd94b27075 100644 --- a/include/xrpl/nodestore/detail/DatabaseNodeImp.h +++ b/include/xrpl/nodestore/detail/DatabaseNodeImp.h @@ -67,9 +67,6 @@ public: backend_->sync(); } - std::vector> - fetchBatch(std::vector const& hashes); - void asyncFetch( uint256 const& hash, diff --git a/src/libxrpl/nodestore/DatabaseNodeImp.cpp b/src/libxrpl/nodestore/DatabaseNodeImp.cpp index ef84862de6..a51c34079b 100644 --- a/src/libxrpl/nodestore/DatabaseNodeImp.cpp +++ b/src/libxrpl/nodestore/DatabaseNodeImp.cpp @@ -4,21 +4,16 @@ #include #include #include -#include -#include #include #include #include #include -#include -#include #include #include #include #include #include -#include namespace xrpl::NodeStore { @@ -81,33 +76,4 @@ DatabaseNodeImp::fetchNodeObject( return nodeObject; } -std::vector> -DatabaseNodeImp::fetchBatch(std::vector const& hashes) -{ - using namespace std::chrono; - auto const before = steady_clock::now(); - - // Get the node objects that match the hashes from the backend. To protect - // against the backends returning fewer or more results than expected, the - // container is resized to the number of hashes. - auto results = backend_->fetchBatch(hashes).first; - XRPL_ASSERT( - results.size() == hashes.size() || results.empty(), - "number of output objects either matches number of input hashes or is empty"); - results.resize(hashes.size()); - for (size_t i = 0; i < results.size(); ++i) - { - if (!results[i]) - { - JLOG(j_.error()) << "fetchBatch - " - << "record not found in db. hash = " << strHex(hashes[i]); - } - } - - auto fetchDurationUs = - std::chrono::duration_cast(steady_clock::now() - before).count(); - updateFetchMetrics(hashes.size(), 0, fetchDurationUs); - return results; -} - } // namespace xrpl::NodeStore diff --git a/src/libxrpl/nodestore/backend/MemoryFactory.cpp b/src/libxrpl/nodestore/backend/MemoryFactory.cpp index 621ad1357c..5bdf8e65b5 100644 --- a/src/libxrpl/nodestore/backend/MemoryFactory.cpp +++ b/src/libxrpl/nodestore/backend/MemoryFactory.cpp @@ -22,7 +22,6 @@ #include #include #include -#include namespace xrpl::NodeStore { @@ -146,28 +145,6 @@ public: return Status::Ok; } - std::pair>, Status> - fetchBatch(std::vector const& hashes) override - { - std::vector> results; - results.reserve(hashes.size()); - for (auto const& h : hashes) - { - std::shared_ptr nObj; - Status const status = fetch(h, &nObj); - if (status != Status::Ok) - { - results.push_back({}); - } - else - { - results.push_back(nObj); - } - } - - return {results, Status::Ok}; - } - void store(std::shared_ptr const& object) override { diff --git a/src/libxrpl/nodestore/backend/NuDBFactory.cpp b/src/libxrpl/nodestore/backend/NuDBFactory.cpp index d026dc254c..abf09e871d 100644 --- a/src/libxrpl/nodestore/backend/NuDBFactory.cpp +++ b/src/libxrpl/nodestore/backend/NuDBFactory.cpp @@ -42,7 +42,6 @@ #include #include #include -#include namespace xrpl::NodeStore { @@ -232,28 +231,6 @@ public: return status; } - std::pair>, Status> - fetchBatch(std::vector const& hashes) override - { - std::vector> results; - results.reserve(hashes.size()); - for (auto const& h : hashes) - { - std::shared_ptr nObj; - Status const status = fetch(h, &nObj); - if (status != Status::Ok) - { - results.push_back({}); - } - else - { - results.push_back(nObj); - } - } - - return {results, Status::Ok}; - } - void doInsert(std::shared_ptr const& no) { diff --git a/src/libxrpl/nodestore/backend/NullFactory.cpp b/src/libxrpl/nodestore/backend/NullFactory.cpp index ae33b7daa2..36b8139984 100644 --- a/src/libxrpl/nodestore/backend/NullFactory.cpp +++ b/src/libxrpl/nodestore/backend/NullFactory.cpp @@ -12,8 +12,6 @@ #include #include #include -#include -#include namespace xrpl::NodeStore { @@ -52,12 +50,6 @@ public: return Status::NotFound; } - std::pair>, Status> - fetchBatch(std::vector const& hashes) override - { - return {}; - } - void store(std::shared_ptr const& object) override { diff --git a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp index c1baf6aaaa..d2c193888c 100644 --- a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp +++ b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp @@ -29,8 +29,6 @@ #include #include #include -#include -#include #if XRPL_ROCKSDB_AVAILABLE #include @@ -330,28 +328,6 @@ public: return status; } - std::pair>, Status> - fetchBatch(std::vector const& hashes) override - { - std::vector> results; - results.reserve(hashes.size()); - for (auto const& h : hashes) - { - std::shared_ptr nObj; - Status const status = fetch(h, &nObj); - if (status != Status::Ok) - { - results.push_back({}); - } - else - { - results.push_back(nObj); - } - } - - return {results, Status::Ok}; - } - void store(std::shared_ptr const& object) override { From 3547a9335f148fa85c0e23831612fb3f872e9b04 Mon Sep 17 00:00:00 2001 From: Gregory Tsipenyuk Date: Thu, 21 May 2026 14:29:53 -0400 Subject: [PATCH 015/135] fix: Add assorted MPT/DEX fixes (#7040) Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com> Co-authored-by: Shawn Xie <35279399+shawnxie999@users.noreply.github.com> --- include/xrpl/ledger/helpers/MPTokenHelpers.h | 22 +- include/xrpl/ledger/helpers/TokenHelpers.h | 6 + include/xrpl/tx/invariants/InvariantCheck.h | 3 +- include/xrpl/tx/invariants/MPTInvariant.h | 38 ++ src/libxrpl/ledger/helpers/MPTokenHelpers.cpp | 116 +---- src/libxrpl/ledger/helpers/TokenHelpers.cpp | 24 + src/libxrpl/protocol/STPathSet.cpp | 10 +- src/libxrpl/tx/invariants/AMMInvariant.cpp | 33 +- src/libxrpl/tx/invariants/InvariantCheck.cpp | 2 +- src/libxrpl/tx/invariants/MPTInvariant.cpp | 150 +++++- src/libxrpl/tx/paths/BookStep.cpp | 35 +- src/libxrpl/tx/paths/MPTEndpointStep.cpp | 12 +- .../tx/transactors/check/CheckCash.cpp | 5 +- .../tx/transactors/check/CheckCreate.cpp | 20 +- src/libxrpl/tx/transactors/dex/AMMCreate.cpp | 34 +- src/libxrpl/tx/transactors/dex/AMMDeposit.cpp | 29 +- .../tx/transactors/dex/AMMWithdraw.cpp | 29 +- .../tx/transactors/dex/OfferCreate.cpp | 20 +- .../tx/transactors/token/MPTokenAuthorize.cpp | 5 +- src/test/app/AMMExtendedMPT_test.cpp | 6 +- src/test/app/AMMMPT_test.cpp | 90 ++-- src/test/app/CheckMPT_test.cpp | 8 +- src/test/app/ClawbackMPT_test.cpp | 435 +++++++++++++++++ src/test/app/Invariants_test.cpp | 184 +++++++ src/test/app/MPToken_test.cpp | 458 ++++++++++++------ src/test/app/OfferMPT_test.cpp | 184 ++++++- src/test/app/Vault_test.cpp | 81 +++- 27 files changed, 1618 insertions(+), 421 deletions(-) create mode 100644 src/test/app/ClawbackMPT_test.cpp diff --git a/include/xrpl/ledger/helpers/MPTokenHelpers.h b/include/xrpl/ledger/helpers/MPTokenHelpers.h index 491bd1933e..c709badab8 100644 --- a/include/xrpl/ledger/helpers/MPTokenHelpers.h +++ b/include/xrpl/ledger/helpers/MPTokenHelpers.h @@ -171,6 +171,15 @@ canTransfer( [[nodiscard]] TER canTrade(ReadView const& view, Asset const& asset, std::uint8_t depth = 0); +/** Convenience to combine canTrade/Transfer. Returns tesSUCCESS if Asset is Issue. + */ +[[nodiscard]] TER +canMPTTradeAndTransfer( + ReadView const& v, + Asset const& asset, + AccountID const& from, + AccountID const& to); + //------------------------------------------------------------------------------ // // Empty holding operations (MPT-specific) @@ -277,17 +286,4 @@ issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue); void issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount); -//------------------------------------------------------------------------------ -// -// MPT DEX -// -//------------------------------------------------------------------------------ - -/* Return true if a transaction is allowed for the specified MPT/account. The - * function checks MPTokenIssuance and MPToken objects flags to determine if the - * transaction is allowed. - */ -TER -checkMPTTxAllowed(ReadView const& v, TxType tx, Asset const& asset, AccountID const& accountID); - } // namespace xrpl diff --git a/include/xrpl/ledger/helpers/TokenHelpers.h b/include/xrpl/ledger/helpers/TokenHelpers.h index b47286a5d5..f736e51d28 100644 --- a/include/xrpl/ledger/helpers/TokenHelpers.h +++ b/include/xrpl/ledger/helpers/TokenHelpers.h @@ -63,9 +63,15 @@ enum class AuthType { StrongAuth, WeakAuth, Legacy }; [[nodiscard]] bool isGlobalFrozen(ReadView const& view, Asset const& asset); +[[nodiscard]] TER +checkGlobalFrozen(ReadView const& view, Asset const& asset); + [[nodiscard]] bool isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset); +[[nodiscard]] TER +checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset); + /** * isFrozen check is recursive for MPT shares in a vault, descending to * assets in the vault, up to maxAssetCheckDepth recursion depth. This is diff --git a/include/xrpl/tx/invariants/InvariantCheck.h b/include/xrpl/tx/invariants/InvariantCheck.h index a58ac0df50..5996039bf0 100644 --- a/include/xrpl/tx/invariants/InvariantCheck.h +++ b/include/xrpl/tx/invariants/InvariantCheck.h @@ -401,7 +401,8 @@ using InvariantChecks = std::tuple< ValidLoanBroker, ValidLoan, ValidVault, - ValidMPTPayment>; + ValidMPTPayment, + ValidMPTTransfer>; /** * @brief get a tuple of all invariant checks diff --git a/include/xrpl/tx/invariants/MPTInvariant.h b/include/xrpl/tx/invariants/MPTInvariant.h index f2b0a18131..becb752126 100644 --- a/include/xrpl/tx/invariants/MPTInvariant.h +++ b/include/xrpl/tx/invariants/MPTInvariant.h @@ -71,4 +71,42 @@ public: finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); }; +class ValidMPTTransfer +{ + struct Value + { + std::optional amtBefore; + std::optional amtAfter; + }; + // MPTID: {holder: Value} + hash_map> amount_; + // Deleted MPToken + // MPToken key: true if MPTAuthorized is set + hash_map deletedAuthorized_; + +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&); + +private: + /** + * @brief Check whether a holder is authorized to send or receive an MPToken. + * + * Deleted MPToken SLEs are no longer present in the view by the time + * finalize() runs, so their authorization state is captured during + * visitEntry() and stored in deletedAuthorized_. For deleted MPTokens, + * returns true if reqAuth is false or lsfMPTAuthorized was set at deletion. + * For existing MPTokens, returns the result of requireAuth() + */ + [[nodiscard]] bool + isAuthorized( + ReadView const& view, + MPTID const& mptid, + AccountID const& holder, + bool requireAuth) const; +}; + } // namespace xrpl diff --git a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp index 542a5b60ed..387116d820 100644 --- a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include @@ -383,10 +382,11 @@ requireAuth( // belong to someone who is explicitly authorized e.g. a vault owner. } - if (featureSAVEnabled) + bool const featureMPTV2Enabled = view.rules().enabled(featureMPTokensV2); + if (featureSAVEnabled || featureMPTV2Enabled) { - // Implicitly authorize Vault and LoanBroker pseudo-accounts - if (isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID})) + // Implicitly authorize Vault, LoanBroker, and AMM pseudo-accounts + if (isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID, &sfAMMID})) return tesSUCCESS; } @@ -618,6 +618,22 @@ canTrade(ReadView const& view, Asset const& asset, std::uint8_t depth) }); } +TER +canMPTTradeAndTransfer( + ReadView const& view, + Asset const& asset, + AccountID const& from, + AccountID const& to) +{ + if (!asset.holds()) + return tesSUCCESS; + + if (auto const ter = canTrade(view, asset); !isTesSuccess(ter)) + return ter; + + return canTransfer(view, asset, from, to); +} + TER lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount, beast::Journal j) { @@ -985,96 +1001,4 @@ issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amo view.issuerSelfDebitHookMPT(issue, amount, available); } -static TER -checkMPTAllowed( - ReadView const& view, - TxType txType, - Asset const& asset, - AccountID const& accountID, - std::uint8_t depth = 0) -{ - if (!asset.holds()) - return tesSUCCESS; - - auto const& issuanceID = asset.get().getMptID(); - auto const validTx = txType == ttAMM_CREATE || txType == ttAMM_DEPOSIT || - txType == ttAMM_WITHDRAW || txType == ttOFFER_CREATE || txType == ttCHECK_CREATE || - txType == ttCHECK_CASH || txType == ttPAYMENT; - XRPL_ASSERT(validTx, "xrpl::checkMPTAllowed : all MPT tx or DEX"); - if (!validTx) - return tefINTERNAL; // LCOV_EXCL_LINE - - auto const& issuer = asset.getIssuer(); - if (!view.exists(keylet::account(issuer))) - return tecNO_ISSUER; // LCOV_EXCL_LINE - - auto const issuanceKey = keylet::mptIssuance(issuanceID); - auto const issuanceSle = view.read(issuanceKey); - if (!issuanceSle) - return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE - - if (issuanceSle->isFlag(lsfMPTLocked)) - return tecLOCKED; // LCOV_EXCL_LINE - // Offer crossing and Payment - if (!issuanceSle->isFlag(lsfMPTCanTrade)) - return tecNO_PERMISSION; - - if (accountID != issuer) - { - if (!issuanceSle->isFlag(lsfMPTCanTransfer)) - return tecNO_PERMISSION; - - auto const mptSle = view.read(keylet::mptoken(issuanceKey.key, accountID)); - // Allow to succeed since some tx create MPToken if it doesn't exist. - // Tx's have their own check for missing MPToken. - if (!mptSle) - return tesSUCCESS; - - if (mptSle->isFlag(lsfMPTLocked)) - return tecLOCKED; - - // Post-fixCleanup3_2_0: vault shares inherit the underlying - // asset's checks here too. Without this, a share could be - // placed on the AMM, in an Offer, or in a Check even after - // the issuer has restricted the underlying. Mirrors the - // canTransfer / canTrade inheritance for path-find-adjacent - // operations that don't go through canTransfer directly. - if (view.rules().enabled(fixCleanup3_2_0) && - issuanceSle->isFieldPresent(sfReferenceHolding)) - { - // Defensive depth bound on the inheritance recursion. - // Reachable only post-fixCleanup3_2_0 and unreachable in - // practice (vault-of-vault-shares forbidden at VaultCreate). - if (depth >= kMaxAssetCheckDepth) - { - // LCOV_EXCL_START - UNREACHABLE("xrpl::MPTokenHelpers::checkMPTAllowed : reached asset check depth"); - return tecINTERNAL; - // LCOV_EXCL_STOP - } - auto const sleHolding = - view.read(keylet::unchecked(issuanceSle->getFieldH256(sfReferenceHolding))); - if (!sleHolding) - return tefINTERNAL; // LCOV_EXCL_LINE - - return checkMPTAllowed( - view, txType, assetOfHolding(*issuanceSle, *sleHolding), accountID, depth + 1); - } - } - - return tesSUCCESS; -} - -TER -checkMPTTxAllowed( - ReadView const& view, - TxType txType, - Asset const& asset, - AccountID const& accountID) -{ - // use isDEXAllowed for payment/offer crossing - XRPL_ASSERT(txType != ttPAYMENT, "xrpl::checkMPTTxAllowed : not payment"); - return checkMPTAllowed(view, txType, asset, accountID); -} - } // namespace xrpl diff --git a/src/libxrpl/ledger/helpers/TokenHelpers.cpp b/src/libxrpl/ledger/helpers/TokenHelpers.cpp index 71019761ed..7191456868 100644 --- a/src/libxrpl/ledger/helpers/TokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/TokenHelpers.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -55,6 +56,14 @@ isGlobalFrozen(ReadView const& view, Asset const& asset) [&](MPTIssue const& issue) { return isGlobalFrozen(view, issue); }); } +TER +checkGlobalFrozen(ReadView const& view, Asset const& asset) +{ + if (isGlobalFrozen(view, asset)) + return asset.holds() ? tecLOCKED : tecFROZEN; + return tesSUCCESS; +} + bool isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset) { @@ -62,6 +71,14 @@ isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& [&](auto const& issue) { return isIndividualFrozen(view, account, issue); }, asset.value()); } +TER +checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset) +{ + if (isIndividualFrozen(view, account, asset)) + return asset.holds() ? tecLOCKED : tecFROZEN; + return tesSUCCESS; +} + bool isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, std::uint8_t depth) { @@ -1105,6 +1122,13 @@ directSendNoFeeMPT( auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID); if (auto sle = view.peek(mptokenID)) { + if (view.rules().enabled(featureMPTokensV2)) + { + if ((*sle)[sfMPTAmount] > (std::numeric_limits::max() - amt)) + { + return tecINTERNAL; // LCOV_EXCL_LINE + } + } view.creditHookMPT(uSenderID, uReceiverID, saAmount, (*sle)[sfMPTAmount], available); (*sle)[sfMPTAmount] += amt; view.update(sle); diff --git a/src/libxrpl/protocol/STPathSet.cpp b/src/libxrpl/protocol/STPathSet.cpp index 35d5fd782b..cb70ac36fe 100644 --- a/src/libxrpl/protocol/STPathSet.cpp +++ b/src/libxrpl/protocol/STPathSet.cpp @@ -90,8 +90,12 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name) if (hasAccount) account = sit.get160(); - XRPL_ASSERT( - !(hasCurrency && hasMPT), "xrpl::STPathSet::STPathSet : not has Currency and MPT"); + if (hasCurrency && hasMPT) + { + JLOG(debugLog().error()) << "Bad path element MPT and Currency in pathset"; + Throw("bad path element: MPT and Currency"); + } + if (hasCurrency) asset = Currency::fromRaw(sit.get160()); @@ -101,7 +105,7 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name) if (hasIssuer) issuer = sit.get160(); - path.emplace_back(account, asset, issuer, hasCurrency); + path.emplace_back(account, asset, issuer, hasCurrency || hasMPT); } } } diff --git a/src/libxrpl/tx/invariants/AMMInvariant.cpp b/src/libxrpl/tx/invariants/AMMInvariant.cpp index e13684eca5..be2a803e93 100644 --- a/src/libxrpl/tx/invariants/AMMInvariant.cpp +++ b/src/libxrpl/tx/invariants/AMMInvariant.cpp @@ -45,7 +45,8 @@ ValidAMM::visitEntry( // AMM pool changed else if ( (type == ltRIPPLE_STATE && after->isFlag(lsfAMMNode)) || - (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID))) + (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)) || + (type == ltMPTOKEN && after->isFlag(lsfMPTAMM))) { ammPoolChanged_ = true; } @@ -85,9 +86,9 @@ ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const { // LPTokens and the pool can not change on vote // LCOV_EXCL_START - JLOG(j.error()) << "AMMVote invariant failed: " << lptAMMBalanceBefore_.value_or(STAmount{}) - << " " << lptAMMBalanceAfter_.value_or(STAmount{}) << " " - << ammPoolChanged_; + JLOG(j.error()) << "Invariant failed: AMMVote failed, " + << lptAMMBalanceBefore_.value_or(STAmount{}) << " " + << lptAMMBalanceAfter_.value_or(STAmount{}) << " " << ammPoolChanged_; if (enforce) return false; // LCOV_EXCL_STOP @@ -103,7 +104,7 @@ ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const { // The pool can not change on bid // LCOV_EXCL_START - JLOG(j.error()) << "AMMBid invariant failed: pool changed"; + JLOG(j.error()) << "Invariant failed: AMMBid failed, pool changed"; if (enforce) return false; // LCOV_EXCL_STOP @@ -114,7 +115,7 @@ ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const (*lptAMMBalanceAfter_ > *lptAMMBalanceBefore_ || *lptAMMBalanceAfter_ <= beast::kZero)) { // LCOV_EXCL_START - JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_ << " " + JLOG(j.error()) << "Invariant failed: AMMBid failed, " << *lptAMMBalanceBefore_ << " " << *lptAMMBalanceAfter_; if (enforce) return false; @@ -134,7 +135,7 @@ ValidAMM::finalizeCreate( if (!ammAccount_) { // LCOV_EXCL_START - JLOG(j.error()) << "AMMCreate invariant failed: AMM object is not created"; + JLOG(j.error()) << "Invariant failed: AMMCreate failed, AMM object is not created"; if (enforce) return false; // LCOV_EXCL_STOP @@ -157,8 +158,8 @@ ValidAMM::finalizeCreate( if (!validBalances(amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) || ammLPTokens(amount, amount2, lptAMMBalanceAfter_->get()) != *lptAMMBalanceAfter_) { - JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " " << amount2 << " " - << *lptAMMBalanceAfter_; + JLOG(j.error()) << "Invariant failed: AMMCreate failed, " << amount << " " << amount2 + << " " << *lptAMMBalanceAfter_; if (enforce) return false; } @@ -176,7 +177,7 @@ ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const // LCOV_EXCL_START std::string const msg = (isTesSuccess(res)) ? "AMM object is not deleted on tesSUCCESS" : "AMM object is changed on tecINCOMPLETE"; - JLOG(j.error()) << "AMMDelete invariant failed: " << msg; + JLOG(j.error()) << "Invariant failed: AMMDelete failed, " << msg; if (enforce) return false; // LCOV_EXCL_STOP @@ -191,7 +192,7 @@ ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const if (ammAccount_) { // LCOV_EXCL_START - JLOG(j.error()) << "AMM swap invariant failed: AMM object changed"; + JLOG(j.error()) << "Invariant failed: AMM swap failed, AMM object changed"; if (enforce) return false; // LCOV_EXCL_STOP @@ -232,10 +233,10 @@ ValidAMM::generalInvariant( }; if (!nonNegativeBalances || (!strongInvariantCheck && !weakInvariantCheck())) { - JLOG(j.error()) << "AMM " << tx.getTxnType() - << " invariant failed: " << tx.getHash(HashPrefix::TransactionId) << " " - << ammPoolChanged_ << " " << amount << " " << amount2 << " " - << poolProductMean << " " << lptAMMBalanceAfter_->getText() << " " + JLOG(j.error()) << "Invariant failed: AMM " << tx.getTxnType() << " " + << tx.getHash(HashPrefix::TransactionId) << " " << ammPoolChanged_ << " " + << amount << " " << amount2 << " " << poolProductMean << " " + << lptAMMBalanceAfter_->getText() << " " << ((*lptAMMBalanceAfter_ == beast::kZero) ? Number{1} : ((*lptAMMBalanceAfter_ - poolProductMean) / poolProductMean)); @@ -256,7 +257,7 @@ ValidAMM::finalizeDeposit( if (!ammAccount_) { // LCOV_EXCL_START - JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted"; + JLOG(j.error()) << "Invariant failed: AMMDeposit failed, AMM object is deleted"; if (enforce) return false; // LCOV_EXCL_STOP diff --git a/src/libxrpl/tx/invariants/InvariantCheck.cpp b/src/libxrpl/tx/invariants/InvariantCheck.cpp index 2019e378e2..3e51b6f877 100644 --- a/src/libxrpl/tx/invariants/InvariantCheck.cpp +++ b/src/libxrpl/tx/invariants/InvariantCheck.cpp @@ -840,7 +840,7 @@ ValidClawback::finalize( [&](MPTIssue const& issue) { return accountHolds( view, - issuer, + holder, issue, FreezeHandling::IgnoreFreeze, AuthHandling::IgnoreAuth, diff --git a/src/libxrpl/tx/invariants/MPTInvariant.cpp b/src/libxrpl/tx/invariants/MPTInvariant.cpp index ecba0b6227..635af25b61 100644 --- a/src/libxrpl/tx/invariants/MPTInvariant.cpp +++ b/src/libxrpl/tx/invariants/MPTInvariant.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -442,11 +443,11 @@ ValidMPTPayment::finalize( { if (isTesSuccess(result)) { - bool const enforce = view.rules().enabled(featureMPTokensV2); + bool const invariantPasses = !view.rules().enabled(featureMPTokensV2); if (overflow_) { JLOG(j.fatal()) << "Invariant failed: OutstandingAmount overflow"; - return !enforce; + return invariantPasses; } auto const signedMax = static_cast(kMaxMpTokenAmount); @@ -464,7 +465,7 @@ ValidMPTPayment::finalize( JLOG(j.fatal()) << "Invariant failed: invalid OutstandingAmount balance " << data.outstanding[kIBefore] << " " << data.outstanding[kIAfter] << " " << data.mptAmount; - return !enforce; + return invariantPasses; } } } @@ -472,4 +473,147 @@ ValidMPTPayment::finalize( return true; } +void +ValidMPTTransfer::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + // Record the before/after MPTAmount for each (issuanceID, account) pair + // so finalize() can determine whether a transfer actually occurred. + auto update = [&](SLE const& sle, bool isBefore) { + if (sle.getType() == ltMPTOKEN) + { + auto const issuanceID = sle[sfMPTokenIssuanceID]; + auto const account = sle[sfAccount]; + auto const amount = sle[sfMPTAmount]; + if (isBefore) + { + amount_[issuanceID][account].amtBefore = amount; + } + else + { + amount_[issuanceID][account].amtAfter = amount; + } + if (isDelete && isBefore) + { + deletedAuthorized_[sle.key()] = sle.isFlag(lsfMPTAuthorized); + } + } + }; + + if (before) + update(*before, true); + + if (after) + update(*after, false); +} + +bool +ValidMPTTransfer::isAuthorized( + ReadView const& view, + MPTID const& mptid, + AccountID const& holder, + bool reqAuth) const +{ + auto const key = keylet::mptoken(mptid, holder); + auto const it = deletedAuthorized_.find(key.key); + if (it != deletedAuthorized_.end()) + return !reqAuth || it->second; + return isTesSuccess(requireAuth(view, MPTIssue{mptid}, holder)); +} + +bool +ValidMPTTransfer::finalize( + STTx const& tx, + TER const, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + if (hasPrivilege(tx, OverrideFreeze)) + return true; + + // DEX transactions (AMM[Create,Deposit], cross-currency payments, offer creates) are + // subject to the MPTCanTrade flag in addition to the standard transfer rules. + // A payment is only DEX if it is a cross-currency payment. + auto const txnType = tx.getTxnType(); + auto const isDEX = [&] { + if (txnType == ttPAYMENT) + { + // A payment is cross-currency (and thus DEX) only if SendMax is present + // and its asset differs from the destination asset. + auto const amount = tx[sfAmount]; + return tx[~sfSendMax].value_or(amount).asset() != amount.asset(); + } + return txnType == ttAMM_CREATE || txnType == ttAMM_DEPOSIT || txnType == ttOFFER_CREATE; + }(); + + // Only enforce once MPTokensV2 is enabled to preserve consensus with non-V2 nodes. + // Log invariant failure error even if MPTokensV2 is disabled. + auto const invariantPasses = !view.rules().enabled(featureMPTokensV2); + + for (auto const& [mptID, values] : amount_) + { + std::uint16_t senders = 0; + std::uint16_t receivers = 0; + bool invalidTransfer = false; + auto const sleIssuance = view.read(keylet::mptIssuance(mptID)); + if (!sleIssuance) + { + continue; + } + + // These transactions are recovery/settlement paths. They may move an + // existing MPT position even after the issuer clears CanTransfer, so + // holders are not trapped in AMM, vault, or loan protocol accounts. + auto const waivesCanTransfer = txnType == ttAMM_WITHDRAW || + (view.rules().enabled(fixCleanup3_2_0) && + (txnType == ttVAULT_WITHDRAW || txnType == ttLOAN_BROKER_COVER_WITHDRAW || + txnType == ttLOAN_PAY)); + auto const canTransfer = sleIssuance->isFlag(lsfMPTCanTransfer) || waivesCanTransfer; + auto const canTrade = sleIssuance->isFlag(lsfMPTCanTrade); + auto const reqAuth = sleIssuance->isFlag(lsfMPTRequireAuth); + + for (auto const& [account, value] : values) + { + // Classify each account as a sender or receiver based on whether their MPTAmount + // decreased or increased. Count new MPToken holders (no amtBefore) as receivers. + // Skip deleted MPToken holders (amtAfter is nullopt); deletion requires zero balance. + if (value.amtAfter.has_value() && value.amtBefore.value_or(0) != *value.amtAfter) + { + if (!value.amtBefore.has_value() || *value.amtAfter > *value.amtBefore) + { + ++receivers; + } + else + { + ++senders; + } + + // Check once: if any involved account is frozen, the whole + // issuance transfer is considered frozen. Only need to check for + // frozen if there is a transfer of funds. + if (!invalidTransfer && + (isFrozen(view, account, MPTIssue{mptID}) || + !isAuthorized(view, mptID, account, reqAuth))) + { + invalidTransfer = true; + } + } + } + // A transfer between holders has occurred (senders > 0 && receivers > 0). + // Fail if the issuance is frozen, does not permit transfers, or — for + // DEX transactions — does not permit trading. + if ((invalidTransfer || !canTransfer || (isDEX && !canTrade)) && senders > 0 && + receivers > 0) + { + JLOG(j.fatal()) << "Invariant failed: invalid MPToken transfer between holders"; + return invariantPasses; + } + } + + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/paths/BookStep.cpp b/src/libxrpl/tx/paths/BookStep.cpp index 0391aa5f77..5cc2a987b8 100644 --- a/src/libxrpl/tx/paths/BookStep.cpp +++ b/src/libxrpl/tx/paths/BookStep.cpp @@ -1361,19 +1361,18 @@ BookStep::check(StrandContext const& ctx) const return terNO_RIPPLE; return std::nullopt; }, - [&](MPTIssue const& issue) -> std::optional { - // Check if can trade on DEX. - if (auto const ter = canTrade(view, book_.in); !isTesSuccess(ter)) - return ter; - if (auto const ter = canTrade(view, book_.out); !isTesSuccess(ter)) - return ter; - return std::nullopt; - }); + [&](MPTIssue const& issue) -> std::optional { return std::nullopt; }); if (err) return *err; } } + // Check if the offer can be traded on DEX. + if (auto const ter = canTrade(ctx.view, book_.in); !isTesSuccess(ter)) + return ter; + if (auto const ter = canTrade(ctx.view, book_.out); !isTesSuccess(ter)) + return ter; + return tesSUCCESS; } @@ -1384,12 +1383,22 @@ BookStep::rate( Asset const& asset, AccountID const& dstAccount) const { - auto const& issuer = asset.getIssuer(); - if (isXRP(issuer) || issuer == dstAccount) - return kParityRate; return asset.visit( - [&](Issue const&) { return transferRate(view, issuer); }, - [&](MPTIssue const& issue) { return transferRate(view, issue.getMptID()); }); + [&](Issue const& issue) -> Rate { + if (isXRP(issue.account) || issue.account == dstAccount) + return kParityRate; + return transferRate(view, issue.account); + }, + [&](MPTIssue const& mptIssue) -> Rate { + // For MPT, parity applies only when this asset is the final strand + // delivery AND the destination is the MPT issuer (holder → issuer, + // which is fee-free). Using strandDst_ alone is wrong because it + // incorrectly suppresses the fee when MPT is an intermediate or + // the in-side of a book that precedes the issuer's XRP receipt. + if (asset == strandDeliver_ && mptIssue.getIssuer() == dstAccount) + return kParityRate; + return transferRate(view, mptIssue.getMptID()); + }); }; template diff --git a/src/libxrpl/tx/paths/MPTEndpointStep.cpp b/src/libxrpl/tx/paths/MPTEndpointStep.cpp index 65f8409379..5595f8ea65 100644 --- a/src/libxrpl/tx/paths/MPTEndpointStep.cpp +++ b/src/libxrpl/tx/paths/MPTEndpointStep.cpp @@ -395,6 +395,9 @@ MPTEndpointPaymentStep::check(StrandContext const& ctx, std::shared_ptr const&) { + // The standard checks are all we can do because any remaining checks + // require the existence of a MPToken. Offer crossing does not + // require a pre-existing MPToken. return tesSUCCESS; } @@ -838,10 +841,17 @@ MPTEndpointStep::check(StrandContext const& ctx) const } // pure issue/redeem can't be frozen (issuer/holder) + // For the first step: check global freeze of the step's own asset. + // For the last step: check only the per-holder MPToken lock. + // Global freeze of the deliver asset is not checked here + // because MPT semantics allow issuer<->holder transfers even when globally + // locked — only holder-to-holder DEX paths are restricted. if (!(ctx.isLast && ctx.isFirst)) { auto const& account = ctx.isFirst ? src_ : dst_; - if (isFrozen(ctx.view, account, mptIssue_)) + bool const frozen = (ctx.isFirst && isGlobalFrozen(ctx.view, mptIssue_)) || + isIndividualFrozen(ctx.view, account, mptIssue_); + if (frozen) return terLOCKED; } diff --git a/src/libxrpl/tx/transactors/check/CheckCash.cpp b/src/libxrpl/tx/transactors/check/CheckCash.cpp index f8dbae66fc..cfc133b501 100644 --- a/src/libxrpl/tx/transactors/check/CheckCash.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCash.cpp @@ -264,9 +264,10 @@ CheckCash::preclaim(PreclaimContext const& ctx) return tecLOCKED; } - if (auto const err = canTrade(ctx.view, value.asset()); !isTesSuccess(err)) + if (auto const err = canTransfer(ctx.view, issue, srcId, dstId); + !isTesSuccess(err)) { - JLOG(ctx.j.warn()) << "MPT DEX is not allowed."; + JLOG(ctx.j.warn()) << "MPT transfer is disabled."; return err; } diff --git a/src/libxrpl/tx/transactors/check/CheckCreate.cpp b/src/libxrpl/tx/transactors/check/CheckCreate.cpp index 6373d7e343..d2d84536de 100644 --- a/src/libxrpl/tx/transactors/check/CheckCreate.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCreate.cpp @@ -111,10 +111,10 @@ CheckCreate::preclaim(PreclaimContext const& ctx) { // The currency may not be globally frozen AccountID const& issuerId{sendMax.getIssuer()}; - if (isGlobalFrozen(ctx.view, sendMax.asset())) + if (auto const ter = checkGlobalFrozen(ctx.view, sendMax.asset()); !isTesSuccess(ter)) { - JLOG(ctx.j.warn()) << "Creating a check for frozen asset"; - return sendMax.asset().holds() ? tecLOCKED : tecFROZEN; + JLOG(ctx.j.warn()) << "Creating a check for frozen or locked asset"; + return ter; } auto const err = sendMax.asset().visit( [&](Issue const& issue) -> std::optional { @@ -153,9 +153,21 @@ CheckCreate::preclaim(PreclaimContext const& ctx) }, [&](MPTIssue const& issue) -> std::optional { if (srcId != issuerId && isFrozen(ctx.view, srcId, issue)) + { + JLOG(ctx.j.warn()) << "Creating a check for locked MPT."; return tecLOCKED; + } if (dstId != issuerId && isFrozen(ctx.view, dstId, issue)) + { + JLOG(ctx.j.warn()) << "Creating a check for locked MPT."; return tecLOCKED; + } + if (auto const ter = canTransfer(ctx.view, issue, srcId, dstId); + !isTesSuccess(ter)) + { + JLOG(ctx.j.warn()) << "MPT transfer is disabled."; + return ter; + } return std::nullopt; }); @@ -169,7 +181,7 @@ CheckCreate::preclaim(PreclaimContext const& ctx) return tecEXPIRED; } - return canTrade(ctx.view, ctx.tx[sfSendMax].asset()); + return tesSUCCESS; } TER diff --git a/src/libxrpl/tx/transactors/dex/AMMCreate.cpp b/src/libxrpl/tx/transactors/dex/AMMCreate.cpp index 3e1fe97a76..60508eab85 100644 --- a/src/libxrpl/tx/transactors/dex/AMMCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMCreate.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -120,11 +119,16 @@ AMMCreate::preclaim(PreclaimContext const& ctx) } // Globally or individually frozen - if (isFrozen(ctx.view, accountID, amount.asset()) || - isFrozen(ctx.view, accountID, amount2.asset())) + if (auto const ter = checkFrozen(ctx.view, accountID, amount.asset()); !isTesSuccess(ter)) + { - JLOG(ctx.j.debug()) << "AMM Instance: involves frozen asset."; - return tecFROZEN; + JLOG(ctx.j.debug()) << "AMM Instance: involves frozen or locked asset."; + return ter; + } + if (auto const ter = checkFrozen(ctx.view, accountID, amount2.asset()); !isTesSuccess(ter)) + { + JLOG(ctx.j.debug()) << "AMM Instance: involves frozen or locked asset."; + return ter; } auto noDefaultRipple = [](ReadView const& view, Asset const& asset) { @@ -191,10 +195,10 @@ AMMCreate::preclaim(PreclaimContext const& ctx) return terADDRESS_COLLISION; } - if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_CREATE, amount.asset(), accountID); + if (auto const ter = canMPTTradeAndTransfer(ctx.view, amount.asset(), accountID, accountID); !isTesSuccess(ter)) return ter; - if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_CREATE, amount2.asset(), accountID); + if (auto const ter = canMPTTradeAndTransfer(ctx.view, amount2.asset(), accountID, accountID); !isTesSuccess(ter)) return ter; @@ -301,22 +305,14 @@ applyCreate(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Jou // Authorize MPT return amount.asset().visit( [&](MPTIssue const& issue) -> TER { - // Authorize MPT auto const& mptIssue = issue; auto const& mptID = mptIssue.getMptID(); - std::uint32_t flags = lsfMPTAMM; - if (auto const err = - requireAuth(ctx.view(), mptIssue, accountId, AuthType::WeakAuth); + // Implicitly authorize MPT asset for AMM pseudo-account. + std::uint32_t const flags = lsfMPTAMM | lsfMPTAuthorized; + if (auto const err = requireAuth(sb, mptIssue, accountId, AuthType::WeakAuth); !isTesSuccess(err)) { - if (err == tecNO_AUTH) - { - flags |= lsfMPTAuthorized; - } - else - { - return err; - } + return err; } if (auto const err = createMPToken(sb, mptID, accountId, flags); !isTesSuccess(err)) diff --git a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp index 86cd52300d..f45529f617 100644 --- a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include @@ -267,12 +266,12 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) return ter; } - if (isFrozen(ctx.view, accountID, asset)) + if (auto const ter = checkFrozen(ctx.view, accountID, asset); !isTesSuccess(ter)) { - JLOG(ctx.j.debug()) << "AMM Deposit: account or currency is frozen, " + JLOG(ctx.j.debug()) << "AMM Deposit: account or currency is frozen or locked, " << to_string(accountID) << " " << to_string(asset); - return tecFROZEN; + return ter; } return tesSUCCESS; @@ -304,18 +303,20 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) // LCOV_EXCL_STOP } // AMM account or currency frozen - if (isFrozen(ctx.view, ammAccountID, amount->asset())) + if (auto const ter = checkFrozen(ctx.view, ammAccountID, amount->asset()); + !isTesSuccess(ter)) { - JLOG(ctx.j.debug()) - << "AMM Deposit: AMM account or currency is frozen, " << to_string(accountID); - return tecFROZEN; + JLOG(ctx.j.debug()) << "AMM Deposit: AMM account or currency is frozen or locked, " + << to_string(accountID); + return ter; } // Account frozen - if (isIndividualFrozen(ctx.view, accountID, amount->asset())) + if (auto const ter = checkIndividualFrozen(ctx.view, accountID, amount->asset()); + !isTesSuccess(ter)) { - JLOG(ctx.j.debug()) << "AMM Deposit: account is frozen, " << to_string(accountID) - << " " << to_string(amount->asset()); - return tecFROZEN; + JLOG(ctx.j.debug()) << "AMM Deposit: account is frozen or locked, " + << to_string(accountID) << " " << to_string(amount->asset()); + return ter; } if (checkBalance) { @@ -368,10 +369,10 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) } } - if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_DEPOSIT, ctx.tx[sfAsset], accountID); + if (auto const ter = canMPTTradeAndTransfer(ctx.view, ctx.tx[sfAsset], accountID, accountID); !isTesSuccess(ter)) return ter; - if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_DEPOSIT, ctx.tx[sfAsset2], accountID); + if (auto const ter = canMPTTradeAndTransfer(ctx.view, ctx.tx[sfAsset2], accountID, accountID); !isTesSuccess(ter)) return ter; diff --git a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp index a4fd1518f5..352f637f2f 100644 --- a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -26,7 +25,6 @@ #include #include #include -#include #include #include @@ -242,24 +240,21 @@ AMMWithdraw::preclaim(PreclaimContext const& ctx) return ter; } // AMM account or currency frozen - if (isFrozen(ctx.view, ammAccountID, amount->asset())) + if (auto const ter = checkFrozen(ctx.view, ammAccountID, amount->asset()); + !isTesSuccess(ter)) { - JLOG(ctx.j.debug()) - << "AMM Withdraw: AMM account or currency is frozen, " << to_string(accountID); - return tecFROZEN; + JLOG(ctx.j.debug()) << "AMM Withdraw: AMM account or currency is frozen or locked, " + << to_string(accountID); + return ter; } // Account frozen - if (isIndividualFrozen(ctx.view, accountID, amount->asset())) - { - JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen, " << to_string(accountID) - << " " << to_string(amount->asset()); - return tecFROZEN; - } - - if (auto const ter = - checkMPTTxAllowed(ctx.view, ttAMM_WITHDRAW, amount->asset(), accountID); + if (auto const ter = checkIndividualFrozen(ctx.view, accountID, amount->asset()); !isTesSuccess(ter)) + { + JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen or locked, " + << to_string(accountID) << " " << to_string(amount->asset()); return ter; + } } return tesSUCCESS; }; @@ -635,10 +630,6 @@ AMMWithdraw::withdraw( auto const balanceAdj = isIssue ? std::max(priorBalance, balance.xrp()) : priorBalance; if (balanceAdj < reserve) return tecINSUFFICIENT_RESERVE; - - // Update owner count. - if (!isIssue) - adjustOwnerCount(view, sleAccount, 1, journal); } return tesSUCCESS; }; diff --git a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp index ed9eb1255d..8bf69d25c0 100644 --- a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp @@ -181,11 +181,15 @@ OfferCreate::preclaim(PreclaimContext const& ctx) auto viewJ = ctx.registry.get().getJournal("View"); - if (isGlobalFrozen(ctx.view, saTakerPays.asset()) || - isGlobalFrozen(ctx.view, saTakerGets.asset())) + if (auto const ter = checkGlobalFrozen(ctx.view, saTakerPays.asset()); !isTesSuccess(ter)) { - JLOG(ctx.j.debug()) << "Offer involves frozen asset"; - return tecFROZEN; + JLOG(ctx.j.debug()) << "Offer involves frozen or locked asset"; + return ter; + } + if (auto const ter = checkGlobalFrozen(ctx.view, saTakerGets.asset()); !isTesSuccess(ter)) + { + JLOG(ctx.j.debug()) << "Offer involves frozen or locked asset"; + return ter; } // Allow unfunded MPT for issuer (OutstandingAmount >= MaximumAmount) @@ -320,7 +324,13 @@ OfferCreate::checkAcceptAsset( [&](MPTIssue const& issue) -> TER { // WeakAuth - don't check if MPToken exists since it's created // if needed. - return requireAuth(view, issue, id, AuthType::WeakAuth); + if (auto const ter = requireAuth(view, issue, id, AuthType::WeakAuth); + !isTesSuccess(ter)) + { + return ter; + } + + return checkFrozen(view, id, issue); }); } diff --git a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp index 382ea7d40e..9b0c8d2b56 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp @@ -133,8 +133,9 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx) // Can't unauthorize the pseudo-accounts because they are implicitly // always authorized. No need to amendment gate since Vault and LoanBroker - // can only be created if the Vault amendment is enabled. - if (isPseudoAccount(ctx.view, *holderID, {&sfVaultID, &sfLoanBrokerID})) + // can only be created if the Vault amendment is enabled; AMM with MPToken asset + // can only be created if MPTokensV2 is enabled. + if (isPseudoAccount(ctx.view, *holderID, {&sfVaultID, &sfLoanBrokerID, &sfAMMID})) return tecNO_PERMISSION; return tesSUCCESS; diff --git a/src/test/app/AMMExtendedMPT_test.cpp b/src/test/app/AMMExtendedMPT_test.cpp index cf1210901f..e6dcc95733 100644 --- a/src/test/app/AMMExtendedMPT_test.cpp +++ b/src/test/app/AMMExtendedMPT_test.cpp @@ -3114,10 +3114,8 @@ private: btc.set({.holder = bob, .flags = tfMPTLock}); { - // different from IOU. The offer is created but not crossed. - env(offer(bob, btc(5), XRP(25))); + env(offer(bob, btc(5), XRP(25)), Ter(tecLOCKED)); env.close(); - BEAST_EXPECT(expectOffers(env, bob, 1, {{{btc(5), XRP(25)}}})); BEAST_EXPECT(ammAlice.expectBalances(XRP(500), btc(105), ammAlice.tokens())); } @@ -3220,7 +3218,7 @@ private: btc.set({.flags = tfMPTLock}); // assets can't be bought on the market - AMM const ammA3(env, a3, btc(1), XRP(1), Ter(tecFROZEN)); + AMM const ammA3(env, a3, btc(1), XRP(1), Ter(tecLOCKED)); // direct issues can be sent env(pay(g1, a2, btc(1))); diff --git a/src/test/app/AMMMPT_test.cpp b/src/test/app/AMMMPT_test.cpp index cfb8edc60b..31b54ceee0 100644 --- a/src/test/app/AMMMPT_test.cpp +++ b/src/test/app/AMMMPT_test.cpp @@ -144,7 +144,7 @@ private: .pay = 30'000, .flags = tfMPTCanLock | kMptDexFlags}); usd.set({.flags = tfMPTLock}); - AMM const ammAliceFail(env, alice_, XRP(10'000), usd(10'000), Ter(tecFROZEN)); + AMM const ammAliceFail(env, alice_, XRP(10'000), usd(10'000), Ter(tecLOCKED)); usd.set({.flags = tfMPTUnlock}); AMM const ammAlice(env, alice_, XRP(10'000), usd(10'000)); } @@ -348,7 +348,7 @@ private: .pay = 30'000, .flags = tfMPTCanLock | kMptDexFlags}); btc.set({.flags = tfMPTLock}); - AMM const ammAlice(env, alice_, USD(10'000), btc(10'000), Ter(tecFROZEN)); + AMM const ammAlice(env, alice_, USD(10'000), btc(10'000), Ter(tecLOCKED)); BEAST_EXPECT(!ammAlice.ammExists()); } @@ -365,7 +365,7 @@ private: btc.set({.holder = alice_, .flags = tfMPTLock}); // alice's token is locked - AMM const ammAlice(env, alice_, USD(10'000), btc(10'000), Ter(tecFROZEN)); + AMM const ammAlice(env, alice_, USD(10'000), btc(10'000), Ter(tecLOCKED)); BEAST_EXPECT(!ammAlice.ammExists()); // bob can create @@ -693,15 +693,15 @@ private: btc.set({.flags = tfMPTLock}); ammAlice.deposit( - carol_, btc(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecFROZEN)); + carol_, btc(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecLOCKED)); ammAlice.deposit( - carol_, USD(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecFROZEN)); + carol_, USD(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecLOCKED)); - ammAlice.deposit(carol_, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN)); + ammAlice.deposit(carol_, 1'000, std::nullopt, std::nullopt, Ter(tecLOCKED)); ammAlice.deposit( - carol_, USD(100), btc(100), std::nullopt, std::nullopt, Ter(tecFROZEN)); + carol_, USD(100), btc(100), std::nullopt, std::nullopt, Ter(tecLOCKED)); } // Individually lock MPT or freeze IOU (AMM) with IOU/MPT AMM @@ -722,20 +722,19 @@ private: // Carol can not deposit locked mpt ammAlice.deposit( - carol_, btc(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecFROZEN)); + carol_, btc(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecLOCKED)); - ammAlice.deposit(carol_, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN)); + ammAlice.deposit(carol_, 1'000, std::nullopt, std::nullopt, Ter(tecLOCKED)); if (!features[featureAMMClawback]) { - ammAlice.deposit( - carol_, USD(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecLOCKED)); + ammAlice.deposit(carol_, USD(100), std::nullopt, std::nullopt, std::nullopt); } else { - // Carol can not deposit non-forzen token either + // Carol can not deposit non-frozen token either ammAlice.deposit( - carol_, USD(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecFROZEN)); + carol_, USD(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecLOCKED)); } // Alice can deposit because she's not individually locked @@ -778,9 +777,9 @@ private: ammAlice.deposit(carol_, USD(100), std::nullopt, std::nullopt, std::nullopt); // Can not deposit locked token - ammAlice.deposit(carol_, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN)); + ammAlice.deposit(carol_, 1'000, std::nullopt, std::nullopt, Ter(tecLOCKED)); ammAlice.deposit( - carol_, btc(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecFROZEN)); + carol_, btc(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecLOCKED)); // Unlock AMM MPT btc.set({.holder = ammAlice.ammAccount(), .flags = tfMPTUnlock}); @@ -812,10 +811,10 @@ private: // Carol's BTC is locked btc.set({.holder = carol_, .flags = tfMPTLock}); ammAlice.deposit( - carol_, usd(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecFROZEN)); + carol_, usd(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecLOCKED)); ammAlice.deposit( - carol_, btc(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecFROZEN)); + carol_, btc(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecLOCKED)); // Unlock carol's BTC btc.set({.holder = carol_, .flags = tfMPTUnlock}); @@ -831,9 +830,9 @@ private: ammAlice.deposit(carol_, usd(100), std::nullopt, std::nullopt, std::nullopt); // Can not deposit locked token BTC - ammAlice.deposit(carol_, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN)); + ammAlice.deposit(carol_, 1'000, std::nullopt, std::nullopt, Ter(tecLOCKED)); ammAlice.deposit( - carol_, btc(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecFROZEN)); + carol_, btc(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecLOCKED)); // Unlock AMM MPT BTC btc.set({.holder = ammAlice.ammAccount(), .flags = tfMPTUnlock}); @@ -848,9 +847,9 @@ private: ammAlice.deposit(carol_, btc(100), std::nullopt, std::nullopt, std::nullopt); // Can not deposit locked token USD - ammAlice.deposit(carol_, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN)); + ammAlice.deposit(carol_, 1'000, std::nullopt, std::nullopt, Ter(tecLOCKED)); ammAlice.deposit( - carol_, usd(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecFROZEN)); + carol_, usd(100), std::nullopt, std::nullopt, std::nullopt, Ter(tecLOCKED)); // Unlock AMM MPT USD usd.set({.holder = ammAlice.ammAccount(), .flags = tfMPTUnlock}); @@ -904,7 +903,7 @@ private: AMM amm(env, gw, XRP(10'000), btc(10'000)); - amm.deposit({.account = alice, .asset1In = btc(10), .err = Ter(tecNO_PERMISSION)}); + amm.deposit({.account = alice, .asset1In = btc(10), .err = Ter(tecNO_AUTH)}); } // Insufficient XRP balance @@ -2246,23 +2245,22 @@ private: Env env{*this}; env.fund(XRP(30'000), gw_, alice_); env.close(); - auto btcm = MPTTester( + auto btc = MPTTester( {.env = env, .issuer = gw_, .holders = {alice_}, .pay = 30'000, - .flags = tfMPTCanTrade, + .flags = kMptDexFlags, + .mutableFlags = tmfMPTCanMutateCanTransfer, .authHolder = true}); - MPT const btc = btcm; AMM amm(env, gw_, XRP(10'000), btc(10'000)); + amm.deposit(DepositArg{.account = alice_, .asset1In = XRP(200), .asset2In = btc(200)}); + // Allow to withdraw if transfer is disabled + btc.set({.mutableFlags = tmfMPTClearCanTransfer}); amm.withdraw( - WithdrawArg{ - .account = alice_, - .asset1Out = btc(100), - .assets = {{XRP, btc}}, - .err = Ter(tecNO_PERMISSION)}); + WithdrawArg{.account = alice_, .asset1Out = btc(100), .assets = {{XRP, btc}}}); } // Globally locked MPT @@ -2283,8 +2281,8 @@ private: btc.set({.flags = tfMPTLock}); ammAlice.withdraw( - alice_, MPT(ammAlice[1])(100), std::nullopt, std::nullopt, Ter(tecFROZEN)); - ammAlice.withdraw(alice_, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN)); + alice_, MPT(ammAlice[1])(100), std::nullopt, std::nullopt, Ter(tecLOCKED)); + ammAlice.withdraw(alice_, 1'000, std::nullopt, std::nullopt, Ter(tecLOCKED)); // can single withdraw the other asset ammAlice.withdraw({.account = alice_, .asset1Out = XRP(100)}); @@ -2312,8 +2310,8 @@ private: // Alice's BTC is locked btc.set({.holder = alice_, .flags = tfMPTLock}); - ammAlice.withdraw(alice_, 1000, std::nullopt, std::nullopt, Ter(tecFROZEN)); - ammAlice.withdraw(alice_, btc(100), std::nullopt, std::nullopt, Ter(tecFROZEN)); + ammAlice.withdraw(alice_, 1000, std::nullopt, std::nullopt, Ter(tecLOCKED)); + ammAlice.withdraw(alice_, btc(100), std::nullopt, std::nullopt, Ter(tecLOCKED)); // can withdraw the other asset ammAlice.withdraw(alice_, usd(100), std::nullopt, std::nullopt); @@ -2341,8 +2339,8 @@ private: // Alice's BTC is locked btc.set({.holder = alice_, .flags = tfMPTLock}); - ammAlice.withdraw(alice_, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN)); - ammAlice.withdraw(alice_, btc(100), std::nullopt, std::nullopt, Ter(tecFROZEN)); + ammAlice.withdraw(alice_, 1'000, std::nullopt, std::nullopt, Ter(tecLOCKED)); + ammAlice.withdraw(alice_, btc(100), std::nullopt, std::nullopt, Ter(tecLOCKED)); // can still single withdraw the unlocked other asset ammAlice.withdraw(alice_, USD(100), std::nullopt, std::nullopt); @@ -2361,8 +2359,8 @@ private: ammAlice.withdraw(alice_, USD(100), std::nullopt, std::nullopt); // Can not withdraw locked token BTC - ammAlice.withdraw(alice_, 1'000, std::nullopt, std::nullopt, Ter(tecFROZEN)); - ammAlice.withdraw(alice_, btc(100), std::nullopt, std::nullopt, Ter(tecFROZEN)); + ammAlice.withdraw(alice_, 1'000, std::nullopt, std::nullopt, Ter(tecLOCKED)); + ammAlice.withdraw(alice_, btc(100), std::nullopt, std::nullopt, Ter(tecLOCKED)); // Unlock AMM MPT btc.set({.holder = ammAlice.ammAccount(), .flags = tfMPTUnlock}); @@ -6886,33 +6884,33 @@ private: cb(amm, btc); }; - // Deposit two assets, one of which is frozen, - // then we should get tecFROZEN error. + // Deposit two assets, one of which is locked, + // then we should get tecLOCKED error. { Env env(*this); testAMMDeposit(env, [&](AMM& amm, MPTTester& btc) { - amm.deposit(alice_, btc(100), XRP(100), std::nullopt, tfTwoAsset, Ter(tecFROZEN)); + amm.deposit(alice_, btc(100), XRP(100), std::nullopt, tfTwoAsset, Ter(tecLOCKED)); }); } - // Deposit one asset, which is the frozen token, - // then we should get tecFROZEN error. + // Deposit one asset, which is the locked token, + // then we should get tecLOCKED error. { Env env(*this); testAMMDeposit(env, [&](AMM& amm, MPTTester& btc) { amm.deposit( - alice_, btc(100), std::nullopt, std::nullopt, tfSingleAsset, Ter(tecFROZEN)); + alice_, btc(100), std::nullopt, std::nullopt, tfSingleAsset, Ter(tecLOCKED)); }); } // Deposit one asset which is not the frozen token, - // but the other asset is frozen. We should get tecFROZEN error + // but the other asset is frozen. We should get tecLOCKED error // when feature AMMClawback is enabled. { Env env(*this); testAMMDeposit(env, [&](AMM& amm, MPTTester& btc) { amm.deposit( - alice_, XRP(100), std::nullopt, std::nullopt, tfSingleAsset, Ter(tecFROZEN)); + alice_, XRP(100), std::nullopt, std::nullopt, tfSingleAsset, Ter(tecLOCKED)); }); } } diff --git a/src/test/app/CheckMPT_test.cpp b/src/test/app/CheckMPT_test.cpp index a7fef5043c..1ca24051dd 100644 --- a/src/test/app/CheckMPT_test.cpp +++ b/src/test/app/CheckMPT_test.cpp @@ -1860,10 +1860,10 @@ class CheckMPT_test : public beast::unit_test::Suite // Use offers to automatically create MPT. MPT const oF4 = gw1["OF4"]; gw1.set(oF4, tfMPTLock); - env(offer(gw1, XRP(92), oF4(92)), Ter(tecFROZEN)); + env(offer(gw1, XRP(92), oF4(92)), Ter(tecLOCKED)); env.close(); BEAST_EXPECT(env.le(keylet::mptoken(oF4, alice)) == nullptr); - env(offer(alice, oF4(92), XRP(92)), Ter(tecFROZEN)); + env(offer(alice, oF4(92), XRP(92)), Ter(tecLOCKED)); env.close(); // No one's owner count should have changed. @@ -1951,10 +1951,10 @@ class CheckMPT_test : public beast::unit_test::Suite // Use offers to automatically create MPT. MPT const oF4 = gw1["OF4"]; gw1.set(oF4, tfMPTLock); - env(offer(alice, XRP(91), oF4(91)), Ter(tecFROZEN)); + env(offer(alice, XRP(91), oF4(91)), Ter(tecLOCKED)); env.close(); BEAST_EXPECT(env.le(keylet::mptoken(oF4, alice)) == nullptr); - env(offer(bob, oF4(91), XRP(91)), Ter(tecFROZEN)); + env(offer(bob, oF4(91), XRP(91)), Ter(tecLOCKED)); env.close(); // No one's owner count should have changed. diff --git a/src/test/app/ClawbackMPT_test.cpp b/src/test/app/ClawbackMPT_test.cpp new file mode 100644 index 0000000000..299e63d028 --- /dev/null +++ b/src/test/app/ClawbackMPT_test.cpp @@ -0,0 +1,435 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +class ClawbackMPT_test : public beast::unit_test::Suite +{ + static std::uint32_t + ticketCount(test::jtx::Env const& env, test::jtx::Account const& acct) + { + std::uint32_t ret{0}; + if (auto const sleAcct = env.le(acct)) + ret = sleAcct->at(~sfTicketCount).value_or(0); + return ret; + } + + void + testValidation(FeatureBitset features) + { + testcase("Validation"); + using namespace test::jtx; + + // MPT clawback fails when featureMPTokensV1 is disabled + { + Env env(*this, features - featureMPTokensV1); + Account const alice{"alice"}; + Account const bob{"bob"}; + + env.fund(XRP(1000), alice, bob); + env.close(); + + auto const mpt = xrpl::test::jtx::MPT(alice.name(), makeMptID(env.seq(alice), alice)); + + env(claw(alice, mpt(5), bob), Ter(temDISABLED)); + env.close(); + } + + // MPT clawback fails when tfMPTCanClawback is not set on the issuance + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create({.ownerCount = 1, .holderCount = 0}); + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 100); + + mptAlice.claw(alice, bob, 10, tecNO_PERMISSION); + } + + // Test preflight validation failures + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + env.fund(XRP(1000), alice, bob); + env.close(); + + auto const mpt = xrpl::test::jtx::MPT(alice.name(), makeMptID(env.seq(alice), alice)); + + // fails due to invalid flag + env(claw(alice, mpt(5), bob), Txflags(0x00008000), Ter(temINVALID_FLAG)); + env.close(); + + // fails due to zero amount + env(claw(alice, mpt(0), bob), Ter(temBAD_AMOUNT)); + env.close(); + + // fails due to negative amount + env(claw(alice, mpt(-1), bob), Ter(temBAD_AMOUNT)); + env.close(); + + // fails when holder is not specified + env(claw(alice, mpt(5)), Ter(temMALFORMED)); + env.close(); + + // fails when issuer and holder are the same account + env(claw(alice, mpt(5), alice), Ter(temMALFORMED)); + env.close(); + } + + // Test preclaim failures + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + auto const fakeMpt = + xrpl::test::jtx::MPT(alice.name(), makeMptID(env.seq(alice), alice)); + + // clawback fails when the issuance does not exist + env(claw(alice, fakeMpt(5), bob), Ter(tecOBJECT_NOT_FOUND)); + env.close(); + + mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback}); + + // clawback fails when bob has no MPToken + mptAlice.claw(alice, bob, 5, tecOBJECT_NOT_FOUND); + + mptAlice.authorize({.account = bob}); + + // clawback fails because bob's balance is 0 + mptAlice.claw(alice, bob, 5, tecINSUFFICIENT_FUNDS); + } + } + + void + testPermission(FeatureBitset features) + { + testcase("Permission"); + using namespace test::jtx; + + // Clawing back from a non-existent account fails + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + // bob is not funded and does not exist + MPTTester mptAlice(env, alice, MPTInit{}); + mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback}); + + mptAlice.claw(alice, bob, 5, terNO_ACCOUNT); + } + + // A non-issuer cannot claw back MPT + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const cindy{"cindy"}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + env.fund(XRP(1000), cindy); + env.close(); + + mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback}); + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 1000); + + // cindy fails to claw because she is not the issuer + mptAlice.claw(cindy, bob, 200, tecNO_PERMISSION); + } + } + + void + testEnabled(FeatureBitset features) + { + testcase("Enable clawback"); + using namespace test::jtx; + + // Test that alice is able to successfully clawback MPT from bob + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback}); + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 1000); + + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 1000)); + + // alice claws back 200 tokens from bob + mptAlice.claw(alice, bob, 200); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 800)); + + // alice claws back remaining 800 tokens + mptAlice.claw(alice, bob, 800); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 0)); + } + + void + testMultiIssuance(FeatureBitset features) + { + testcase("Multi issuance"); + using namespace test::jtx; + + // Two issuers each issue their own MPT to cindy. + // Clawback from one does not affect the other. + { + Env env(*this, features); + + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const cindy{"cindy"}; + + MPTTester mptAlice(env, alice, {.holders = {cindy}}); + mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback}); + mptAlice.authorize({.account = cindy}); + mptAlice.pay(alice, cindy, 1000); + + MPTTester mptBob(env, bob, MPTInit{}); // cindy already funded by mptAlice + mptBob.create({.ownerCount = 1, .flags = tfMPTCanClawback}); + mptBob.authorize({.account = cindy}); + mptBob.pay(bob, cindy, 1000); + + BEAST_EXPECT(mptAlice.checkMPTokenAmount(cindy, 1000)); + BEAST_EXPECT(mptBob.checkMPTokenAmount(cindy, 1000)); + + // alice claws back 200 from cindy, bob's issuance is unaffected + mptAlice.claw(alice, cindy, 200); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(cindy, 800)); + BEAST_EXPECT(mptBob.checkMPTokenAmount(cindy, 1000)); + + // bob claws back 600 from cindy, alice's issuance is unaffected + mptBob.claw(bob, cindy, 600); + BEAST_EXPECT(mptBob.checkMPTokenAmount(cindy, 400)); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(cindy, 800)); + } + + // One issuer issues MPT to two different holders. + // Clawback from one holder does not affect the other. + { + Env env(*this, features); + + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const cindy{"cindy"}; + + MPTTester mptAlice(env, alice, {.holders = {bob, cindy}}); + mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback}); + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = cindy}); + mptAlice.pay(alice, bob, 600); + mptAlice.pay(alice, cindy, 1000); + + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 600)); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(cindy, 1000)); + + // alice claws back 500 from bob, cindy's balance is unchanged + mptAlice.claw(alice, bob, 500); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 100)); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(cindy, 1000)); + + // alice claws back 300 from cindy, bob's balance is unchanged + mptAlice.claw(alice, cindy, 300); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(cindy, 700)); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 100)); + } + } + + void + testZeroBalanceAfterClawback(FeatureBitset features) + { + testcase("Zero balance after clawback"); + using namespace test::jtx; + + // After clawback reduces balance to zero, the MPToken object + // still exists (unlike IOU trustlines which are deleted). + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback}); + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 1000); + + BEAST_EXPECT(ownerCount(env, bob) == 1); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 1000)); + + // alice claws back the full amount + mptAlice.claw(alice, bob, 1000); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 0)); + + // bob still holds the MPToken object (balance 0, not deleted) + BEAST_EXPECT(ownerCount(env, bob) == 1); + } + + void + testLockedMPT(FeatureBitset features) + { + testcase("Locked MPT"); + using namespace test::jtx; + + // Test that globally locked MPT can still be clawed back + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock | tfMPTCanClawback}); + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 1000); + + // globally lock the issuance + mptAlice.set({.account = alice, .flags = tfMPTLock}); + + // clawback succeeds despite global lock + mptAlice.claw(alice, bob, 200); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 800)); + } + + // Test that individually locked MPT can still be clawed back + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock | tfMPTCanClawback}); + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 1000); + + // individually lock bob's MPToken + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + + // clawback succeeds despite individual lock + mptAlice.claw(alice, bob, 200); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 800)); + } + } + + void + testAmountExceedsAvailable(FeatureBitset features) + { + testcase("Amount exceeds available"); + using namespace test::jtx; + + // When alice tries to claw back more than bob holds, + // only the available balance is clawed back + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback}); + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 1000); + + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 1000)); + + // alice tries to claw back 2000, but bob only has 1000 + mptAlice.claw(alice, bob, 2000); + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 0)); + BEAST_EXPECT(mptAlice.checkMPTokenOutstandingAmount(0)); + + // MPToken object still exists with 0 balance + BEAST_EXPECT(ownerCount(env, bob) == 1); + } + + void + testTickets(FeatureBitset features) + { + testcase("Tickets"); + using namespace test::jtx; + + // Tests MPT clawback using tickets + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback}); + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 100); + + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 100)); + + // alice creates 10 tickets + std::uint32_t ticketCnt = 10; + std::uint32_t aliceTicketSeq{env.seq(alice) + 1}; + env(ticket::create(alice, ticketCnt)); + env.close(); + std::uint32_t const aliceSeq{env.seq(alice)}; + BEAST_EXPECT(ticketCount(env, alice) == ticketCnt); + BEAST_EXPECT(ownerCount(env, alice) == ticketCnt + 1); // tickets + issuance + + while (ticketCnt > 0) + { + // alice claws back 5 tokens using a ticket + env(claw(alice, mptAlice.mpt(5), bob), ticket::Use(aliceTicketSeq++)); + env.close(); + + ticketCnt--; + BEAST_EXPECT(ticketCount(env, alice) == ticketCnt); + } + + // alice clawed back 50 tokens total, 50 remain + BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 50)); + + // account sequence numbers did not advance + BEAST_EXPECT(env.seq(alice) == aliceSeq); + } + + void + testWithFeats(FeatureBitset features) + { + testValidation(features); + testPermission(features); + testEnabled(features); + testMultiIssuance(features); + testZeroBalanceAfterClawback(features); + testLockedMPT(features); + testAmountExceedsAvailable(features); + testTickets(features); + } + +public: + void + run() override + { + using namespace test::jtx; + FeatureBitset const all{testableAmendments()}; + testWithFeats(all); + } +}; + +BEAST_DEFINE_TESTSUITE(ClawbackMPT, app, xrpl); +} // namespace xrpl diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 5cb872f291..6fd1904992 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -4313,6 +4313,189 @@ class Invariants_test : public beast::unit_test::Suite return true; }); } + + // Invalid transfer + std::array, 3> const invalidTransferTests = { + std::make_pair(ttAMM_WITHDRAW, false), + std::make_pair(ttPAYMENT, false), + std::make_pair(ttPAYMENT, true)}; + for (auto const enabled : {true, false}) + { + for (auto const& [tx, crossCurrencyPayment] : invalidTransferTests) + { + for (auto const flag : + {static_cast(lsfMPTLocked), + ~lsfMPTCanTransfer, + ~lsfMPTCanTrade, + 0u}) + { + MPTID id{}; + auto const isSuccess = !enabled || flag == 0 || + (tx == ttPAYMENT && !crossCurrencyPayment && (flag == ~lsfMPTCanTrade)) || + (tx == ttAMM_WITHDRAW && + (flag == ~lsfMPTCanTrade || flag == ~lsfMPTCanTransfer)); + std::pair const error = isSuccess + ? std::make_pair(TER(tesSUCCESS), TER(tesSUCCESS)) + : std::make_pair(TER(tecINVARIANT_FAILED), TER(tefINVARIANT_FAILED)); + doInvariantCheck( + {{isSuccess ? "" : "invalid MPToken transfer between holders"}}, + [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto update = [&](AccountID const& a, std::uint64_t v) { + auto sle = ac.view().peek(keylet::mptoken(id, a)); + if (!sle) + return false; + sle->at(sfMPTAmount) = v; + ac.view().update(sle); + return true; + }; + auto issuanceSle = ac.view().peek(keylet::mptIssuance(id)); + if (!issuanceSle) + return false; + auto const flags = issuanceSle->at(sfFlags); + if (flag == lsfMPTLocked) + { + issuanceSle->at(sfFlags) = flags | lsfMPTLocked; + } + else if (flag != 0u) + { + issuanceSle->at(sfFlags) = flags & flag; + } + issuanceSle->at(sfOutstandingAmount) = 200; + ac.view().update(issuanceSle); + return update(a1, 101) && update(a2, 99); + }, + XRPAmount{}, + STTx{ + tx, + [&](STObject& tx) { + if (crossCurrencyPayment) + { + tx.setFieldAmount( + sfSendMax, STAmount(MPTAmount{100}, MPTIssue{id})); + } + }}, + {error.first, error.second}, + [&](Account const& a1, Account const& a2, Env& env) { + Account const gw("gw"); + env.fund(XRP(1'000), gw); + MPTTester const usd( + {.env = env, .issuer = gw, .holders = {a1, a2}, .pay = 100}); + id = usd.issuanceID(); + if (!enabled) + { + env.disableFeature(featureMPTokensV2); + } + return true; + }); + } + } + } + } + + void + testAMM() + { + testcase << "AMM"; + using namespace jtx; + + MPTID mptID{}; + uint256 ammID{}; + AccountID ammAccountID{}; + Account const gw{"gw"}; + Issue lptIssue{}; + PrettyAsset poolAsset{xrpIssue()}; + + auto deleteAMMAccount = [&](ApplyContext& ac, bool) { + auto sle = ac.view().peek(keylet::account(ammAccountID)); + if (!sle) + return false; + ac.view().erase(sle); + return true; + }; + + auto updateLPTokensBalance = [&](ApplyContext& ac, std::int64_t amount) { + auto sle = ac.view().peek(keylet::amm(ammID)); + if (!sle) + return false; + sle->setFieldAmount(sfLPTokenBalance, STAmount{lptIssue, amount}); + ac.view().update(sle); + return true; + }; + auto updateLPTokensBadAmount = [&](ApplyContext& ac, bool) { + return updateLPTokensBalance(ac, -1); + }; + auto updateLPTokensBadBalance = [&](ApplyContext& ac, bool) { + return updateLPTokensBalance(ac, 200'000'000); + }; + auto updateAMM = [&](ApplyContext& ac, bool) { return updateLPTokensBalance(ac, 10); }; + + auto updateAMMPool = [&](ApplyContext& ac, bool isMPT) { + if (isMPT) + { + auto sle = ac.view().peek(keylet::mptoken(mptID, ammAccountID)); + if (!sle) + return false; + sle->setFieldU64(sfMPTAmount, 1); + ac.view().update(sle); + return true; + } + auto sle = ac.view().peek(keylet::account(ammAccountID)); + if (!sle) + return false; + sle->setFieldAmount(sfBalance, XRP(1)); + ac.view().update(sle); + return true; + }; + + auto test = [&](auto const txType, + auto&& update, + bool isMPT, + TER error = tecINVARIANT_FAILED) { + doInvariantCheck( + {{"AMM"}}, + [&](Account const&, Account const&, ApplyContext& ac) { return update(ac, isMPT); }, + XRPAmount{}, + STTx{txType, [&](STObject& tx) {}}, + {tecINVARIANT_FAILED, error}, + [&](Account const&, Account const&, Env& env) { + env.fund(XRP(1'000), gw); + poolAsset = [&]() -> PrettyAsset { + if (isMPT) + { + MPT const mpt = MPTTester({.env = env, .issuer = gw}); + mptID = mpt.issuanceID; + return mpt; + } + return gw["USD"]; + }(); + AMM const amm(env, gw, XRP(100), poolAsset(100)); + ammAccountID = amm.ammAccount(); + ammID = amm.ammID(); + lptIssue = amm.lptIssue(); + return true; + }); + }; + + for (bool const isMPT : {false, true}) + { + auto const error = isMPT ? TER(tecINVARIANT_FAILED) : TER(tefINVARIANT_FAILED); + for (auto txType : {ttAMM_CREATE, ttAMM_DEPOSIT, ttAMM_CLAWBACK, ttAMM_WITHDRAW}) + { + test(txType, deleteAMMAccount, isMPT, tefINVARIANT_FAILED); + test(txType, updateLPTokensBadAmount, isMPT); + test(txType, updateLPTokensBadBalance, isMPT); + } + for (auto txType : {ttAMM_BID, ttAMM_VOTE}) + { + test(txType, updateAMMPool, isMPT, error); + test(txType, updateLPTokensBadAmount, isMPT); + test(txType, updateLPTokensBadBalance, isMPT); + } + for (auto txType : {ttAMM_DELETE, ttCHECK_CASH, ttOFFER_CREATE, ttPAYMENT}) + { + test(txType, updateAMM, isMPT); + } + } } // Test the invariant overwrite fix for both pre- and post-amendment @@ -4600,6 +4783,7 @@ public: testInvariantOverwrite(defaultAmendments()); testInvariantOverwrite(defaultAmendments() - fixCleanup3_1_3); testVaultComputeCoarsestScale(); + testAMM(); } }; diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 74bdc656df..6906c4aba0 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -3498,10 +3499,10 @@ class MPToken_test : public beast::unit_test::Suite auto const [errBuy, errSell] = [&]() -> std::pair { // Global lock if (lockMPTIssue) - return std::make_pair(tecFROZEN, tecFROZEN); + return std::make_pair(tecLOCKED, tecLOCKED); // Local lock if (lockMPToken) - return std::make_pair(tesSUCCESS, error(tecUNFUNDED_OFFER)); + return std::make_pair(error(tecLOCKED), error(tecUNFUNDED_OFFER)); // MPToken doesn't exist if (requireAuth) return std::make_pair(error(tecNO_AUTH), error(tecUNFUNDED_OFFER)); @@ -3564,51 +3565,77 @@ class MPToken_test : public beast::unit_test::Suite env(offer(alice, btc(10), eth(10)), Ter(tecUNFUNDED_OFFER)); } - // MPTLock flag is set and the account is not the issuer of MPT + // MPTLock flag is set: MPT/MPT offer crossing with independent issuers. + // gw2 issues BTC and gw issues ETH so each asset can be frozen independently. + // Passive setup: bob sells BTC (offer(bob, ETH, BTC)); dan sells ETH (offer(dan, BTC, + // ETH)). { + Account const gw2 = Account("gw2"); Account const bob = Account("bob"); Account const dan = Account("dan"); Env env(*this); - env.fund(XRP(1'000), gw, alice, carol, bob, dan); + env.fund(XRP(1'000), gw, gw2, alice, carol, bob, dan); MPTTester btc( {.env = env, - .issuer = gw, - .holders = {alice, carol, bob, dan}, + .issuer = gw2, + .holders = {alice, carol, bob, dan, gw}, .pay = 100, .flags = tfMPTCanLock | kMptDexFlags}); MPTTester eth( {.env = env, .issuer = gw, - .holders = {alice, carol, bob, dan}, + .holders = {alice, carol, bob, dan, gw2}, .pay = 100, .flags = tfMPTCanLock | kMptDexFlags}); + // bob sells BTC (takerGets=BTC); dan sells ETH (takerGets=ETH) env(offer(bob, eth(10), btc(10)), Txflags(tfPassive)); env(offer(dan, btc(10), eth(10)), Txflags(tfPassive)); + env.close(); - auto test = [&](auto const& flag, bool gwOwner = false) { - btc.set({.holder = carol, .flags = flag}); - btc.set({.holder = alice, .flags = flag}); + // --- Individual lock on BTC --- + // alice sells locked BTC (takerGets): balance zeroed, offer unfunded + btc.set({.holder = alice, .flags = tfMPTLock}); + env(offer(alice, eth(1), btc(1)), Ter(tecUNFUNDED_OFFER)); + btc.set({.holder = alice, .flags = tfMPTUnlock}); + // carol buys locked BTC (takerPays): locked MPToken cannot receive + btc.set({.holder = carol, .flags = tfMPTLock}); + env(offer(carol, btc(1), eth(1)), Ter(tecLOCKED)); + btc.set({.holder = carol, .flags = tfMPTUnlock}); + // gw2 is BTC issuer: individual lock on holders does not affect issuer + env(offer(gw2, eth(1), btc(1))); - if (gwOwner) - { - // Succeeds if the account is the issuer - env(offer(gw, eth(1), btc(1))); - env(offer(gw, btc(1), eth(1))); - } - else - { - auto const err = flag == tfMPTLock ? Ter(tecUNFUNDED_OFFER) : Ter(tesSUCCESS); - env(offer(alice, eth(1), btc(1)), err); - // Offer created by not crossed - env(offer(carol, btc(1), eth(1))); - BEAST_EXPECT(expectOffers(env, carol, 1, {{btc(1), eth(1)}})); - } - }; + // --- Individual lock on ETH --- + // alice sells locked ETH (takerGets): balance zeroed, offer unfunded + eth.set({.holder = alice, .flags = tfMPTLock}); + env(offer(alice, btc(1), eth(1)), Ter(tecUNFUNDED_OFFER)); + eth.set({.holder = alice, .flags = tfMPTUnlock}); + // carol buys locked ETH (takerPays): locked MPToken cannot receive + eth.set({.holder = carol, .flags = tfMPTLock}); + env(offer(carol, eth(1), btc(1)), Ter(tecLOCKED)); + eth.set({.holder = carol, .flags = tfMPTUnlock}); + // gw is ETH issuer: individual lock on holders does not affect issuer + env(offer(gw, btc(1), eth(1))); - test(tfMPTLock); - test(tfMPTLock, true); - test(tfMPTUnlock); + // --- Global lock on BTC --- + // All accounts fail regardless of role: global lock is checked in OfferCreate + // before offer crossing, so it applies even to the issuer. + btc.set({.flags = tfMPTLock}); + env(offer(alice, eth(1), btc(1)), Ter(tecLOCKED)); // alice sells BTC + env(offer(alice, btc(1), eth(1)), Ter(tecLOCKED)); // alice buys BTC + env(offer(gw2, eth(1), btc(1)), Ter(tecLOCKED)); // gw2 is BTC issuer, still fails + btc.set({.flags = tfMPTUnlock}); + + // --- Global lock on ETH --- + eth.set({.flags = tfMPTLock}); + env(offer(alice, btc(1), eth(1)), Ter(tecLOCKED)); // alice sells ETH + env(offer(alice, eth(1), btc(1)), Ter(tecLOCKED)); // alice buys ETH + env(offer(gw, btc(1), eth(1)), Ter(tecLOCKED)); // gw is ETH issuer, still fails + eth.set({.flags = tfMPTUnlock}); + + // --- After all locks cleared: normal crossing succeeds --- + env(offer(alice, eth(1), btc(1))); + env(offer(carol, btc(1), eth(1))); } // MPTRequireAuth flag is set and the account is not authorized @@ -3823,6 +3850,7 @@ class MPToken_test : public beast::unit_test::Suite testcase("Cross Asset Payment"); using namespace test::jtx; Account const gw = Account("gw"); + Account const gw2 = Account("gw2"); Account const alice = Account("alice"); Account const carol = Account("carol"); Account const bob = Account("bob"); @@ -3999,6 +4027,7 @@ class MPToken_test : public beast::unit_test::Suite env(pay(ed, gw, btc(10)), Path(~btc), Sendmax(eth(10))); // BTC is transferred from issuer to bob env(pay(gw, ed, eth(10)), Path(~eth), Sendmax(btc(10))); + // // BTC is transferred from ed to bob, ed is not authorized env(pay(ed, gw, eth(10)), Path(~eth), Sendmax(btc(10)), Ter(tecNO_AUTH)); env.close(); @@ -4013,13 +4042,11 @@ class MPToken_test : public beast::unit_test::Suite env(pay(carol, ed, btc(10)), Path(~btc), Sendmax(eth(10)), Ter(tecPATH_PARTIAL)); env(pay(ed, carol, eth(10)), Path(~eth), Sendmax(btc(10)), Ter(tecPATH_PARTIAL)); env(pay(carol, ed, eth(10)), Path(~eth), Sendmax(btc(10)), Ter(tecPATH_PARTIAL)); - // Fail because BTC, which has CanTransfer disabled, is sent to - // bob + // Fail because BTC, which has CanTransfer disabled, is sent to bob env(pay(ed, gw, eth(10)), Path(~eth), Sendmax(btc(10)), Ter(tecPATH_PARTIAL)); env(pay(ed, gw, btc(10)), Path(~btc), Sendmax(eth(10)), Ter(tesSUCCESS)); env(pay(gw, ed, eth(10)), Path(~eth), Sendmax(btc(10)), Ter(tesSUCCESS)); - // Fail because BTC, which has CanTransfer disabled, is sent to - // ed + // Fail because BTC, which has CanTransfer disabled, is sent to ed env(pay(gw, ed, btc(10)), Path(~btc), Sendmax(eth(10)), Ter(tecPATH_PARTIAL)); env.close(); env(offer(gw, eth(100), btc(100)), Txflags(tfPassive)); @@ -4194,7 +4221,17 @@ class MPToken_test : public beast::unit_test::Suite env(pay(gw, carol, usd(1)), Path(~btc, ~eth, ~usd), Sendmax(XRP(1)), - Ter(tecPATH_PARTIAL)); + Txflags(tfPartialPayment | tfNoRippleDirect), + Ter(tecNO_PERMISSION)); + env.close(); + BEAST_EXPECT(expectOffers(env, bob, 3)); + + env(pay(carol, bob, btc(10)), Sendmax(XRP(10)), Ter(tecNO_PERMISSION)); + env(pay(carol, bob, XRP(10)), Sendmax(btc(10)), Ter(tecNO_PERMISSION)); + env(pay(gw, bob, btc(10)), Sendmax(XRP(10)), Ter(tecNO_PERMISSION)); + env(pay(gw, bob, XRP(10)), Sendmax(btc(10)), Ter(tecNO_PERMISSION)); + env(pay(carol, gw, btc(10)), Sendmax(XRP(10)), Ter(tecNO_PERMISSION)); + env(pay(carol, gw, XRP(10)), Sendmax(btc(10)), Ter(tecNO_PERMISSION)); env.close(); BEAST_EXPECT(expectOffers(env, bob, 3)); } @@ -4229,31 +4266,36 @@ class MPToken_test : public beast::unit_test::Suite auto getMPT = [&](Env& env) { MPTTester const btc( {.env = env, - .issuer = gw, - .holders = {alice, carol, bob}, + .issuer = gw2, + .holders = {alice, carol, bob, gw}, .pay = 100, .flags = tfMPTCanLock | kMptDexFlags}); MPTTester const eth( {.env = env, .issuer = gw, - .holders = {alice, carol, bob}, + .holders = {alice, carol, bob, gw2}, .pay = 100, .flags = tfMPTCanLock | kMptDexFlags}); return std::make_pair(btc, eth); }; auto getIOU = [&](Env& env) { - for (auto const& iou : {gw["BTC"], gw["ETH"]}) + for (auto const& a : {alice, carol, bob}) { - for (auto const& a : {alice, carol, bob}) - { - env(fset(a, asfDefaultRipple)); - env.close(); - env(trust(a, iou(200))); - env(pay(gw, a, iou(100))); - env.close(); - } + env(fset(a, asfDefaultRipple)); + env.close(); + env(trust(a, gw["ETH"](200))); + env(pay(gw, a, gw["ETH"](100))); + env(trust(a, gw2["BTC"](200))); + env(pay(gw2, a, gw2["BTC"](100))); + env.close(); } - return std::make_pair(gw["BTC"], gw["ETH"]); + // gw2 needs an ETH trust line to receive ETH when its offers fill + // gw needs BTC to sell BTC + env(trust(gw2, gw["ETH"](200))); + env(trust(gw, gw2["BTC"](200))); + env(pay(gw2, gw, gw2["BTC"](100))); + env.close(); + return std::make_pair(gw2["BTC"], gw["ETH"]); }; auto lock = [&]( Env& env, Account const& account, Token& token, LockType lock) { @@ -4263,12 +4305,12 @@ class MPToken_test : public beast::unit_test::Suite { if (lock == LockType::Global) { - env(fset(gw, asfGlobalFreeze)); + env(fset(token.account, asfGlobalFreeze)); } else { IOU const iou{account, token.currency}; - env(trust(gw, iou(0), tfSetFreeze)); + env(trust(token.account, iou(0), tfSetFreeze)); } } else if constexpr (std::is_same_v) @@ -4285,7 +4327,7 @@ class MPToken_test : public beast::unit_test::Suite }; auto test = [&](auto&& getTokens, TestArg const& arg) { Env env(*this); - env.fund(XRP(1'000), gw, alice, carol, bob); + env.fund(XRP(1'000), gw, gw2, alice, carol, bob); auto [btc, eth] = getTokens(env); @@ -4303,7 +4345,7 @@ class MPToken_test : public beast::unit_test::Suite } if (arg.globalFlagSell != LockType::None) { - lock(env, gw, btc, LockType::Global); + lock(env, gw2, btc, LockType::Global); } else { @@ -4321,34 +4363,58 @@ class MPToken_test : public beast::unit_test::Suite }; // clang-format off std::vector const tests = { - // src, dst, offer's owner are a holder - {.src = alice, .dst = carol, .offerOwner = bob, .srcFlag = LockType::Individual, .err = tecPATH_DRY}, - // dst can receive IOU even if the account is frozen - {.src = alice, .dst = carol, .offerOwner = bob, .dstFlag = LockType::Individual, .err = tecPATH_DRY, .errIOU = tesSUCCESS}, - {.src = alice, .dst = carol, .offerOwner = bob, .globalFlagBuy = LockType::Global, .err = tecPATH_DRY}, - {.src = alice, .dst = carol, .offerOwner = bob, .globalFlagSell = LockType::Global, .err = tecPATH_DRY}, - // offer's owner can receive IOU even if the account is frozen - {.src = alice, .dst = carol, .offerOwner = bob, .offerFlagBuy = LockType::Individual, .err = - tecPATH_PARTIAL, .errIOU = tesSUCCESS}, - {.src = alice, .dst = carol, .offerOwner = bob, .offerFlagSell = LockType::Individual, .err = tecPATH_PARTIAL}, - // src, dst are a holder, offer's owner is an issuer - {.src = alice, .dst = carol, .offerOwner = gw, .srcFlag = LockType::Individual, .err = tecPATH_DRY}, - // dst can receive IOU even if the account is frozen - {.src = alice, .dst = carol, .offerOwner = gw, .dstFlag = LockType::Individual, .err = tecPATH_DRY, .errIOU = tesSUCCESS}, - {.src = alice, .dst = carol, .offerOwner = gw, .globalFlagBuy = LockType::Global, .err = tecPATH_DRY}, - {.src = alice, .dst = carol, .offerOwner = gw, .globalFlagSell = LockType::Global, .err = tecPATH_DRY}, - // src is issuer, dst and offer's owner are a holder - // dst can receive IOU even if the account is frozen - {.src = gw, .dst = carol, .offerOwner = bob, .dstFlag = LockType::Individual, .err = tecPATH_DRY, .errIOU = tesSUCCESS}, - // offer's owner can receive IOU from an issuer even if takerBuys is frozen, MPT offer is unfunded in this case - {.src = gw, .dst = carol, .offerOwner = bob, .offerFlagBuy = LockType::Individual, .err = tecPATH_PARTIAL, .errIOU = tesSUCCESS}, - {.src = gw, .dst = carol, .offerOwner = bob, .offerFlagSell = LockType::Individual, .err = tecPATH_PARTIAL}, - // dst is issuer, src and offer's owner are a holder - {.src = alice, .dst = gw, .offerOwner = bob, .srcFlag = LockType::Individual, .err = tecPATH_DRY}, - // offer's owner can receive IOU even if the account is frozen - {.src = alice, .dst = gw, .offerOwner = bob, .offerFlagBuy = LockType::Individual, .err = tecPATH_PARTIAL, - .errIOU = tesSUCCESS}, - {.src = alice, .dst = gw, .offerOwner = bob, .offerFlagSell = LockType::Individual, .err = tecPATH_PARTIAL}, + // ----- src=alice (holder), dst=carol (holder), offerOwner=bob (holder) ----- + // alice's ETH locked: caught in check() + {.src = alice, .dst = carol, .offerOwner = bob, .srcFlag = LockType::Individual, .err = tecPATH_DRY}, + // carol's BTC locked: caught in MPT check(); IOU dst can still receive when frozen + {.src = alice, .dst = carol, .offerOwner = bob, .dstFlag = LockType::Individual, .err = tecPATH_DRY, .errIOU = tesSUCCESS}, + // ETH globally locked: caught in check() + {.src = alice, .dst = carol, .offerOwner = bob, .globalFlagBuy = LockType::Global, .err = tecPATH_DRY}, + // BTC globally locked: bob's offer unfunded in OfferStream + {.src = alice, .dst = carol, .offerOwner = bob, .globalFlagSell = LockType::Global, .err = tecPATH_PARTIAL}, + // bob's ETH (takerPays) locked: MPT offer unfunded in OfferStream (locked holder cannot receive); IOU can still receive + {.src = alice, .dst = carol, .offerOwner = bob, .offerFlagBuy = LockType::Individual, .err = tecPATH_PARTIAL, .errIOU = tesSUCCESS}, + // bob's BTC (takerGets) locked: offer unfunded in OfferStream + {.src = alice, .dst = carol, .offerOwner = bob, .offerFlagSell = LockType::Individual, .err = tecPATH_PARTIAL}, + // ----- src=alice (holder), dst=carol (holder), offerOwner=gw2 (BTC issuer) ----- + // alice's ETH locked: caught in check() + {.src = alice, .dst = carol, .offerOwner = gw2, .srcFlag = LockType::Individual, .err = tecPATH_DRY}, + // carol's BTC locked: caught in MPT check(); IOU dst can still receive when frozen + {.src = alice, .dst = carol, .offerOwner = gw2, .dstFlag = LockType::Individual, .err = tecPATH_DRY, .errIOU = tesSUCCESS}, + // ETH globally locked: caught in check() + {.src = alice, .dst = carol, .offerOwner = gw2, .globalFlagBuy = LockType::Global, .err = tecPATH_DRY}, + // BTC globally locked: gw2 is the BTC issuer, offer always permitted regardless of global freeze + {.src = alice, .dst = carol, .offerOwner = gw2, .globalFlagSell = LockType::Global, .err = tesSUCCESS}, + // ----- src=alice (holder), dst=carol (holder), offerOwner=gw (ETH issuer, BTC holder) ----- + // alice's ETH locked: caught in check() + {.src = alice, .dst = carol, .offerOwner = gw, .srcFlag = LockType::Individual, .err = tecPATH_DRY}, + // carol's BTC locked: caught in MPT check(); IOU dst can still receive when frozen + {.src = alice, .dst = carol, .offerOwner = gw, .dstFlag = LockType::Individual, .err = tecPATH_DRY, .errIOU = tesSUCCESS}, + // ETH globally locked: caught in check() + {.src = alice, .dst = carol, .offerOwner = gw, .globalFlagBuy = LockType::Global, .err = tecPATH_DRY}, + // BTC globally locked: gw holds BTC as a holder (not BTC issuer), offer unfunded in OfferStream + {.src = alice, .dst = carol, .offerOwner = gw, .globalFlagSell = LockType::Global, .err = tecPATH_PARTIAL}, + // ----- src=gw (ETH issuer), dst=carol (holder), offerOwner=bob (holder) ----- + // ETH globally locked, src is ETH issuer: no first MPTEndpointStep so check() passes; + // MPT offer unfunded in OfferStream (globally-locked ETH cannot flow to holder via DEX); IOU issuer can still issue + {.src = gw, .dst = carol, .offerOwner = bob, .srcFlag = LockType::Global, .err = tecPATH_PARTIAL, .errIOU = tesSUCCESS}, + // BTC globally locked: last MPTEndpointStep only checks individual freeze, check() passes; offer unfunded in OfferStream + {.src = gw, .dst = carol, .offerOwner = bob, .dstFlag = LockType::Global, .err = tecPATH_PARTIAL}, + // carol's BTC locked: caught in MPT check(); IOU dst can still receive when frozen + {.src = gw, .dst = carol, .offerOwner = bob, .dstFlag = LockType::Individual, .err = tecPATH_DRY, .errIOU = tesSUCCESS}, + // bob's ETH (takerPays) locked: MPT offer unfunded in OfferStream (locked holder cannot receive); IOU can still receive + {.src = gw, .dst = carol, .offerOwner = bob, .offerFlagBuy = LockType::Individual, .err = tecPATH_PARTIAL, .errIOU = tesSUCCESS}, + // bob's BTC (takerGets) locked: offer unfunded in OfferStream + {.src = gw, .dst = carol, .offerOwner = bob, .offerFlagSell = LockType::Individual, .err = tecPATH_PARTIAL}, + // ----- src=alice (holder), dst=gw2 (BTC issuer), offerOwner=bob (holder) ----- + // alice's ETH locked: caught in check() + {.src = alice, .dst = gw2, .offerOwner = bob, .srcFlag = LockType::Individual, .err = tecPATH_DRY}, + // BTC globally locked, dst is BTC issuer: no last MPTEndpointStep so check() passes; offer unfunded in OfferStream + {.src = alice, .dst = gw2, .offerOwner = bob, .dstFlag = LockType::Global, .err = tecPATH_PARTIAL}, + // bob's ETH (takerPays) locked: MPT offer unfunded in OfferStream (locked holder cannot receive); IOU can still receive + {.src = alice, .dst = gw2, .offerOwner = bob, .offerFlagBuy = LockType::Individual, .err = tecPATH_PARTIAL, .errIOU = tesSUCCESS}, + // bob's BTC (takerGets) locked: offer unfunded in OfferStream + {.src = alice, .dst = gw2, .offerOwner = bob, .offerFlagSell = LockType::Individual, .err = tecPATH_PARTIAL}, }; // clang-format on @@ -5949,7 +6015,7 @@ class MPToken_test : public beast::unit_test::Suite env.close(); } - // MPTCanTransfer disabled + // MPTCanTransfer is disabled { Env env{*this, features}; env.fund(XRP(1'000), gw, alice, carol); @@ -5990,49 +6056,50 @@ class MPToken_test : public beast::unit_test::Suite BEAST_EXPECT(env.balance(alice, mpt) == mpt(0)); BEAST_EXPECT(env.balance(gw, mpt) == mpt(0)); - // neither src nor dst is issuer, can still create + // neither src nor dst is issuer, can't create + checkId = keylet::check(alice, env.seq(alice)).key; + env(check::create(alice, carol, mpt(100)), Ter(tecNO_AUTH)); + env.close(); + + // can create now + mpt.set({.account = gw, .mutableFlags = tmfMPTSetCanTransfer}); checkId = keylet::check(alice, env.seq(alice)).key; env(check::create(alice, carol, mpt(100))); env.close(); - - // can't cash - env(check::cash(carol, checkId, mpt(10)), Ter(tecPATH_PARTIAL)); - env.close(); - - // can cash now - mpt.set({.account = gw, .mutableFlags = tmfMPTSetCanTransfer}); env(pay(gw, alice, mpt(10))); env.close(); + // can't cash + mpt.set({.account = gw, .mutableFlags = tmfMPTClearCanTransfer}); + env.close(); + env(check::cash(carol, checkId, mpt(10)), Ter(tecNO_AUTH)); + env.close(); + // can cash + mpt.set({.account = gw, .mutableFlags = tmfMPTSetCanTransfer}); env(check::cash(carol, checkId, mpt(10))); env.close(); } - // MPTCanTrade disabled + // MPTCanTrade is disabled { Env env{*this, features}; env.fund(XRP(1'000), gw, alice, carol); env.close(); - MPTTester mpt( + MPT const mpt = MPTTester( {.env = env, .issuer = gw, .holders = {alice, carol}, - .flags = tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateCanTrade}); + .pay = 10, + .flags = tfMPTCanTransfer}); - uint256 checkId{keylet::check(gw, env.seq(gw)).key}; + uint256 const checkId{keylet::check(alice, env.seq(alice)).key}; - // can't create - env(check::create(gw, alice, mpt(100)), Ter(tecNO_PERMISSION)); + // can create + env(check::create(alice, carol, mpt(100))); env.close(); - mpt.set({.account = gw, .mutableFlags = tmfMPTSetCanTrade}); - // can't cash - checkId = keylet::check(gw, env.seq(gw)).key; - env(check::create(gw, carol, mpt(100))); - env.close(); - mpt.set({.account = gw, .mutableFlags = tmfMPTClearCanTrade}); - env(check::cash(carol, checkId, mpt(10)), Ter(tecNO_PERMISSION)); + // can cash + env(check::cash(carol, checkId, mpt(10))); env.close(); } @@ -6087,33 +6154,6 @@ class MPToken_test : public beast::unit_test::Suite env.close(); } - // MPTCanTransfer is not set and the account is not the issuer of MPT - { - Env env{*this, features}; - env.fund(XRP(1'000), gw, alice, carol); - - auto eur = MPTTester( - {.env = env, .issuer = gw, .holders = {alice, carol}, .flags = tfMPTCanTrade}); - uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; - // alice can create - env(check::create(alice, carol, eur(1))); - env.close(); - - // carol can't cash - env(check::cash(carol, chkId, eur(1)), Ter(tecPATH_PARTIAL)); - env.close(); - - // if issuer creates a check then carol can cash since - // it's a transfer from the issuer - uint256 const chkId1{getCheckIndex(gw, env.seq(gw))}; - // alice can't create since CanTransfer is not set - env(check::create(gw, carol, eur(1))); - env.close(); - - env(check::cash(carol, chkId1, eur(1))); - env.close(); - } - // Can create check if src/dst don't own MPT { Env env{*this, features}; @@ -6274,8 +6314,7 @@ class MPToken_test : public beast::unit_test::Suite AMM amm(env, gw, btc(100), usd(100)); env.close(); // alice can't deposit since MPTCanTransfer is not set - amm.deposit( - DepositArg{.account = alice, .tokens = 1'000, .err = Ter(tecNO_PERMISSION)}); + amm.deposit(DepositArg{.account = alice, .tokens = 1'000, .err = Ter(tecNO_AUTH)}); env.close(); // can't clawback since alice is not an LP @@ -6508,8 +6547,8 @@ class MPToken_test : public beast::unit_test::Suite // alice and issuer can't create usd.set({.flags = tfMPTLock}); - createFail(alice, tecFROZEN); - createFail(gw, tecFROZEN); + createFail(alice, tecLOCKED); + createFail(gw, tecLOCKED); // MPTRequireAuth is set @@ -6529,7 +6568,7 @@ class MPToken_test : public beast::unit_test::Suite usd.set({.mutableFlags = tmfMPTClearRequireAuth}); usd.set({.mutableFlags = tmfMPTClearCanTransfer}); // alice can't create - createFail(alice, tecNO_PERMISSION); + createFail(alice, tecNO_AUTH); // issuer can create createDeleteAMM(gw); usd.set({.mutableFlags = tmfMPTSetCanTransfer}); @@ -6575,12 +6614,12 @@ class MPToken_test : public beast::unit_test::Suite {.account = account, .asset1In = usd(1), .asset2In = eur(1), - .err = Ter(tecFROZEN)}); + .err = Ter(tecLOCKED)}); amm.deposit( {.account = account, .asset1In = eur(1), .assets = std::make_pair(eur, usd), - .err = Ter(tecFROZEN)}); + .err = Ter(tecLOCKED)}); } usd.set({.flags = tfMPTUnlock}); @@ -6593,8 +6632,6 @@ class MPToken_test : public beast::unit_test::Suite eur.authorize({.account = carol}); env(pay(gw, carol, eur(1'000'000))); usd.set({.mutableFlags = tmfMPTSetRequireAuth}); - // have to authorize amm account - usd.authorize({.account = gw, .holder = Account{"amm", amm.ammAccount()}}); env.close(); amm.deposit( {.account = carol, .asset1In = usd(1), .asset2In = eur(1), .err = Ter(tecNO_AUTH)}); @@ -6608,6 +6645,16 @@ class MPToken_test : public beast::unit_test::Suite // carol is authorized, can deposit usd.authorize({.account = gw, .holder = carol}); amm.deposit({.account = carol, .tokens = 1'000}); + // Can't authorize or unauthorize AMM pseudo-account + usd.authorize( + {.account = gw, + .holder = Account{"amm", amm.ammAccount()}, + .err = tecNO_PERMISSION}); + usd.authorize( + {.account = gw, + .holder = Account{"amm", amm.ammAccount()}, + .flags = tfMPTUnauthorize, + .err = tecNO_PERMISSION}); // MPTCanTransfer is not set @@ -6615,15 +6662,12 @@ class MPToken_test : public beast::unit_test::Suite usd.set({.mutableFlags = tmfMPTClearCanTransfer}); // carol can't deposit amm.deposit( - {.account = carol, - .asset1In = usd(1), - .asset2In = eur(1), - .err = Ter(tecNO_PERMISSION)}); + {.account = carol, .asset1In = usd(1), .asset2In = eur(1), .err = Ter(tecNO_AUTH)}); amm.deposit( {.account = carol, .asset1In = eur(1), .assets = std::make_pair(eur, usd), - .err = Ter(tecNO_PERMISSION)}); + .err = Ter(tecNO_AUTH)}); // issuer can deposit amm.deposit({.account = gw, .tokens = 1'000}); // carol can deposit @@ -6665,8 +6709,8 @@ class MPToken_test : public beast::unit_test::Suite {.account = account, .asset1Out = usd(1), .asset2Out = eur(1), - .err = Ter(tecFROZEN)}); - amm.withdraw({.account = account, .tokens = 1'000, .err = Ter(tecFROZEN)}); + .err = Ter(tecLOCKED)}); + amm.withdraw({.account = account, .tokens = 1'000, .err = Ter(tecLOCKED)}); // can single withdraw another asset amm.withdraw( {.account = account, .asset1Out = eur(1), .assets = std::make_pair(eur, usd)}); @@ -6692,29 +6736,38 @@ class MPToken_test : public beast::unit_test::Suite usd.authorize({.account = gw, .holder = carol}); amm.withdraw({.account = carol, .asset1Out = usd(1), .asset2Out = eur(1)}); - // MPTCanTransfer is set + // MPTCanTransfer is not set, allow to withdraw usd.set({.mutableFlags = tmfMPTClearRequireAuth}); usd.set({.mutableFlags = tmfMPTClearCanTransfer}); - // carol can't withdraw - amm.withdraw( - {.account = carol, - .asset1Out = usd(1), - .asset2Out = eur(1), - .err = Ter(tecNO_PERMISSION)}); + // carol can withdraw + amm.withdraw({.account = carol, .asset1Out = usd(1), .asset2Out = eur(1)}); // can withdraw another asset amm.withdraw( {.account = carol, .asset1Out = eur(1), .assets = std::make_pair(eur, usd)}); // issuer can withdraw amm.withdraw({.account = gw, .asset1Out = usd(1), .asset2Out = eur(1)}); + // Holder can't transfer to another holder + env.fund(XRP(1'000), bob); + usd.authorize({.account = bob}); + env(pay(carol, bob, usd(1)), Ter(tecNO_AUTH)); + usd.authorize({.account = bob, .flags = tfMPTUnauthorize}); + // Can redeem + env(pay(carol, gw, usd(1))); // carol can withdraw usd.set({.mutableFlags = tmfMPTSetCanTransfer}); amm.withdraw({.account = carol, .asset1Out = usd(1), .asset2Out = eur(1)}); usd.set({.mutableFlags = tmfMPTSetCanTransfer}); + + // MPTCanTrade is not set, allow to withdraw + usd.set({.mutableFlags = tmfMPTClearCanTrade}); - amm.withdraw({.account = gw, .tokens = 1'000, .err = Ter(tecNO_PERMISSION)}); - amm.withdraw({.account = carol, .tokens = 1'000, .err = Ter(tecNO_PERMISSION)}); + amm.withdraw({.account = gw, .tokens = 1'000}); + amm.withdraw({.account = carol, .tokens = 1'000}); + // Can't DEX + amm.deposit( + DepositArg{.account = carol, .asset1In = usd(1), .err = Ter(tecNO_PERMISSION)}); usd.set({.mutableFlags = tmfMPTSetCanTrade}); // MPToken created on withdraw @@ -6733,6 +6786,99 @@ class MPToken_test : public beast::unit_test::Suite } } + void + testFixDoubleOwnerCount(FeatureBitset all) + { + testcase("Fix Double adjustOwnerCount in AMMWithdraw"); + + using namespace jtx; + + // Carol deposits XRP into an XRP/MPT pool, then withdraws MPT. + // Carol has no MPToken before the withdrawal. If the bug exists, + // her ownerCount will be inflated by +1 extra. + Account const gw{"gw"}; + Account const alice{"alice"}; + Account const carol{"carol"}; + Env env(*this, all); + env.fund(XRP(30'000), gw, alice, carol); + env.close(); + + // Create MPT with DEX flags. Only alice is a holder initially. + MPT const btc = MPTTester( + {.env = env, .issuer = gw, .holders = {alice}, .pay = 20'000, .flags = kMptDexFlags}); + + // Alice creates XRP/MPT AMM pool + AMM amm(env, alice, XRP(10'000), btc(10'000)); + + // Carol deposits XRP (single asset) into the pool. + // Carol gets LP tokens but does NOT have an MPToken yet. + auto const carolOwnersBefore = ownerCount(env, carol); + amm.deposit(carol, XRP(1'000), std::nullopt, std::nullopt, tfSingleAsset); + auto const carolOwnersAfterDeposit = ownerCount(env, carol); + // Carol should have +1 for LP token trustline + BEAST_EXPECT(carolOwnersAfterDeposit == carolOwnersBefore + 1); + + auto const carolOwnersBeforeWithdraw = ownerCount(env, carol); + // Carol withdraws single MPT asset. She doesn't have an MPToken, + // so one must be created. Bug: ownerCount incremented twice. + amm.withdraw({.account = carol, .asset1Out = btc(100), .flags = tfSingleAsset}); + auto const carolOwnersAfterWithdraw = ownerCount(env, carol); + + // Expected: +1 for the new MPToken (so total increase = 1) + BEAST_EXPECT(carolOwnersAfterWithdraw == carolOwnersBeforeWithdraw + 1); + } + + void + testTradeAndTransfer() + { + using namespace jtx; + testcase("Trade and Transfer"); + + // Verify canMPTTradeAndTransfer validates the flags when from == to and from != to + + Account const gw{"gw"}; + Account const alice{"alice"}; + Account const carol{"carol"}; + Env env(*this); + env.fund(XRP(1'000), gw, alice, carol); + + MPTTester mpt( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 100, + .flags = kMptDexFlags, + .mutableFlags = tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanTrade}); + + // Both flags are enabled + BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, gw, gw))); + BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, gw, alice))); + BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, alice, alice))); + BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, alice, carol))); + + // MPTCanTrade is disabled + mpt.set({.mutableFlags = tmfMPTClearCanTrade}); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, gw) == tecNO_PERMISSION); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, alice) == tecNO_PERMISSION); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, alice) == tecNO_PERMISSION); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, carol) == tecNO_PERMISSION); + + // MPTCanTransfer is disabled + mpt.set({.mutableFlags = tmfMPTSetCanTrade}); + mpt.set({.mutableFlags = tmfMPTClearCanTransfer}); + BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, gw, gw))); + BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, gw, alice))); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, alice) == tecNO_AUTH); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, carol) == tecNO_AUTH); + + // Both flags are disabled + mpt.set({.mutableFlags = tmfMPTClearCanTrade}); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, gw) == tecNO_PERMISSION); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, alice) == tecNO_PERMISSION); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, alice) == tecNO_PERMISSION); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, carol) == tecNO_PERMISSION); + } + public: void run() override @@ -6839,6 +6985,12 @@ public: // Test AMM testBasicAMM(all); + + // Test Trade/Transfer + testTradeAndTransfer(); + + // Fixes + testFixDoubleOwnerCount(all); } }; diff --git a/src/test/app/OfferMPT_test.cpp b/src/test/app/OfferMPT_test.cpp index a52ba7fd58..3f88e57bc7 100644 --- a/src/test/app/OfferMPT_test.cpp +++ b/src/test/app/OfferMPT_test.cpp @@ -2764,7 +2764,7 @@ public: // USD(125) was removed from his account due to the gateway fee. // // A comparable payment would look like this: - // env (pay (bob, alice, USD(100)), sendmax(USD(125))) + // env (pay (bob, alice, USD(100)), Sendmax(USD(125))) env(offer(bob, XRP(1), usd(10'000))); env.close(); @@ -3012,6 +3012,92 @@ public: } }; testHelper2TokensMix(test); + + // Payment trIn: MPT transfer fee must be charged when the payment + // destination is the MPT issuer and MPT crosses the DEX (1-hop). + // Bug: rate() returned parity because strandDst_ == MPT issuer. + // Fix: parity only when this asset IS the final delivered asset. + { + auto const gw = Account("gw_tr1"); + auto const alice = Account("alice_tr1"); + auto const bob = Account("bob_tr1"); + + Env env{*this, features}; + env.fund(XRP(10'000), gw, alice, bob); + env.close(); + + MPT const usd = MPTTester( + {.env = env, .issuer = gw, .holders = {alice, bob}, .transferFee = 25'000}); + + // alice needs MPT(1250): MPT(1000) to bob's offer + MPT(250) transfer fee (25%) + env(pay(gw, alice, usd(1'250))); + // bob's offer: give XRP(1000), want MPT(1000) + env(offer(bob, usd(1'000), XRP(1'000))); + env.close(); + + // alice pays gw (MPT issuer) XRP(1000) using MPT as source + // strand: alice -> [MPT/XRP BookStep] -> gw + // strandDst_ = gw = MPT issuer, strandDeliver_ = XRP + // trIn = rate(MPT, gw): fix charges 25% (MPT != strandDeliver_) + env(pay(alice, gw, XRP(1'000)), Path(~XRP), Sendmax(usd(1'250))); + env.close(); + + // alice consumed all MPT(1250): MPT(1000) to bob + MPT(250) fee + BEAST_EXPECT(env.balance(alice, usd) == usd(0)); + // bob received MPT(1000) net + BEAST_EXPECT(env.balance(bob, usd) == usd(1'000)); + } + + // Payment trIn (2-hop): MPT transfer fee must be charged when MPT is + // intermediate and the destination is the MPT issuer. + // BookStep2(MPT/XRP) prevStep=BookStep1 returns redeems direction + // (ownerPaysTransferFee_=false for Payment), so trIn applies. + // Bug: parity because strandDst_ == MPT issuer. + // Fix: 25% fee because MPT != strandDeliver_(XRP). + { + auto const gw = Account("gw_tr2"); + auto const gw2 = Account("gw2_tr2"); + auto const alice = Account("alice_tr2"); + auto const bob = Account("bob_tr2"); + auto const carol = Account("carol_tr2"); + + Env env{*this, features}; + env.fund(XRP(10'000), gw, gw2, alice, bob, carol); + env.close(); + + MPT const musd = MPTTester( + {.env = env, .issuer = gw, .holders = {bob, carol}, .transferFee = 25'000}); + auto const gusd = gw2["USD"]; + + env(trust(alice, gusd(10'000))); + env(trust(bob, gusd(10'000))); + env.close(); + + env(pay(gw2, alice, gusd(1'000))); + env(pay(gw, bob, musd(1'000))); + env.close(); + + // bob's offer: give MPT(1000), want GUSD(1000) + env(offer(bob, gusd(1'000), musd(1'000))); + // carol's offer: give XRP(800), want MPT(800) + env(offer(carol, musd(800), XRP(800))); + env.close(); + + // Payment: alice GUSD -> [BookStep1: GUSD/MUSD] -> [BookStep2: MUSD/XRP] -> gw XRP + // strandDst_ = gw = MPT issuer, strandDeliver_ = XRP + // BookStep2 trIn: fix = 1.25 -> upstream needs MUSD(1000) for carol's MUSD(800) offer + // => alice must provide full GUSD(1000) to bob's offer; without fix alice only pays + // GUSD(800) + env(pay(alice, gw, XRP(800)), Path(~musd), Sendmax(gusd(1'000))); + env.close(); + + // alice spent all GUSD(1000); bug would leave GUSD(200) unspent + BEAST_EXPECT(env.balance(alice, gusd) == gusd(0)); + // bob gave MPT(1000) and received GUSD(1000) + BEAST_EXPECT(env.balance(bob, musd) == musd(0)); + // carol received MPT(800) net (MPT(200) went to gw as fee) + BEAST_EXPECT(env.balance(carol, musd) == musd(800)); + } } void @@ -4707,6 +4793,101 @@ public: } } + void + testAutoCreateReserve(FeatureBitset features) + { + // When an offer on the book is partially crossed, the payment engine + // auto-creates a new ledger object (MPToken or IOU trustline) for the + // offer owner to hold the incoming asset. This happens inside + // BookStep::forEachOffer (MPT: checkCreateMPT) and BookStep::consumeOffer + // (IOU: directSendNoFeeIOU -> trustCreate) without a reserve sufficiency + // check. The offer owner can therefore end up with more objects than + // their XRP balance can reserve for, consistent with IOU behavior. + + testcase("Auto-Create Object Without Reserve Check During Partial Crossing"); + + using namespace jtx; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const carol = Account{"carol"}; + auto const bob = Account{"bob"}; + + auto test = [&](auto&& getToken, auto&& execTx) { + // MPT/IOU: carol's existing offer buys MPT/IOU by selling XRP. + // carol has no MPToken/Trustline for this issuance. When alice partially crosses + // carol's offer, an MPToken/Trustline is auto-created for carol without checking + // that she can afford the extra reserve slot. + Env env{*this, features}; + + auto const f = env.current()->fees().base; + auto const r = reserve(env, 0); + auto const inc = reserve(env, 1) - r; + + env.fund(XRP(10'000), gw, alice, bob); + + // getToken: + // - Create MPT with CanTransfer + CanTrade; authorize alice as holder. + // - Create IOU trustline + auto const token = getToken(env); + + // carol: reserve(0) + 1 increment + fee covers placing one offer. + // After the offer tx she has exactly reserve(1) + XRP(30). + // XRP(30) < inc (50 XRP), so receiving a second object will put her + // below reserve(2). + if (BEAST_EXPECT(inc > XRP(30))) + env.fund(r + inc + f + XRP(30), carol); + + // carol's offer goes on the book (no counterpart yet). + // TakerPays=Token(30): carol will receive Token when crossed. + // TakerGets=XRP(30): carol will give XRP when crossed. + env(offer(carol, token(30), XRP(30))); + env.require(Owners(carol, 1)); + + // Execute offer create or cross-currency payment + // alice partially crosses carol's offer. + // alice sends Token(15) to carol and receives XRP(15). + // Token: + // - MPT: checkCreateMPT auto-creates an MPToken for carol (no reserve check). + // - IOU: directSendNoFeeIOU auto-creates an Trustline for carol (no reserve check). + execTx(env, token); + + // Carol now owns 2 objects (remaining offer + new MPToken) even + // though her XRP balance is only reserve(1) + XRP(15), which is + // below reserve(2) = reserve(1) + inc. + auto const carolBalance = r + inc + XRP(15); + env.require(Owners(carol, 2), Balance(carol, token(15)), Balance(carol, carolBalance)); + BEAST_EXPECT(carolBalance < r + 2 * inc); // below reserve(2) + }; + std::function const getIOU = [&](Env& env) -> PrettyAsset { + env.trust(gw["USD"](1'000), alice); + env(pay(gw, alice, gw["USD"](100))); + return gw["USD"]; + }; + std::function const getMPT = [&](Env& env) -> PrettyAsset { + MPT const mpT1 = MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 100}); + return mpT1; + }; + for (auto&& getToken : {getIOU, getMPT}) + { + test(getToken, [&](Env& env, PrettyAsset const& token) { + // alice partially crosses carol's offer. + // alice sends Token(15) to carol and receives XRP(15). + // Token is MPT: checkCreateMPT auto-creates an MPToken for carol (no reserve + // check). Token is IOU: directSendNoFeeIOU auto-creates a trustline for carol (no + // reserve check). + env(offer(alice, XRP(15), token(15))); + }); + test(getToken, [&](Env& env, PrettyAsset const& token) { + // Similar to above but with cross-currency payment. + env(pay(alice, bob, XRP(15)), + Sendmax(token(15)), + Path(~XRP), + Txflags(tfNoRippleDirect | tfPartialPayment)); + }); + } + } + void testAll(FeatureBitset features) { @@ -4763,6 +4944,7 @@ public: testRmSmallIncreasedQOffersMPT(features); testFillOrKill(features); testTickSize(features); + testAutoCreateReserve(features); } void diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index af6f4841f4..c6a9a54a53 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -10,9 +10,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -2377,6 +2379,83 @@ class Vault_test : public beast::unit_test::Suite env.close(); } + { + testcase("MPT locked: vault shares inherit underlying lock"); + + Env env{*this, testableAmendments()}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + env.fund(XRP(10'000), issuer, owner, alice, bob, carol); + env.close(); + Vault const vault{env}; + + MPTTester asset{ + {.env = env, + .issuer = issuer, + .holders = {owner, alice, bob, carol}, + .flags = tfMPTCanTransfer | tfMPTCanTrade | tfMPTCanLock}}; + env(pay(issuer, alice, asset(1'000))); + env(pay(issuer, bob, asset(1'000))); + env.close(); + + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(500)})); + // Bob also deposits so he has a share MPToken to receive into. + env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(500)})); + env.close(); + + auto const shares = [&]() -> PrettyAsset { + auto const sle = env.le(keylet); + BEAST_EXPECT(sle != nullptr); + return MPTIssue(sle->at(sfShareMPTID)); + }(); + auto const shareMptID = shares.raw().get().getMptID(); + auto const shareBalance = [&](Account const& account) { + auto const sle = env.le(keylet::mptoken(shareMptID, account)); + return sle ? sle->at(sfMPTAmount) : 0; + }; + + // Sanity: before the underlying lock, peer-to-peer share + // transfers are allowed. + env(pay(alice, bob, shares(1))); + env.close(); + + // Create the offer while shares are spendable, then lock the + // underlying to test whether a stale offer can still be crossed. + env(offer(alice, XRP(1), shares(1))); + env.close(); + + // Lock the underlying after the vault and share balances exist. + asset.set({.account = issuer, .flags = tfMPTLock}); + env.close(); + + // Direct vault share payment inherits the underlying lock via + // sfReferenceHolding. + BEAST_EXPECT(shareBalance(alice) == 499); + BEAST_EXPECT(shareBalance(bob) == 501); + env(pay(alice, bob, shares(1)), Ter{tecLOCKED}); + env.close(); + BEAST_EXPECT(shareBalance(alice) == 499); + BEAST_EXPECT(shareBalance(bob) == 501); + + // The same inherited lock must also block DEX payment paths that + // would consume an offer selling vault shares. + env(pay(carol, bob, shares(1)), + Sendmax(XRP(1)), + Path(BookSpec{shares.raw()}), + Ter{tecPATH_PARTIAL}); + env.close(); + BEAST_EXPECT(shareBalance(alice) == 499); + BEAST_EXPECT(shareBalance(bob) == 501); + BEAST_EXPECT(expectOffers(env, alice, 1)); + } + { testcase("MPT non-transferable: pre-fixCleanup3_2_0 share transfer succeeds"); @@ -2981,7 +3060,7 @@ class Vault_test : public beast::unit_test::Suite env(tx); env.close(); } - // Behavioural shift introduced by share inheritance: + // Behavioral shift introduced by share inheritance: // before fixCleanup3_2_0 this share Payment succeeded // and the underlying IOU's NoRipple restriction surfaced // only later on Charlie's withdrawal (terNO_RIPPLE). From a37afe13ff33402f72e476d304e8d5f18392611f Mon Sep 17 00:00:00 2001 From: Michael Legleux Date: Fri, 22 May 2026 04:30:37 -0700 Subject: [PATCH 016/135] ci: Re-enable full nproc for Linux (#7315) --- .github/workflows/reusable-build-test-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 4c7c41fd0c..cc927512ea 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -181,7 +181,7 @@ jobs: - name: Build the binary working-directory: ${{ env.BUILD_DIR }} env: - BUILD_NPROC: ${{ runner.os == 'Linux' && '16' || steps.nproc.outputs.nproc }} + BUILD_NPROC: ${{ steps.nproc.outputs.nproc }} BUILD_TYPE: ${{ inputs.build_type }} CMAKE_TARGET: ${{ inputs.cmake_target }} run: | From 15dd653e4b2cf3c0165111784d8136b91f0ef91d Mon Sep 17 00:00:00 2001 From: Michael Legleux Date: Fri, 22 May 2026 04:30:45 -0700 Subject: [PATCH 017/135] fix: Fix RPM prerelease ordering and start xrpld on DEB install (#7313) --- package/build_pkg.sh | 6 +++++- package/debian/rules | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package/build_pkg.sh b/package/build_pkg.sh index c834951493..adac2dc169 100755 --- a/package/build_pkg.sh +++ b/package/build_pkg.sh @@ -135,8 +135,12 @@ build_rpm() { # RPM Version can't contain '-'. A pre-release goes in Release with a # leading "0." so 3.2.0-b1 sorts before the final 3.2.0-. + # The order is "0.." (e.g. 0.1.b6) — the Fedora/EPEL + # convention. Reversing to "0.." (e.g. 0.b6.1) breaks + # rpmvercmp against the former because numeric segments outrank alphabetic + # ones, so "0.1.b5" would sort newer than "0.b6.1". local rpm_release="${PKG_RELEASE}" - [[ -n "${VER_SUFFIX}" ]] && rpm_release="0.${VER_SUFFIX}.${PKG_RELEASE}" + [[ -n "${VER_SUFFIX}" ]] && rpm_release="0.${PKG_RELEASE}.${VER_SUFFIX}" set -x rpmbuild -bb \ diff --git a/package/debian/rules b/package/debian/rules index 0fae101358..cd94da7e5b 100644 --- a/package/debian/rules +++ b/package/debian/rules @@ -9,7 +9,7 @@ override_dh_auto_configure override_dh_auto_build override_dh_auto_test: @: override_dh_installsystemd: - dh_installsystemd --no-start xrpld.service + dh_installsystemd --no-stop-on-upgrade xrpld.service dh_installsystemd --name=update-xrpld --no-start update-xrpld.service update-xrpld.timer execute_before_dh_installtmpfiles: From 179e73594ae3d22afaddb144be7905549e276ce5 Mon Sep 17 00:00:00 2001 From: Jingchen Date: Fri, 22 May 2026 12:58:48 +0100 Subject: [PATCH 018/135] fix: Check if the MPT first loss cover can be sent to the broker before deleting the broker (#7125) Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com> --- .../transactors/lending/LoanBrokerDelete.cpp | 14 ++ src/test/app/LoanBroker_test.cpp | 210 ++++++++++++++++++ 2 files changed, 224 insertions(+) diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp index 805a4612f2..6b77914370 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -106,6 +107,19 @@ LoanBrokerDelete::preclaim(PreclaimContext const& ctx) } } + if (ctx.view.rules().enabled(fixCleanup3_2_0)) + { + if (coverAvailable > beast::kZero) + { + auto const brokerPseudo = sleBroker->at(sfAccount); + if (auto const ret = checkFrozen(ctx.view, brokerPseudo, asset)) + { + JLOG(ctx.j.warn()) << "Broker pseudo-account is frozen/locked."; + return ret; + } + } + } + return tesSUCCESS; } diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index c899778391..92949256fd 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -1577,6 +1577,210 @@ class LoanBroker_test : public beast::unit_test::Suite env(loanBroker::set(lender, vaultKeylet.key), Ter(tecFROZEN)); } + void + testLoanBrokerDeleteLockedMPT(FeatureBitset features) + { + testcase << "LoanBrokerDelete - locked broker pseudo-account MPT"; + using namespace jtx; + using namespace loanBroker; + + Account const issuer("issuer"); + Account const alice("alice"); + + auto const withFix = features[fixCleanup3_2_0]; + Env env(*this, features); + env.fund(XRP(100'000), issuer, alice); + env.close(); + + // Create MPT with locking enabled + MPTTester mptt{env, issuer, kMptInitNoFund}; + mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock}); + + PrettyAsset const mpt{mptt.issuanceID()}; + + // Fund alice + mptt.authorize({.account = alice}); + env.close(); + env(pay(issuer, alice, mpt(100'000))); + env.close(); + + // Create vault + Vault const vault{env}; + auto [tx, vaultKeylet] = vault.create({.owner = alice, .asset = mpt}); + env(tx); + env.close(); + + // Deposit into vault + env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = mpt(10'000)})); + env.close(); + + // Create loan broker + auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice)); + env(set(alice, vaultKeylet.key)); + env.close(); + + // Deposit cover + env(coverDeposit(alice, brokerKeylet.key, mpt(5'000).value())); + env.close(); + + // Verify cover is deposited + auto const broker = env.le(brokerKeylet); + if (!BEAST_EXPECT(broker)) + return; + BEAST_EXPECT(broker->at(sfCoverAvailable) > 0); + + // Get the broker pseudo-account ID + auto const brokerPseudoID = broker->at(sfAccount); + + // Verify the broker pseudo-account has an MPToken + auto const pseudoMptKey = keylet::mptoken(mptt.issuanceID(), brokerPseudoID); + auto const pseudoMpt = env.le(pseudoMptKey); + if (!BEAST_EXPECT(pseudoMpt)) + return; + + // Issuer locks the broker pseudo-account's individual MPToken + { + json::Value jv; + jv[jss::Account] = issuer.human(); + jv[sfMPTokenIssuanceID] = to_string(mptt.issuanceID()); + jv[jss::Holder] = toBase58(brokerPseudoID); + jv[jss::TransactionType] = jss::MPTokenIssuanceSet; + jv[jss::Flags] = tfMPTLock; + env(jv); + env.close(); + } + + // Verify the pseudo-account's MPToken is now locked + { + auto const sle = env.le(pseudoMptKey); + if (!BEAST_EXPECT(sle)) + return; + BEAST_EXPECT(sle->isFlag(lsfMPTLocked)); + } + + // Record alice's balance before deletion + auto const aliceBalanceBefore = env.balance(alice, mpt); + + // With fixCleanup3_2_0, preclaim() checks the broker pseudo-account's + // freeze/lock state via checkFrozen(), so deletion is blocked. + // Without the fix, the check is missing and the locked cover is + // returned to the owner. + if (withFix) + { + env(del(alice, brokerKeylet.key), Ter(tecLOCKED)); + env.close(); + + // Verify the broker is not deleted + BEAST_EXPECT(env.le(brokerKeylet) != nullptr); + + // Verify alice did not receive the cover despite the lock + auto const aliceBalanceAfter = env.balance(alice, mpt); + BEAST_EXPECT(aliceBalanceAfter == aliceBalanceBefore); + + // Verify the locked MPToken was not deleted + BEAST_EXPECT(env.le(pseudoMptKey) != nullptr); + } + else + { + env(del(alice, brokerKeylet.key), Ter(tesSUCCESS)); + env.close(); + + // Verify the broker is deleted + BEAST_EXPECT(env.le(brokerKeylet) == nullptr); + + // Verify alice received the cover despite the lock + auto const aliceBalanceAfter = env.balance(alice, mpt); + BEAST_EXPECT(aliceBalanceAfter > aliceBalanceBefore); + + // Verify the locked MPToken was deleted + BEAST_EXPECT(env.le(pseudoMptKey) == nullptr); + } + } + + void + testLoanBrokerDeleteFrozenIOU(FeatureBitset features) + { + testcase << "LoanBrokerDelete - frozen broker pseudo-account IOU"; + using namespace jtx; + using namespace loanBroker; + + Account const issuer("issuer"); + Account const alice("alice"); + + auto const withFix = features[fixCleanup3_2_0]; + Env env(*this, features); + env.fund(XRP(100'000), issuer, alice); + env.close(); + + auto const iou = issuer["IOU"]; + + // Set up trust lines and fund alice + env(trust(alice, iou(1'000'000))); + env.close(); + env(pay(issuer, alice, iou(100'000))); + env.close(); + + // Create vault + Vault const vault{env}; + auto [tx, vaultKeylet] = vault.create({.owner = alice, .asset = iou.asset()}); + env(tx); + env.close(); + + // Deposit into vault + env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = iou(10'000)})); + env.close(); + + // Create loan broker + auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice)); + env(set(alice, vaultKeylet.key)); + env.close(); + + // Deposit cover + env(coverDeposit(alice, brokerKeylet.key, iou(5'000))); + env.close(); + + // Verify cover is deposited + auto const broker = env.le(brokerKeylet); + if (!BEAST_EXPECT(broker)) + return; + BEAST_EXPECT(broker->at(sfCoverAvailable) > 0); + + // Get the broker pseudo-account + auto const brokerPseudoID = broker->at(sfAccount); + auto const brokerPseudo = Account("BrokerPseudo", brokerPseudoID); + + // Issuer freezes the broker pseudo-account's trust line + env(trust(issuer, brokerPseudo["IOU"](0), tfSetFreeze)); + env.close(); + + // Record alice's balance before deletion attempt + auto const aliceBalanceBefore = env.balance(alice, iou); + + // With fixCleanup3_2_0, preclaim() checks the broker + // pseudo-account's freeze state via checkFrozen(), so + // deletion is blocked early with tecFROZEN. + // Without the fix, preclaim() does not check the pseudo-account, + // but the TransfersNotFrozen invariant catches the frozen transfer + // in doApply() and fails with tecINVARIANT_FAILED. + // Either way, the broker survives and alice's balance is unchanged. + if (withFix) + { + env(del(alice, brokerKeylet.key), Ter(tecFROZEN)); + } + else + { + env(del(alice, brokerKeylet.key), Ter(tecINVARIANT_FAILED)); + } + env.close(); + + // Broker still exists + BEAST_EXPECT(env.le(brokerKeylet) != nullptr); + + // Alice's balance unchanged + auto const aliceBalanceAfter = env.balance(alice, iou); + BEAST_EXPECT(aliceBalanceAfter == aliceBalanceBefore); + } + void testRIPD4274IOU() { @@ -2056,6 +2260,12 @@ public: testRIPD4274(); + testLoanBrokerDeleteLockedMPT(all_); + testLoanBrokerDeleteLockedMPT(all_ - fixCleanup3_2_0); + + testLoanBrokerDeleteFrozenIOU(all_); + testLoanBrokerDeleteFrozenIOU(all_ - fixCleanup3_2_0); + // TODO: Write clawback failure tests with an issuer / MPT that doesn't // have the right flags set. } From dfb9b8ed9a0ad04c1da00c9957fe3c7454930835 Mon Sep 17 00:00:00 2001 From: Bart Date: Fri, 22 May 2026 20:32:12 +0100 Subject: [PATCH 019/135] release: Bump version to 3.2.0-b7 (#7316) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- 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 3d4fff9f04..5613a9366e 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -23,7 +23,7 @@ namespace { //------------------------------------------------------------------------------ // clang-format off // NOLINTNEXTLINE(readability-identifier-naming) -char const* const versionString = "3.2.0-b6" +char const* const versionString = "3.2.0-b7" // clang-format on ; From dcd2ff0b5f2019c40c3f38640e751426df97d3c8 Mon Sep 17 00:00:00 2001 From: Gregory Tsipenyuk Date: Sat, 23 May 2026 02:40:26 -0400 Subject: [PATCH 020/135] fix: Fix non-canonical MPT amount (#7117) Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- include/xrpl/protocol/STAmount.h | 17 +- include/xrpl/tx/Transactor.h | 12 + include/xrpl/tx/invariants/InvariantCheck.h | 16 + .../xrpl/tx/transactors/escrow/EscrowCreate.h | 3 + src/libxrpl/protocol/STAmount.cpp | 46 + src/libxrpl/tx/Transactor.cpp | 10 + src/libxrpl/tx/invariants/InvariantCheck.cpp | 31 + .../tx/transactors/check/CheckCash.cpp | 5 + .../tx/transactors/escrow/EscrowCreate.cpp | 12 +- src/test/app/Invariants_test.cpp | 57 ++ src/test/app/MPToken_test.cpp | 867 +++++++++++++++++- src/test/app/OfferMPT_test.cpp | 2 +- 12 files changed, 1074 insertions(+), 4 deletions(-) diff --git a/include/xrpl/protocol/STAmount.h b/include/xrpl/protocol/STAmount.h index c576c0da31..bf3e25eedb 100644 --- a/include/xrpl/protocol/STAmount.h +++ b/include/xrpl/protocol/STAmount.h @@ -3,11 +3,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -593,12 +595,25 @@ STAmount::value() const noexcept return *this; } -inline bool +[[nodiscard]] inline bool isLegalNet(STAmount const& value) { return !value.native() || (value.mantissa() <= STAmount::kMaxNativeN); } +[[nodiscard]] inline bool +isLegalMPT(STAmount const& value) +{ + return !value.holds() || + (!value.negative() && value.exponent() == 0 && value.mantissa() <= kMaxMpTokenAmount); +} + +/* Check recursively if an object has invalid MPTAmount or XRPAmount in STAmount field. + * Calls isLegalNet() and isLegalMPT(). + */ +[[nodiscard]] bool +hasInvalidAmount(STBase const& field, beast::Journal j); + //------------------------------------------------------------------------------ // // Operators diff --git a/include/xrpl/tx/Transactor.h b/include/xrpl/tx/Transactor.h index 61d943c4d5..1440a5097f 100644 --- a/include/xrpl/tx/Transactor.h +++ b/include/xrpl/tx/Transactor.h @@ -398,6 +398,15 @@ private: static NotTEC preflight2(PreflightContext const& ctx); + /** Universal validations + - Valid MPTAmount and XRPAmount + + Do not try to call preflightUniversal from preflight() in derived classes. See + the description of invokePreflight for details. + */ + static NotTEC + preflightUniversal(PreflightContext const& ctx); + /** Check transaction-specific invariants only. * * Walks every modified ledger entry via visitInvariantEntry, then @@ -463,6 +472,9 @@ Transactor::invokePreflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx, T::getFlagsMask(ctx))) return ret; + if (auto const ret = preflightUniversal(ctx)) + return ret; + if (auto const ret = T::preflight(ctx)) return ret; diff --git a/include/xrpl/tx/invariants/InvariantCheck.h b/include/xrpl/tx/invariants/InvariantCheck.h index 5996039bf0..d4c0154269 100644 --- a/include/xrpl/tx/invariants/InvariantCheck.h +++ b/include/xrpl/tx/invariants/InvariantCheck.h @@ -373,6 +373,21 @@ public: finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); }; +/** Verify that MPT/XRP STAmounts are canonical in any ledger entries left after the + * transaction applies. + */ +class ValidAmounts +{ + std::vector> afterEntries_; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + [[nodiscard]] bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; +}; + // additional invariant checks can be declared above and then added to this // tuple using InvariantChecks = std::tuple< @@ -402,6 +417,7 @@ using InvariantChecks = std::tuple< ValidLoan, ValidVault, ValidMPTPayment, + ValidAmounts, ValidMPTTransfer>; /** diff --git a/include/xrpl/tx/transactors/escrow/EscrowCreate.h b/include/xrpl/tx/transactors/escrow/EscrowCreate.h index 8682ed7369..2e9da89896 100644 --- a/include/xrpl/tx/transactors/escrow/EscrowCreate.h +++ b/include/xrpl/tx/transactors/escrow/EscrowCreate.h @@ -16,6 +16,9 @@ public: static TxConsequences makeTxConsequences(PreflightContext const& ctx); + static bool + checkExtraFeatures(PreflightContext const& ctx); + static NotTEC preflight(PreflightContext const& ctx); diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index c40beabf12..1ba9cd042f 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -19,8 +19,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -1222,6 +1224,50 @@ operator-(STAmount const& value) STAmount::Unchecked{}); } +static bool +hasInvalidAmount(STBase const& field, int depth, beast::Journal j); + +static bool +hasInvalidAmount(STObject const& object, int depth, beast::Journal j) +{ + return std::ranges::any_of( + object, [&](STBase const& field) { return hasInvalidAmount(field, depth, j); }); +} + +static bool +hasInvalidAmount(STArray const& array, int depth, beast::Journal j) +{ + return std::ranges::any_of( + array, [&](STObject const& object) { return hasInvalidAmount(object, depth, j); }); +} + +static bool +hasInvalidAmount(STBase const& field, int depth, beast::Journal j) +{ + if (depth > 10) + { + JLOG(j.error()) << "hasInvalidAmount: depth exceeds 10"; + return true; + } + + if (auto const amount = dynamic_cast(&field)) + return !isLegalMPT(*amount) || !isLegalNet(*amount); + + if (auto const object = dynamic_cast(&field)) + return hasInvalidAmount(*object, depth + 1, j); + + if (auto const array = dynamic_cast(&field)) + return hasInvalidAmount(*array, depth + 1, j); + + return false; +} + +bool +hasInvalidAmount(STBase const& field, beast::Journal j) +{ + return hasInvalidAmount(field, 0, j); +} + //------------------------------------------------------------------------------ // // Arithmetic diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index 97f2cabff2..28fa059902 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include // IWYU pragma: keep @@ -256,6 +257,15 @@ Transactor::preflight2(PreflightContext const& ctx) return tesSUCCESS; } +NotTEC +Transactor::preflightUniversal(PreflightContext const& ctx) +{ + if (ctx.rules.enabled(fixCleanup3_2_0) && hasInvalidAmount(ctx.tx, ctx.j)) + return temBAD_AMOUNT; + + return tesSUCCESS; +} + //------------------------------------------------------------------------------ Transactor::Transactor(ApplyContext& ctx) diff --git a/src/libxrpl/tx/invariants/InvariantCheck.cpp b/src/libxrpl/tx/invariants/InvariantCheck.cpp index 3e51b6f877..0154dca747 100644 --- a/src/libxrpl/tx/invariants/InvariantCheck.cpp +++ b/src/libxrpl/tx/invariants/InvariantCheck.cpp @@ -1080,4 +1080,35 @@ NoModifiedUnmodifiableFields::finalize( return true; } +void +ValidAmounts::visitEntry( + bool isDelete, + std::shared_ptr const&, + std::shared_ptr const& after) +{ + if (!isDelete && after) + afterEntries_.push_back(after); +} + +bool +ValidAmounts::finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) const +{ + bool const badLedgerEntry = std::ranges::any_of( + afterEntries_, [&](auto const& sle) { return hasInvalidAmount(*sle, j); }); + + if (badLedgerEntry) + { + JLOG(j.fatal()) + << "Invariant failed: ledger entry contains non-canonical MPT or XRP amount"; + return !view.rules().enabled(fixCleanup3_2_0); + } + + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/check/CheckCash.cpp b/src/libxrpl/tx/transactors/check/CheckCash.cpp index cfc133b501..af903ff177 100644 --- a/src/libxrpl/tx/transactors/check/CheckCash.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCash.cpp @@ -139,6 +139,11 @@ CheckCash::preclaim(PreclaimContext const& ctx) }(ctx.tx)}; STAmount const sendMax = sleCheck->at(sfSendMax); + // A legacy Check may contain a non-canonical MPT sfSendMax. Universal + // preflight only validates the CheckCash transaction, not the stored Check. + if (ctx.view.rules().enabled(fixCleanup3_2_0) && !isLegalMPT(sendMax)) + return tefBAD_LEDGER; + if (!equalTokens(value.asset(), sendMax.asset())) { JLOG(ctx.j.warn()) << "Check cash does not match check currency."; diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index 0b1db125f6..4de302db3e 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -81,6 +81,16 @@ EscrowCreate::makeTxConsequences(PreflightContext const& ctx) return TxConsequences{ctx.tx, isXRP(amount) ? amount.xrp() : beast::kZero}; } +bool +EscrowCreate::checkExtraFeatures(PreflightContext const& ctx) +{ + // Only require featureMPTokensV1 when the escrow amount is an MPT and + // fixCleanup3_2_0 is active; XRP/IOU escrows are unaffected by this gate. + if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfAmount].holds()) + return ctx.rules.enabled(featureMPTokensV1); + return true; +} + template static NotTEC escrowCreatePreflightHelper(PreflightContext const& ctx); @@ -103,7 +113,7 @@ template <> NotTEC escrowCreatePreflightHelper(PreflightContext const& ctx) { - if (!ctx.rules.enabled(featureMPTokensV1)) + if (!ctx.rules.enabled(fixCleanup3_2_0) && !ctx.rules.enabled(featureMPTokensV1)) return temDISABLED; auto const amount = ctx.tx[sfAmount]; diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 6fd1904992..cc16948553 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -4065,6 +4065,63 @@ class Invariants_test : public beast::unit_test::Suite using namespace test::jtx; testcase << "MPT"; + MPTIssue const nonCanonicalMPTIssue{makeMptID(1, AccountID(0x4985601))}; + auto const nonCanonicalMPTAmount = [&](SField const& field) { + return STAmount{ + field, + nonCanonicalMPTIssue, + kMaxMpTokenAmount + std::uint64_t{1}, + 0, + false, + STAmount::Unchecked{}}; + }; + auto const negativeMPTAmount = [&](SField const& field) { + return STAmount{field, nonCanonicalMPTIssue, 2, 0, true, STAmount::Unchecked{}}; + }; + auto const nonCanonicalMPTPayment = [&]() { + return STTx{ttPAYMENT, [&](STObject& tx) { + tx.setFieldAmount(sfAmount, nonCanonicalMPTAmount(sfAmount)); + }}; + }; + + doInvariantCheck( + Env{*this, defaultAmendments() - fixCleanup3_2_0}, + {}, + [](Account const&, Account const&, ApplyContext&) { return true; }, + XRPAmount{}, + nonCanonicalMPTPayment(), + {tesSUCCESS, tesSUCCESS}); + + doInvariantCheck( + {{"ledger entry contains non-canonical MPT or XRP amount"}}, + [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sle = ac.view().peek(keylet::account(a1.id())); + if (!sle) + return false; + + auto sleNew = std::make_shared(keylet::check(a1.id(), (*sle)[sfSequence])); + sleNew->setAccountID(sfAccount, a1.id()); + sleNew->setAccountID(sfDestination, a2.id()); + sleNew->setFieldAmount(sfSendMax, nonCanonicalMPTAmount(sfSendMax)); + ac.view().insert(sleNew); + return true; + }); + + doInvariantCheck( + {{"ledger entry contains non-canonical MPT or XRP amount"}}, + [&](Account const& a1, Account const& a2, ApplyContext& ac) { + auto const sle = ac.view().peek(keylet::account(a1.id())); + if (!sle) + return false; + + auto sleNew = std::make_shared(keylet::check(a1.id(), (*sle)[sfSequence])); + sleNew->setAccountID(sfAccount, a1.id()); + sleNew->setAccountID(sfDestination, a2.id()); + sleNew->setFieldAmount(sfSendMax, negativeMPTAmount(sfSendMax)); + ac.view().insert(sleNew); + return true; + }); + // MPT OutstandingAmount > MaximumAmount doInvariantCheck( {{"OutstandingAmount overflow"}}, diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 6906c4aba0..3d6cff0885 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -25,11 +25,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -49,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -57,13 +60,17 @@ #include #include +#include #include #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -2115,6 +2122,864 @@ class MPToken_test : public beast::unit_test::Suite BEAST_EXPECT(txWithAmounts.empty()); } + void + testNonCanonicalMPTAmountCleanup(FeatureBitset features) + { + using namespace test::jtx; + using namespace std::literals; + FeatureBitset const withoutFix = features - fixCleanup3_2_0; + FeatureBitset const withFix = features | fixCleanup3_2_0; + FeatureBitset const withoutFixAndV2 = withoutFix - featureMPTokensV2; + FeatureBitset const withFixAndWithoutV2 = withFix - featureMPTokensV2; + + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const gw{"gw"}; + + using MPTValue = MPTAmount::value_type; + MPTValue const mptMin = std::numeric_limits::min(); + MPTValue const mptMax = std::numeric_limits::max(); + std::uint64_t const u64Max = std::numeric_limits::max(); + std::uint64_t const firstInvalidMPTMantissa = static_cast(mptMax) + 1; + MPTValue const alice0 = 10'000; + MPTValue const gw0 = -20'000; + TER const success = tesSUCCESS; + TER const invariantFailed = tecINVARIANT_FAILED; + TER const pathPartial = tecPATH_PARTIAL; + TER const badAmountTer = temBAD_AMOUNT; + + struct BadMPTAmount + { + std::string_view name; + std::uint64_t mantissa; + bool negative; + MPTValue mptValue; + TER issuerToHolderPreFixTer; + TER holderSourcePreFixTer; + MPTValue issuerToHolderAliceAfterPreFix; + MPTValue issuerToHolderIssuerAfterPreFix; + MPTValue issuerToHolderAliceAfterPostFix; + MPTValue issuerToHolderIssuerAfterPostFix; + }; + // clang-format off + std::array const badMPTAmounts = {{ + { .name="INT64_MAX + 1", .mantissa=firstInvalidMPTMantissa, .negative=false, .mptValue=mptMin, .issuerToHolderPreFixTer=invariantFailed, .holderSourcePreFixTer=pathPartial, .issuerToHolderAliceAfterPreFix=alice0, .issuerToHolderIssuerAfterPreFix=gw0, .issuerToHolderAliceAfterPostFix=alice0 - 1, .issuerToHolderIssuerAfterPostFix=gw0 + 1}, + { .name="INT64_MAX + 10", .mantissa=firstInvalidMPTMantissa + 9, .negative=false, .mptValue=mptMin + 9, .issuerToHolderPreFixTer=invariantFailed, .holderSourcePreFixTer=pathPartial, .issuerToHolderAliceAfterPreFix=alice0, .issuerToHolderIssuerAfterPreFix=gw0, .issuerToHolderAliceAfterPostFix=alice0 - 1, .issuerToHolderIssuerAfterPostFix=gw0 + 1}, + { .name="UINT64_MAX - 9998", .mantissa=u64Max - 9'998, .negative=false, .mptValue=MPTValue{-9'999}, .issuerToHolderPreFixTer=success, .holderSourcePreFixTer=pathPartial, .issuerToHolderAliceAfterPreFix=alice0 - 9'999, .issuerToHolderIssuerAfterPreFix=gw0 + 9'999, .issuerToHolderAliceAfterPostFix=alice0 - 10'000, .issuerToHolderIssuerAfterPostFix=gw0 + 10'000}, + { .name="UINT64_MAX - 9", .mantissa=u64Max - 9, .negative=false, .mptValue=MPTValue{-10}, .issuerToHolderPreFixTer=success, .holderSourcePreFixTer=pathPartial, .issuerToHolderAliceAfterPreFix=alice0 - 10, .issuerToHolderIssuerAfterPreFix=gw0 + 10, .issuerToHolderAliceAfterPostFix=alice0 - 11, .issuerToHolderIssuerAfterPostFix=gw0 + 11}, + { .name="UINT64_MAX - 1", .mantissa=u64Max - 1, .negative=false, .mptValue=MPTValue{-2}, .issuerToHolderPreFixTer=success, .holderSourcePreFixTer=pathPartial, .issuerToHolderAliceAfterPreFix=alice0 - 2, .issuerToHolderIssuerAfterPreFix=gw0 + 2, .issuerToHolderAliceAfterPostFix=alice0 - 3, .issuerToHolderIssuerAfterPostFix=gw0 + 3}, + { .name="UINT64_MAX", .mantissa=u64Max, .negative=false, .mptValue=MPTValue{-1}, .issuerToHolderPreFixTer=success, .holderSourcePreFixTer=pathPartial, .issuerToHolderAliceAfterPreFix=alice0 - 1, .issuerToHolderIssuerAfterPreFix=gw0 + 1, .issuerToHolderAliceAfterPostFix=alice0 - 2, .issuerToHolderIssuerAfterPostFix=gw0 + 2}, + { .name="-2", .mantissa=std::uint64_t{2}, .negative=true, .mptValue=MPTValue{-2}, .issuerToHolderPreFixTer=badAmountTer, .holderSourcePreFixTer=badAmountTer, .issuerToHolderAliceAfterPreFix=alice0, .issuerToHolderIssuerAfterPreFix=gw0, .issuerToHolderAliceAfterPostFix=alice0 - 1, .issuerToHolderIssuerAfterPostFix=gw0 + 1} + }}; + // clang-format on + auto const badMPTAmount = [&](MPTIssue const& issue, BadMPTAmount const& bad) { + return STAmount{issue, bad.mantissa, 0, bad.negative, STAmount::Unchecked{}}; + }; + auto const makeIssue = [&](Env& env) { + MPTTester const mpt{ + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 10'000, + .flags = tfMPTCanTransfer | tfMPTCanTrade | tfMPTCanEscrow | tfMPTCanClawback}}; + return MPTIssue{mpt.issuanceID()}; + }; + auto const withNonCanonicalMPTAmount = + [](JTx jt, SField const& field, STAmount const& amount, Account const& signer) { + STTx tx{*jt.stx}; + tx.setFieldAmount(field, amount); + tx.sign(signer.pk(), signer.sk()); + jt.stx = std::make_shared(tx); + return jt; + }; + auto const roundTrip = [](STTx const& tx) { + Serializer s; + tx.add(s); + SerialIter sit{s.slice()}; + return STTx{sit}; + }; + auto const expectRoundTripBadMPT = + [&](JTx const& jt, SField const& field, BadMPTAmount const& bad) { + auto const roundTripped = roundTrip(*jt.stx); + auto const persisted = roundTripped.getFieldAmount(field); + BEAST_EXPECT(persisted.holds()); + BEAST_EXPECT(persisted.mantissa() == bad.mantissa); + BEAST_EXPECT(persisted.exponent() == 0); + BEAST_EXPECT(persisted.negative() == bad.negative); + BEAST_EXPECT(persisted.mpt().value() == bad.mptValue); + if (!bad.negative) + BEAST_EXPECT(persisted.mantissa() > kMaxMpTokenAmount); + }; + + for (auto const& bad : badMPTAmounts) + { + testcase("fixCleanup3_2_0 rejects non-canonical MPT Payment amounts"); + { + Env env{*this, withoutFixAndV2}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto malformedHolderToHolder = withNonCanonicalMPTAmount( + env.jt(pay(alice, bob, STAmount{issue, std::uint64_t{1}})), + sfAmount, + badAmount, + alice); + expectRoundTripBadMPT(malformedHolderToHolder, sfAmount, bad); + malformedHolderToHolder.ter = bad.holderSourcePreFixTer; + env.submit(malformedHolderToHolder); + env.close(); + BEAST_EXPECT( + (env.balance(alice, issue).value() == STAmount{MPTAmount{10'000}, issue})); + BEAST_EXPECT( + (env.balance(bob, issue).value() == STAmount{MPTAmount{10'000}, issue})); + BEAST_EXPECT( + (env.balance(gw, issue).value() == STAmount{MPTAmount{-20'000}, issue})); + + env.enableFeature(fixCleanup3_2_0); + env.close(); + env(env.jt(pay(bob, alice, STAmount{issue, std::uint64_t{1}})), Ter{tesSUCCESS}); + env.close(); + BEAST_EXPECT( + (env.balance(alice, issue).value() == STAmount{MPTAmount{10'001}, issue})); + BEAST_EXPECT( + (env.balance(bob, issue).value() == STAmount{MPTAmount{9'999}, issue})); + BEAST_EXPECT( + (env.balance(gw, issue).value() == STAmount{MPTAmount{-20'000}, issue})); + } + { + Env env{*this, envconfig(), withoutFixAndV2, nullptr, beast::Severity::Disabled}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto malformedIssuerToHolder = withNonCanonicalMPTAmount( + env.jt(pay(gw, alice, STAmount{issue, std::uint64_t{1}})), + sfAmount, + badAmount, + gw); + expectRoundTripBadMPT(malformedIssuerToHolder, sfAmount, bad); + malformedIssuerToHolder.ter = bad.issuerToHolderPreFixTer; + env.submit(malformedIssuerToHolder); + env.close(); + BEAST_EXPECT( + (env.balance(alice, issue).value() == + STAmount{MPTAmount{bad.issuerToHolderAliceAfterPreFix}, issue})); + BEAST_EXPECT( + (env.balance(bob, issue).value() == STAmount{MPTAmount{10'000}, issue})); + BEAST_EXPECT( + (env.balance(gw, issue).value() == + STAmount{MPTAmount{bad.issuerToHolderIssuerAfterPreFix}, issue})); + + env.enableFeature(fixCleanup3_2_0); + env.close(); + env(env.jt(pay(alice, gw, STAmount{issue, std::uint64_t{1}})), Ter{tesSUCCESS}); + env.close(); + BEAST_EXPECT( + (env.balance(alice, issue).value() == + STAmount{MPTAmount{bad.issuerToHolderAliceAfterPostFix}, issue})); + BEAST_EXPECT( + (env.balance(bob, issue).value() == STAmount{MPTAmount{10'000}, issue})); + BEAST_EXPECT( + (env.balance(gw, issue).value() == + STAmount{MPTAmount{bad.issuerToHolderIssuerAfterPostFix}, issue})); + } + { + Env env{*this, withoutFixAndV2}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto malformedHolderToIssuer = withNonCanonicalMPTAmount( + env.jt(pay(alice, gw, STAmount{issue, std::uint64_t{1}})), + sfAmount, + badAmount, + alice); + expectRoundTripBadMPT(malformedHolderToIssuer, sfAmount, bad); + malformedHolderToIssuer.ter = bad.holderSourcePreFixTer; + env.submit(malformedHolderToIssuer); + env.close(); + BEAST_EXPECT( + (env.balance(alice, issue).value() == STAmount{MPTAmount{10'000}, issue})); + BEAST_EXPECT( + (env.balance(bob, issue).value() == STAmount{MPTAmount{10'000}, issue})); + BEAST_EXPECT( + (env.balance(gw, issue).value() == STAmount{MPTAmount{-20'000}, issue})); + + env.enableFeature(fixCleanup3_2_0); + env.close(); + env(env.jt(pay(gw, alice, STAmount{issue, std::uint64_t{1}})), Ter{tesSUCCESS}); + env.close(); + BEAST_EXPECT( + (env.balance(alice, issue).value() == STAmount{MPTAmount{10'001}, issue})); + BEAST_EXPECT( + (env.balance(bob, issue).value() == STAmount{MPTAmount{10'000}, issue})); + BEAST_EXPECT( + (env.balance(gw, issue).value() == STAmount{MPTAmount{-20'001}, issue})); + } + { + Env env{*this, withFixAndWithoutV2}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt(pay(alice, bob, STAmount{issue, std::uint64_t{1}})), + sfAmount, + badAmount, + alice); + tx.ter = temBAD_AMOUNT; + env.submit(tx); + } + { + Env env{*this, withFixAndWithoutV2}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt(pay(gw, alice, STAmount{issue, std::uint64_t{1}})), + sfAmount, + badAmount, + gw); + tx.ter = temBAD_AMOUNT; + env.submit(tx); + } + { + Env env{*this, withFixAndWithoutV2}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt(pay(alice, gw, STAmount{issue, std::uint64_t{1}})), + sfAmount, + badAmount, + alice); + tx.ter = temBAD_AMOUNT; + env.submit(tx); + } + + testcase("fixCleanup3_2_0 rejects non-canonical MPT Check amounts"); + { + Env env{*this, envconfig(), withoutFix, nullptr, beast::Severity::Disabled}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badSendMax = badMPTAmount(issue, bad); + auto const checkSeq = env.seq(alice); + auto tx = withNonCanonicalMPTAmount( + env.jt(check::create(alice, bob, STAmount{issue, std::uint64_t{10}})), + sfSendMax, + badSendMax, + alice); + tx.ter = bad.negative ? TER{temBAD_AMOUNT} : TER{tesSUCCESS}; + env.submit(tx); + env.close(); + + auto const checkKeylet = keylet::check(alice.id(), checkSeq); + auto const sleCheck = env.le(checkKeylet); + BEAST_EXPECT((sleCheck != nullptr) == !bad.negative); + if (sleCheck && !bad.negative) + { + auto const persisted = sleCheck->getFieldAmount(sfSendMax); + BEAST_EXPECT(persisted.holds()); + BEAST_EXPECT(persisted.mantissa() == bad.mantissa); + BEAST_EXPECT(persisted.negative() == bad.negative); + } + } + { + Env env{*this, envconfig(), withoutFix, nullptr, beast::Severity::Disabled}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badSendMax = badMPTAmount(issue, bad); + auto const checkSeq = env.seq(alice); + auto tx = withNonCanonicalMPTAmount( + env.jt(check::create(alice, bob, STAmount{issue, std::uint64_t{10}})), + sfSendMax, + badSendMax, + alice); + tx.ter = bad.negative ? TER{temBAD_AMOUNT} : TER{tesSUCCESS}; + env.submit(tx); + env.close(); + + auto const checkKeylet = keylet::check(alice.id(), checkSeq); + BEAST_EXPECT((env.le(checkKeylet) != nullptr) == !bad.negative); + if (!bad.negative) + { + // CheckCancel has no amount fields, but it must be able to + // remove a malformed legacy Check while the fix is disabled. + env(env.jt(check::cancel(alice, checkKeylet.key)), Ter{tesSUCCESS}); + env.close(); + BEAST_EXPECT(env.le(checkKeylet) == nullptr); + } + } + { + Env env{*this, envconfig(), withoutFix, nullptr, beast::Severity::Disabled}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badSendMax = badMPTAmount(issue, bad); + auto const checkSeq = env.seq(alice); + auto tx = withNonCanonicalMPTAmount( + env.jt(check::create(alice, bob, STAmount{issue, std::uint64_t{10}})), + sfSendMax, + badSendMax, + alice); + tx.ter = bad.negative ? TER{temBAD_AMOUNT} : TER{tesSUCCESS}; + env.submit(tx); + env.close(); + + auto const checkKeylet = keylet::check(alice.id(), checkSeq); + BEAST_EXPECT((env.le(checkKeylet) != nullptr) == !bad.negative); + if (!bad.negative) + { + env.enableFeature(fixCleanup3_2_0); + env.close(); + + // Once the fix is enabled, CheckCancel should still remove + // a legacy Check because it does not consume the bad amount. + env(env.jt(check::cancel(alice, checkKeylet.key)), Ter{tesSUCCESS}); + env.close(); + BEAST_EXPECT(env.le(checkKeylet) == nullptr); + } + } + { + Env env{*this, envconfig(), withoutFix, nullptr, beast::Severity::Disabled}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badSendMax = badMPTAmount(issue, bad); + auto const checkSeq = env.seq(alice); + auto tx = withNonCanonicalMPTAmount( + env.jt(check::create(alice, bob, STAmount{issue, std::uint64_t{10}})), + sfSendMax, + badSendMax, + alice); + tx.ter = bad.negative ? TER{temBAD_AMOUNT} : TER{tesSUCCESS}; + env.submit(tx); + env.close(); + + auto const checkKeylet = keylet::check(alice.id(), checkSeq); + BEAST_EXPECT((env.le(checkKeylet) != nullptr) == !bad.negative); + if (!bad.negative) + { + env.enableFeature(fixCleanup3_2_0); + env.close(); + + auto const cashAmount = STAmount{sfAmount, issue, std::uint64_t{1}, 0, false}; + env(env.jt(check::cash(bob, checkKeylet.key, cashAmount)), Ter{tefBAD_LEDGER}); + env.close(); + BEAST_EXPECT(env.le(checkKeylet) != nullptr); + } + } + { + Env env{*this, withoutFix}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const sendMax = STAmount{sfSendMax, issue, std::uint64_t{10}, 0, false}; + auto const checkSeq = env.seq(alice); + env(env.jt(check::create(alice, bob, sendMax)), Ter{tesSUCCESS}); + env.close(); + + auto const badCashAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + check::cash( + bob, + keylet::check(alice.id(), checkSeq).key, + STAmount{issue, std::uint64_t{1}})), + sfAmount, + badCashAmount, + bob); + expectRoundTripBadMPT(tx, sfAmount, bad); + tx.ter = bad.holderSourcePreFixTer; + env.submit(tx); + env.close(); + BEAST_EXPECT(env.le(keylet::check(alice.id(), checkSeq)) != nullptr); + BEAST_EXPECT( + (env.balance(alice, issue).value() == STAmount{MPTAmount{10'000}, issue})); + BEAST_EXPECT( + (env.balance(bob, issue).value() == STAmount{MPTAmount{10'000}, issue})); + BEAST_EXPECT( + (env.balance(gw, issue).value() == STAmount{MPTAmount{-20'000}, issue})); + } + { + Env env{*this, withFix}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const sendMax = STAmount{sfSendMax, issue, std::uint64_t{10}, 0, false}; + auto const checkSeq = env.seq(alice); + env(env.jt(check::create(alice, bob, sendMax)), Ter{tesSUCCESS}); + env.close(); + + auto const badCashAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + check::cash( + bob, + keylet::check(alice.id(), checkSeq).key, + STAmount{issue, std::uint64_t{1}})), + sfAmount, + badCashAmount, + bob); + tx.ter = temBAD_AMOUNT; + env.submit(tx); + } + + testcase("fixCleanup3_2_0 rejects non-canonical MPT Escrow amounts"); + { + Env env{*this, withoutFix}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const escrowSeq = env.seq(alice); + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + escrow::create(alice, bob, STAmount{issue, std::uint64_t{1}}), + escrow::kFinishTime(env.now() + 1s)), + sfAmount, + badAmount, + alice); + tx.ter = bad.negative ? TER{temBAD_AMOUNT} : TER{tecINSUFFICIENT_FUNDS}; + env.submit(tx); + env.close(); + BEAST_EXPECT(env.le(keylet::escrow(alice.id(), escrowSeq)) == nullptr); + } + { + Env env{*this, withFix}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + escrow::create(alice, bob, STAmount{issue, std::uint64_t{1}}), + escrow::kFinishTime(env.now() + 1s)), + sfAmount, + badAmount, + alice); + tx.ter = temBAD_AMOUNT; + env.submit(tx); + } + + testcase("fixCleanup3_2_0 rejects non-canonical MPT Clawback amounts"); + { + Env env{*this, withoutFix}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt(claw(gw, STAmount{issue, std::uint64_t{1}}, bob)), + sfAmount, + badAmount, + gw); + expectRoundTripBadMPT(tx, sfAmount, bad); + tx.ter = bad.negative ? TER{temBAD_AMOUNT} : TER{tesSUCCESS}; + env.submit(tx); + env.close(); + + MPTValue const bobAfter = bad.negative ? MPTValue{10'000} : MPTValue{0}; + MPTValue const gwAfter = bad.negative ? MPTValue{-20'000} : MPTValue{-10'000}; + BEAST_EXPECT( + (env.balance(alice, issue).value() == STAmount{MPTAmount{10'000}, issue})); + BEAST_EXPECT( + (env.balance(bob, issue).value() == STAmount{MPTAmount{bobAfter}, issue})); + BEAST_EXPECT( + (env.balance(gw, issue).value() == STAmount{MPTAmount{gwAfter}, issue})); + } + { + Env env{*this, withFix}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt(claw(gw, STAmount{issue, std::uint64_t{1}}, bob)), + sfAmount, + badAmount, + gw); + tx.ter = temBAD_AMOUNT; + env.submit(tx); + env.close(); + + BEAST_EXPECT( + (env.balance(alice, issue).value() == STAmount{MPTAmount{10'000}, issue})); + BEAST_EXPECT( + (env.balance(bob, issue).value() == STAmount{MPTAmount{10'000}, issue})); + BEAST_EXPECT( + (env.balance(gw, issue).value() == STAmount{MPTAmount{-20'000}, issue})); + } + + testcase("featureMPTokensV2 disabled rejects MPT OfferCreate amounts"); + { + Env env{*this, withoutFixAndV2}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badTakerPays = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt(offer(alice, STAmount{issue, std::uint64_t{1}}, XRP(10))), + sfTakerPays, + badTakerPays, + alice); + expectRoundTripBadMPT(tx, sfTakerPays, bad); + tx.ter = temDISABLED; + env.submit(tx); + } + { + Env env{*this, withFixAndWithoutV2}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badTakerPays = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt(offer(alice, STAmount{issue, std::uint64_t{1}}, XRP(10))), + sfTakerPays, + badTakerPays, + alice); + tx.ter = temDISABLED; + env.submit(tx); + } + { + // sfTakerPays is MPT: both amendments active. Negative offers + // fail in OfferCreate::preflight() before the universal check; + // positive non-canonical amounts fail in the universal check. + Env env{*this, withFix}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badTakerPays = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt(offer(alice, STAmount{issue, std::uint64_t{1}}, XRP(10))), + sfTakerPays, + badTakerPays, + alice); + tx.ter = TER{temBAD_AMOUNT}; + env.submit(tx); + } + { + // sfTakerGets is MPT: both amendments active. Negative offers + // fail in OfferCreate::preflight() before the universal check; + // positive non-canonical amounts fail in the universal check. + Env env{*this, withFix}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badTakerGets = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt(offer(alice, XRP(10), STAmount{issue, std::uint64_t{1}})), + sfTakerGets, + badTakerGets, + alice); + tx.ter = TER{temBAD_AMOUNT}; + env.submit(tx); + } + + testcase("featureMPTokensV2 disabled rejects MPT AMMCreate amounts"); + { + Env env{*this, withoutFixAndV2}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + AMM::createJv(alice.id(), STAmount{issue, std::uint64_t{1}}, XRP(1), 0), + Fee(static_cast(env.current()->fees().increment.drops()))), + sfAmount, + badAmount, + alice); + expectRoundTripBadMPT(tx, sfAmount, bad); + tx.ter = temDISABLED; + env.submit(tx); + } + { + Env env{*this, withFixAndWithoutV2}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + AMM::createJv(alice.id(), STAmount{issue, std::uint64_t{1}}, XRP(1), 0), + Fee(static_cast(env.current()->fees().increment.drops()))), + sfAmount, + badAmount, + alice); + tx.ter = temDISABLED; + env.submit(tx); + } + { + // sfAmount is MPT: both amendments active, expect temBAD_AMOUNT + Env env{*this, withFix}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + AMM::createJv(alice.id(), STAmount{issue, std::uint64_t{1}}, XRP(1), 0), + Fee(static_cast(env.current()->fees().increment.drops()))), + sfAmount, + badAmount, + alice); + tx.ter = temBAD_AMOUNT; + env.submit(tx); + } + + testcase("featureMPTokensV2 disabled rejects MPT AMMDeposit amounts"); + { + Env env{*this, withoutFixAndV2}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + AMM::depositJv( + {.account = alice, + .asset1In = STAmount{issue, std::uint64_t{1}}, + .assets = std::make_pair(Asset{issue}, Asset{xrpIssue()})}), + Fee(static_cast(env.current()->fees().increment.drops()))), + sfAmount, + badAmount, + alice); + expectRoundTripBadMPT(tx, sfAmount, bad); + tx.ter = temDISABLED; + env.submit(tx); + } + { + Env env{*this, withFixAndWithoutV2}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + AMM::depositJv( + {.account = alice, + .asset1In = STAmount{issue, std::uint64_t{1}}, + .assets = std::make_pair(Asset{issue}, Asset{xrpIssue()})}), + Fee(static_cast(env.current()->fees().increment.drops()))), + sfAmount, + badAmount, + alice); + tx.ter = temDISABLED; + env.submit(tx); + } + { + // sfAmount is MPT: both amendments active, expect temBAD_AMOUNT + Env env{*this, withFix}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + AMM::depositJv( + {.account = alice, + .asset1In = STAmount{issue, std::uint64_t{1}}, + .assets = std::make_pair(Asset{issue}, Asset{xrpIssue()})}), + Fee(static_cast(env.current()->fees().increment.drops()))), + sfAmount, + badAmount, + alice); + tx.ter = temBAD_AMOUNT; + env.submit(tx); + } + + testcase("featureMPTokensV2 disabled rejects MPT AMMWithdraw amounts"); + { + Env env{*this, withoutFixAndV2}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + AMM::withdrawJv( + {.account = alice, + .asset1Out = STAmount{issue, std::uint64_t{1}}, + .assets = std::make_pair(Asset{issue}, Asset{xrpIssue()})}), + Fee(static_cast(env.current()->fees().increment.drops()))), + sfAmount, + badAmount, + alice); + expectRoundTripBadMPT(tx, sfAmount, bad); + tx.ter = temDISABLED; + env.submit(tx); + } + { + Env env{*this, withFixAndWithoutV2}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + AMM::withdrawJv( + {.account = alice, + .asset1Out = STAmount{issue, std::uint64_t{1}}, + .assets = std::make_pair(Asset{issue}, Asset{xrpIssue()})}), + Fee(static_cast(env.current()->fees().increment.drops()))), + sfAmount, + badAmount, + alice); + tx.ter = temDISABLED; + env.submit(tx); + } + { + // sfAmount is MPT: both amendments active, expect temBAD_AMOUNT + Env env{*this, withFix}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + AMM::withdrawJv( + {.account = alice, + .asset1Out = STAmount{issue, std::uint64_t{1}}, + .assets = std::make_pair(Asset{issue}, Asset{xrpIssue()})}), + Fee(static_cast(env.current()->fees().increment.drops()))), + sfAmount, + badAmount, + alice); + tx.ter = temBAD_AMOUNT; + env.submit(tx); + } + + testcase("featureMPTokensV2 disabled rejects MPT AMMClawback amounts"); + { + Env env{*this, withoutFixAndV2}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + amm::ammClawback( + gw, + alice, + Asset{issue}, + Asset{xrpIssue()}, + std::make_optional(STAmount{issue, std::uint64_t{1}})), + Fee(static_cast(env.current()->fees().increment.drops()))), + sfAmount, + badAmount, + gw); + expectRoundTripBadMPT(tx, sfAmount, bad); + tx.ter = temDISABLED; + env.submit(tx); + } + { + Env env{*this, withFixAndWithoutV2}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + amm::ammClawback( + gw, + alice, + Asset{issue}, + Asset{xrpIssue()}, + std::make_optional(STAmount{issue, std::uint64_t{1}})), + Fee(static_cast(env.current()->fees().increment.drops()))), + sfAmount, + badAmount, + gw); + tx.ter = temDISABLED; + env.submit(tx); + } + { + // sfAmount is MPT: both amendments active, expect temBAD_AMOUNT + Env env{*this, withFix}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + auto tx = withNonCanonicalMPTAmount( + env.jt( + amm::ammClawback( + gw, + alice, + Asset{issue}, + Asset{xrpIssue()}, + std::make_optional(STAmount{issue, std::uint64_t{1}})), + Fee(static_cast(env.current()->fees().increment.drops()))), + sfAmount, + badAmount, + gw); + tx.ter = temBAD_AMOUNT; + env.submit(tx); + } + + testcase("fixCleanup3_2_0 rejects non-canonical MPT VaultClawback amounts"); + { + Env env{*this, withFix}; + env.fund(XRP(100'000), alice, bob, gw); + env.close(); + auto const issue = makeIssue(env); + + auto const badAmount = badMPTAmount(issue, bad); + uint256 const fakeVaultId = keylet::vault(gw.id(), 1).key; + auto tx = withNonCanonicalMPTAmount( + env.jt( + Vault::clawback( + {.issuer = gw, + .id = fakeVaultId, + .holder = alice, + .amount = STAmount{issue, std::uint64_t{1}}}), + Fee(static_cast(env.current()->fees().increment.drops()))), + sfAmount, + badAmount, + gw); + tx.ter = temBAD_AMOUNT; + env.submit(tx); + } + } + } + void testTxJsonMetaFields(FeatureBitset features) { @@ -6947,7 +7812,7 @@ public: // Test MPT Amount is invalid in Tx, which don't support MPT testMPTInvalidInTx(all); - + testNonCanonicalMPTAmountCleanup(all); // Test parsed MPTokenIssuanceID in API response metadata testTxJsonMetaFields(all); diff --git a/src/test/app/OfferMPT_test.cpp b/src/test/app/OfferMPT_test.cpp index 3f88e57bc7..e9366f7c32 100644 --- a/src/test/app/OfferMPT_test.cpp +++ b/src/test/app/OfferMPT_test.cpp @@ -973,7 +973,7 @@ public: // Offers with negative amounts { - env(offer(alice, -usd(1'000), XRP(1'000)), Ter(temBAD_OFFER)); + env(offer(alice, -usd(1'000), XRP(1'000)), Ter(temBAD_AMOUNT)); env.require(Owners(alice, 1), offers(alice, 0)); } From 30de556224bdcdedb1732a8865fcce3b40ab2531 Mon Sep 17 00:00:00 2001 From: Valentin Balaschenko <13349202+vlntb@users.noreply.github.com> Date: Sat, 23 May 2026 15:48:48 +0100 Subject: [PATCH 021/135] fix: Address review feedback on FD/handle guarding (#5823 follow-up) (#7310) --- include/xrpl/server/detail/Door.h | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/include/xrpl/server/detail/Door.h b/include/xrpl/server/detail/Door.h index b72bc9bdea..79d36cae0c 100644 --- a/include/xrpl/server/detail/Door.h +++ b/include/xrpl/server/detail/Door.h @@ -23,7 +23,6 @@ #include #include -#include #endif #include @@ -99,7 +98,10 @@ private: static constexpr std::chrono::milliseconds kMaxAcceptDelay{2000}; std::chrono::milliseconds acceptDelay_{kInitialAcceptDelay}; boost::asio::steady_timer backoffTimer_; - static constexpr double kFreeFdThreshold = 0.70; + static constexpr std::uint64_t kMaxUsedFdPercent = 70; + static constexpr std::chrono::milliseconds kFdSampleInterval{250}; + clock_type::time_point fdSampleAt_; + bool cachedThrottle_{false}; struct FDStats { @@ -280,6 +282,7 @@ Door::Door( , acceptor_(ioContext) , strand_(boost::asio::make_strand(ioContext)) , backoffTimer_(ioContext) + , fdSampleAt_(clock_type::now() - kFdSampleInterval) { reOpen(); } @@ -338,11 +341,11 @@ Door::doAccept(boost::asio::yield_context doYield) { if (shouldThrottleForFds()) { + JLOG(j_.warn()) << "Throttling do_accept for " << acceptDelay_.count() << "ms."; backoffTimer_.expires_after(acceptDelay_); boost::system::error_code tec; backoffTimer_.async_wait(doYield[tec]); acceptDelay_ = std::min(acceptDelay_ * 2, kMaxAcceptDelay); - JLOG(j_.warn()) << "Throttling do_accept for " << acceptDelay_.count() << "ms."; continue; } @@ -359,8 +362,11 @@ Door::doAccept(boost::asio::yield_context doYield) if (ec == boost::asio::error::no_descriptors || ec == boost::asio::error::no_buffer_space) { - JLOG(j_.warn()) << "accept: Too many open files. Pausing for " - << acceptDelay_.count() << "ms."; + char const* const cause = (ec == boost::asio::error::no_descriptors) + ? "too many open files" + : "kernel buffer space exhausted"; + JLOG(j_.warn()) << "accept: " << cause << ". Pausing for " << acceptDelay_.count() + << "ms."; backoffTimer_.expires_after(acceptDelay_); boost::system::error_code tec; @@ -428,14 +434,15 @@ Door::shouldThrottleForFds() #if BOOST_OS_WINDOWS return false; #else - auto const stats = queryFdStats(); - if (!stats || stats->limit == 0) - return false; + auto const now = clock_type::now(); + if (now - fdSampleAt_ < kFdSampleInterval) + return cachedThrottle_; - auto const& s = *stats; - auto const free = (s.limit > s.used) ? (s.limit - s.used) : 0ull; - double const freeRatio = static_cast(free) / static_cast(s.limit); - return freeRatio < kFreeFdThreshold; + fdSampleAt_ = now; + auto const stats = queryFdStats(); + cachedThrottle_ = + stats && stats->limit > 0 && stats->used * 100 > stats->limit * kMaxUsedFdPercent; + return cachedThrottle_; #endif } From e34c2667d7b29cc38d83e8ec7190cd7769ec4e9f Mon Sep 17 00:00:00 2001 From: Peter Chen <34582813+PeterChen13579@users.noreply.github.com> Date: Sun, 24 May 2026 16:37:16 -0400 Subject: [PATCH 022/135] fix: Skip deleted book directories and non-root modifications in `ValidBookDirectory` invariant (#7312) --- .../tx/invariants/DirectoryInvariant.cpp | 18 ++++-- src/test/app/Invariants_test.cpp | 64 +++++++++++++++++++ 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/libxrpl/tx/invariants/DirectoryInvariant.cpp b/src/libxrpl/tx/invariants/DirectoryInvariant.cpp index 2a4fac07d0..1624a19830 100644 --- a/src/libxrpl/tx/invariants/DirectoryInvariant.cpp +++ b/src/libxrpl/tx/invariants/DirectoryInvariant.cpp @@ -40,19 +40,27 @@ badExchangeRate(SLE const& dir) void ValidBookDirectory::visitEntry( - bool, + bool isDelete, std::shared_ptr const& before, std::shared_ptr const& after) { // New root directories must have matching exchange-rate metadata. New - // child directories must point to an existing root. + // child directories, and modified directories that change sfRootIndex, must + // point to an existing root. - // Only validate newly-created directories; LedgerStateFix handles legacy - // bad exchange-rate metadata. - if (badBookDirectory_ || before || !after || after->getType() != ltDIR_NODE) + // Only validate newly-created directories and sfRootIndex changes; + // LedgerStateFix handles legacy bad exchange-rate metadata. Skip deletions + // because `after` is not guaranteed to be null. + if (badBookDirectory_ || isDelete || !after || after->getType() != ltDIR_NODE) return; auto const rootIndex = after->getFieldH256(sfRootIndex); + // Ignore ordinary modifications that do not change which root this + // directory belongs to. That tolerates legacy bad exchange-rate metadata + // during normal operation while still checking sfRootIndex changes. + if (before && before->getFieldH256(sfRootIndex) == rootIndex) + return; + if (after->key() == rootIndex && !badBookDirectory_) { badBookDirectory_ = badBookDirectory_ || badExchangeRate(*after); diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index cc16948553..c8a6e813de 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -2137,6 +2137,70 @@ class Invariants_test : public beast::unit_test::Suite BEAST_EXPECT( invariant.finalize(makeOfferCreateTx(), tesSUCCESS, XRPAmount{}, view, jlog)); } + + // A bad root is rejected when added, ignored when a legacy bad root is + // modified without changing sfRootIndex or deleted, and checked when a + // modified directory changes sfRootIndex. + { + Env env{*this, defaultAmendments()}; + Account const a1{"A1"}; + env.fund(XRP(1000), a1); + env.close(); + + OpenView view{*env.current()}; + auto const directoryQuality = STAmount::kURateOne; + auto const rootDir = getBookRootKey(a1, directoryQuality); + auto const missingRootDir = getBookRootKey(a1, directoryQuality + 1); + auto const badRoot = makeRootPage(rootDir, directoryQuality + 1); + view.rawInsert(badRoot); + + test::StreamSink sink{beast::Severity::Warning}; + beast::Journal const jlog{sink}; + + { + // add + ValidBookDirectory invariant; + invariant.visitEntry(false, nullptr, badRoot); + + BEAST_EXPECT( + !invariant.finalize(makeOfferCreateTx(), tesSUCCESS, XRPAmount{}, view, jlog)); + } + { + // modify (without changing the sfRootIndex) + ValidBookDirectory invariant; + invariant.visitEntry(false, badRoot, badRoot); + + BEAST_EXPECT( + invariant.finalize(makeOfferCreateTx(), tesSUCCESS, XRPAmount{}, view, jlog)); + } + { + // modify (changing sfRootIndex to a missing root) + auto const childBefore = makeChildPage(rootDir); + auto const childAfter = std::make_shared(*childBefore, childBefore->key()); + childAfter->setFieldH256(sfRootIndex, missingRootDir.key); + + ValidBookDirectory invariant; + invariant.visitEntry(false, childBefore, childAfter); + + test::StreamSink missingRootSink{beast::Severity::Warning}; + beast::Journal const missingRootJlog{missingRootSink}; + BEAST_EXPECT(!invariant.finalize( + makeOfferCreateTx(), tesSUCCESS, XRPAmount{}, view, missingRootJlog)); + BEAST_EXPECT( + missingRootSink.messages().str().find("book directory root missing") != + std::string::npos); + } + { + // delete + view.rawErase(badRoot); + BEAST_EXPECT(!view.exists(rootDir)); + + ValidBookDirectory invariant; + invariant.visitEntry(true, badRoot, badRoot); + BEAST_EXPECT( + invariant.finalize(makeOfferCreateTx(), tesSUCCESS, XRPAmount{}, view, jlog)); + } + } } Keylet From a911f9089e7c2f67b564dd888715fbf71e1197fd Mon Sep 17 00:00:00 2001 From: Jingchen Date: Sun, 24 May 2026 21:44:29 +0100 Subject: [PATCH 023/135] fix: Use consistent scale for `debtTotal` (#7093) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- include/xrpl/ledger/helpers/LendingHelpers.h | 15 + .../lending/LoanBrokerCoverClawback.cpp | 29 +- .../lending/LoanBrokerCoverWithdraw.cpp | 6 + .../tx/transactors/lending/LoanPay.cpp | 30 +- .../tx/transactors/lending/LoanSet.cpp | 18 +- src/test/app/Loan_test.cpp | 409 ++++++++++++++++++ 6 files changed, 482 insertions(+), 25 deletions(-) diff --git a/include/xrpl/ledger/helpers/LendingHelpers.h b/include/xrpl/ledger/helpers/LendingHelpers.h index cce41a38c5..b7a1ed6bf2 100644 --- a/include/xrpl/ledger/helpers/LendingHelpers.h +++ b/include/xrpl/ledger/helpers/LendingHelpers.h @@ -203,6 +203,21 @@ getAssetsTotalScale(SLE::const_ref vaultSle) return scale(vaultSle->at(sfAssetsTotal), vaultSle->at(sfAsset)); } +// Compute the minimum required broker cover, rounded consistently. +// DebtTotal is a broker-level aggregate maintained at vault scale, so the +// rounding must also use vault scale — never an individual loan's scale. +inline Number +minimumBrokerCover(Number const& debtTotal, TenthBips32 coverRateMinimum, SLE::const_ref vaultSle) +{ + XRPL_ASSERT( + vaultSle && vaultSle->getType() == ltVAULT, "xrpl::minimumBrokerCover : valid Vault sle"); + NumberRoundModeGuard const mg(Number::RoundingMode::Upward); + return roundToAsset( + vaultSle->at(sfAsset), + tenthBipsOfValue(debtTotal, coverRateMinimum), + getAssetsTotalScale(vaultSle)); +} + TER checkLoanGuards( Asset const& vaultAsset, diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp index 48cb6b90aa..11095fdebe 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -160,15 +161,25 @@ Expected determineClawAmount( SLE const& sleBroker, Asset const& vaultAsset, - std::optional const& amount) + std::optional const& amount, + SLE::const_ref vaultSle, + Rules const& rules) { auto const maxClawAmount = [&]() { - // Always round the minimum required up - NumberRoundModeGuard const mg1(Number::RoundingMode::Upward); - auto const minRequiredCover = - tenthBipsOfValue(sleBroker[sfDebtTotal], TenthBips32(sleBroker[sfCoverRateMinimum])); + auto const minRequiredCover = [&]() { + if (rules.enabled(fixCleanup3_2_0)) + { + return minimumBrokerCover( + sleBroker[sfDebtTotal], TenthBips32(sleBroker[sfCoverRateMinimum]), vaultSle); + } + + // Always round the minimum required up + NumberRoundModeGuard const mg(Number::RoundingMode::Upward); + return tenthBipsOfValue( + sleBroker[sfDebtTotal], TenthBips32(sleBroker[sfCoverRateMinimum])); + }(); // The subtraction probably won't round, but round down if it does. - NumberRoundModeGuard const mg2(Number::RoundingMode::Downward); + NumberRoundModeGuard const mg(Number::RoundingMode::Downward); return sleBroker[sfCoverAvailable] - minRequiredCover; }(); if (maxClawAmount <= beast::kZero) @@ -283,7 +294,8 @@ LoanBrokerCoverClawback::preclaim(PreclaimContext const& ctx) } } - auto const findClawAmount = determineClawAmount(*sleBroker, vaultAsset, amount); + auto const findClawAmount = + determineClawAmount(*sleBroker, vaultAsset, amount, vault, ctx.view.rules()); if (!findClawAmount) { JLOG(ctx.j.warn()) << "LoanBroker cover is already at minimum."; @@ -345,7 +357,8 @@ LoanBrokerCoverClawback::doApply() auto const vaultAsset = vault->at(sfAsset); - auto const findClawAmount = determineClawAmount(*sleBroker, vaultAsset, amount); + auto const findClawAmount = + determineClawAmount(*sleBroker, vaultAsset, amount, vault, view().rules()); if (!findClawAmount) return tecINTERNAL; // LCOV_EXCL_LINE STAmount const& clawAmount = *findClawAmount; diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp index dbe2de2100..f4c95541f6 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp @@ -142,6 +142,12 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx) // Cover Rate is in 1/10 bips units auto const currentDebtTotal = sleBroker->at(sfDebtTotal); auto const minimumCover = [&]() { + if (ctx.view.rules().enabled(fixCleanup3_2_0)) + { + return minimumBrokerCover( + currentDebtTotal, TenthBips32{sleBroker->at(sfCoverRateMinimum)}, vault); + } + // Always round the minimum required up. // Applies to `tenthBipsOfValue` as well as `roundToAsset`. NumberRoundModeGuard const mg(Number::RoundingMode::Upward); diff --git a/src/libxrpl/tx/transactors/lending/LoanPay.cpp b/src/libxrpl/tx/transactors/lending/LoanPay.cpp index b8b940f3e3..65220573de 100644 --- a/src/libxrpl/tx/transactors/lending/LoanPay.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanPay.cpp @@ -311,6 +311,8 @@ LoanPay::doApply() TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)}; auto debtTotalProxy = brokerSle->at(sfDebtTotal); + auto const vaultScale = getAssetsTotalScale(vaultSle); + // Send the broker fee to the owner if they have sufficient cover available, // _and_ if the owner can receive funds // _and_ if the broker is authorized to hold funds. If not, so as not to @@ -320,14 +322,22 @@ LoanPay::doApply() // Normally freeze status is checked in preclaim, but we do it here to // avoid duplicating the check. It'll claim a fee either way. bool const sendBrokerFeeToOwner = [&]() { - // Round the minimum required cover up to be conservative. This ensures - // CoverAvailable never drops below the theoretical minimum, protecting - // the broker's solvency. - NumberRoundModeGuard const mg(Number::RoundingMode::Upward); - return coverAvailableProxy >= - roundToAsset( - asset, tenthBipsOfValue(debtTotalProxy.value(), coverRateMinimum), loanScale) && - !isDeepFrozen(view, brokerOwner, asset) && + // In the fixCleanup3_2_0 path, vault-related values (for example, + // DebtTotal) use vaultScale. The legacy path below intentionally retains + // its pre-amendment loanScale behavior. + auto const minCover = [&]() { + if (view.rules().enabled(fixCleanup3_2_0)) + { + return minimumBrokerCover(debtTotalProxy.value(), coverRateMinimum, vaultSle); + } + // Round the minimum required cover up to be conservative. This ensures + // CoverAvailable never drops below the theoretical minimum, protecting + // the broker's solvency. + NumberRoundModeGuard const mg(Number::RoundingMode::Upward); + return roundToAsset( + asset, tenthBipsOfValue(debtTotalProxy.value(), coverRateMinimum), loanScale); + }(); + return coverAvailableProxy >= minCover && !isDeepFrozen(view, brokerOwner, asset) && !requireAuth(view, asset, brokerOwner, AuthType::StrongAuth); }(); @@ -423,10 +433,6 @@ LoanPay::doApply() auto assetsAvailableProxy = vaultSle->at(sfAssetsAvailable); auto assetsTotalProxy = vaultSle->at(sfAssetsTotal); - // The vault may be at a different scale than the loan. Reduce rounding - // errors during the payment by rounding some of the values to that scale. - auto const vaultScale = getAssetsTotalScale(vaultSle); - auto const totalPaidToVaultRaw = paymentParts->principalPaid + paymentParts->interestPaid; auto const totalPaidToVaultRounded = roundToAsset(asset, totalPaidToVaultRaw, vaultScale, Number::RoundingMode::Downward); diff --git a/src/libxrpl/tx/transactors/lending/LoanSet.cpp b/src/libxrpl/tx/transactors/lending/LoanSet.cpp index a3b71fd20d..561ab6b2aa 100644 --- a/src/libxrpl/tx/transactors/lending/LoanSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanSet.cpp @@ -493,11 +493,19 @@ LoanSet::doApply() } TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)}; { - // Round the minimum required cover up to be conservative. This ensures - // CoverAvailable never drops below the theoretical minimum, protecting - // the broker's solvency. - NumberRoundModeGuard const mg(Number::RoundingMode::Upward); - if (brokerSle->at(sfCoverAvailable) < tenthBipsOfValue(newDebtTotal, coverRateMinimum)) + auto const minCover = [&]() { + if (ctx_.view().rules().enabled(fixCleanup3_2_0)) + { + return minimumBrokerCover(newDebtTotal, coverRateMinimum, vaultSle); + } + + // Round the minimum required cover up to be conservative. This ensures + // CoverAvailable never drops below the theoretical minimum, protecting + // the broker's solvency. + NumberRoundModeGuard const mg(Number::RoundingMode::Upward); + return tenthBipsOfValue(newDebtTotal, coverRateMinimum); + }(); + if (brokerSle->at(sfCoverAvailable) < minCover) { JLOG(j_.warn()) << "Insufficient first-loss capital to cover the loan."; return tecINSUFFICIENT_FUNDS; diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index b5687265ab..5e8e89cefa 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -7858,6 +7858,414 @@ protected: to_string(tolerance)); } + // Verify that LoanPay, LoanBrokerCoverWithdraw, and LoanSet all use the + // same vault-scale minimum cover when fixCleanup3_2_0 is enabled. + // Before the amendment, each transactor computed its minimum cover at a + // different precision (loanScale, debtScale, or the raw unrounded + // tenthBipsOfValue), which could lead to inconsistent decisions for the + // same broker state. After the amendment all three use + // minimumBrokerCover at vaultScale. + void + testMinimumBrokerCoverConsistency(FeatureBitset features) + { + using namespace jtx; + using namespace loan; + using namespace loanBroker; + + bool const withAmendment = features[fixCleanup3_2_0]; + + struct Ctx + { + jtx::Account issuer; + jtx::Account lender; + jtx::Account borrower; + jtx::PrettyAsset iou; + BrokerInfo broker; + BrokerParameters brokerParams; + }; + + // Shared setup, parametrized by vaultDeposit (the only varying setup + // field across the three scenarios). Each call runs in its own Env + // so multiple invocations within one scenario cannot interfere. + // The caller is responsible for invoking testcase(...) before the + // first runTest call of each scenario. + auto runTest = [&](Number vaultDeposit, auto&& body) { + Env env(*this, features); + + Account const issuer{"issuer"}; + Account const lender{"lender"}; + Account const borrower{"borrower"}; + + env.fund(XRP(1'000'000'000), issuer, lender, borrower); + env.close(); + + // Enable clawback on the issuer *before* any trust lines exist + // (asfAllowTrustLineClawback requires an empty owner directory). + env(fset(issuer, asfAllowTrustLineClawback)); + env.close(); + + PrettyAsset const iou = issuer[iouCurrency_]; + env(trust(lender, iou(1'000'000'000))); + env(trust(borrower, iou(1'000'000'000))); + env.close(); + env(pay(issuer, lender, iou(100'000'000))); + env(pay(issuer, borrower, iou(100'000'000))); + env.close(); + + // 13.37% — non-round rate produces a messier minimum. + BrokerParameters const brokerParams{ + .vaultDeposit = vaultDeposit, + .debtMax = 0, + .coverRateMin = TenthBips32{13'370}, + .coverDeposit = 5'000, + .managementFeeRate = TenthBips16{500}}; + + BrokerInfo const broker = createVaultAndBroker(env, iou, lender, brokerParams); + + body( + env, + Ctx{.issuer = issuer, + .lender = lender, + .borrower = borrower, + .iou = iou, + .broker = broker, + .brokerParams = brokerParams}); + }; + + // Scenario 1 — LoanPay + // + // Verify that LoanPay's minimum cover check uses vault scale (not + // loan scale). Before the amendment, different loans could produce + // different fee routing decisions for the same broker-level state. + // Small vault deposit => vaultScale = -12. + testcase("LoanPay minimum cover scale consistency"); + { + struct LoanKeylets + { + Keylet tiny; + Keylet big; + }; + + // Create the tiny + big loans and reduce cover via clawback so + // that subsequent LoanPay calls hit the minimum-cover boundary. + // Used by the two pay-and-check sub-tests below so each can run + // in its own Env. + auto setupLoansAndClawback = [&](Env& env, Ctx const& c) -> std::optional { + Asset const asset{c.iou}; + + // Create the TINY loan first (while vaultScale is still + // small). principal 0.01, 0% interest, 1 payment => + // loanScale = vaultScale. + auto const brokerSle1 = env.le(keylet::loanbroker(c.broker.brokerID)); + if (!BEAST_EXPECT(brokerSle1)) + return std::nullopt; + auto const tinyLoanSeq = brokerSle1->at(sfLoanSequence); + auto const tinyLoanKeylet = keylet::loan(c.broker.brokerID, tinyLoanSeq); + + env(set(c.borrower, c.broker.brokerID, Number{1, -2}), + Sig(sfCounterpartySignature, c.lender), + kInterestRate(TenthBips32{0}), + kPaymentTotal(1), + kPaymentInterval(86400 * 365), + Fee(XRP(10))); + env.close(); + + // Create the BIG loan second. 100% annual interest over 20 + // payments pushes totalValueOutstanding high enough that + // loanScale > vaultScale. + auto const brokerSle2 = env.le(keylet::loanbroker(c.broker.brokerID)); + if (!BEAST_EXPECT(brokerSle2)) + return std::nullopt; + auto const bigLoanSeq = brokerSle2->at(sfLoanSequence); + auto const bigLoanKeylet = keylet::loan(c.broker.brokerID, bigLoanSeq); + + env(set(c.borrower, c.broker.brokerID, Number{500}), + Sig(sfCounterpartySignature, c.lender), + kInterestRate(TenthBips32{100'000}), + kPaymentTotal(20), + kPaymentInterval(86400 * 365), + Fee(XRP(10))); + env.close(); + + // The tiny loan's scale is frozen at the vault's pre-big-loan + // scale, so it is strictly smaller than the big loan's. + // After the big loan is created the vault absorbs its value, + // pushing vaultScale up to match bigLoanScale. + auto const tinyLoanSle = env.le(tinyLoanKeylet); + auto const bigLoanSle = env.le(bigLoanKeylet); + auto const vaultSle = env.le(keylet::vault(c.broker.vaultID)); + if (!BEAST_EXPECT(tinyLoanSle) || !BEAST_EXPECT(bigLoanSle) || + !BEAST_EXPECT(vaultSle)) + return std::nullopt; + if (!BEAST_EXPECT(tinyLoanSle->at(sfLoanScale) == -12) || + !BEAST_EXPECT(bigLoanSle->at(sfLoanScale) == -11) || + !BEAST_EXPECT(getAssetsTotalScale(vaultSle) == -11)) + return std::nullopt; + + // Use issuer clawback to reduce cover to the minimum the + // clawback transactor allows. Compute the amount as + // initialCover - expectedCoverAfter so we exercise the exact + // clawback rather than relying on the transactor to clip + // down. + // + // Before the amendment the clawback minimum is the + // *unrounded* tenthBipsOfValue — strictly less than the + // rounded-at-vaultScale minimum LoanPay uses for the big + // loan. After the amendment both clawback and LoanPay use + // the same rounded minimum (via minimumBrokerCover), so + // cover lands exactly at that threshold. + Number const expectedCoverAfter = withAmendment ? Number{1330651855688460000, -15} + : Number{1330651855688458000, -15}; + Number const clawbackAmount = + Number{c.brokerParams.coverDeposit} - expectedCoverAfter; + + env(coverClawback(c.issuer), + kLoanBrokerId(c.broker.brokerID), + kAmount(STAmount{asset, clawbackAmount})); + env.close(); + + auto const brokerSle = env.le(keylet::loanbroker(c.broker.brokerID)); + if (!BEAST_EXPECT(brokerSle) || + !BEAST_EXPECT(brokerSle->at(sfCoverAvailable) == expectedCoverAfter)) + return std::nullopt; + + return LoanKeylets{.tiny = tinyLoanKeylet, .big = bigLoanKeylet}; + }; + + // Pay one loan and report whether the fee went to the broker's + // pseudo account (the fallback when cover < minimum) rather + // than to the owner. + auto feeGoesToPseudo = [&](Env& env, Ctx const& c, Keylet const& loanKeylet) -> bool { + Asset const asset{c.iou}; + auto const brokerSle = env.le(keylet::loanbroker(c.broker.brokerID)); + if (!BEAST_EXPECT(brokerSle)) + return false; + auto const pseudoAcct = Account("pseudo", brokerSle->at(sfAccount)); + auto const pseudoBefore = env.balance(pseudoAcct, c.iou); + + auto const payLoan = env.le(loanKeylet); + if (!BEAST_EXPECT(payLoan)) + return false; + auto const periodicPayment = payLoan->at(sfPeriodicPayment); + auto const serviceFee = payLoan->at(sfLoanServiceFee); + std::int32_t const loanScale = payLoan->at(sfLoanScale); + + auto const payment = roundPeriodicPayment(asset, periodicPayment, loanScale); + auto const payAmt = STAmount{asset, payment + serviceFee}; + + env(loan::pay(c.borrower, loanKeylet.key, payAmt), Fee(XRP(10))); + env.close(); + + auto const pseudoAfter = env.balance(pseudoAcct, c.iou); + return pseudoAfter.number() > pseudoBefore.number(); + }; + + // Pay the BIG loan in its own Env so its outcome cannot affect + // the TINY-loan check. With the fix, LoanPay and clawback use + // the same vaultScale minimum (cover == minAtVaultScale => + // fee to owner). Without the fix, LoanPay uses bigLoanScale=-11, + // rounds up to a larger minimum than what clawback used => + // cover < min => fee to pseudo. + runTest(/*vaultDeposit=*/1'000, [&](Env& env, Ctx const& c) { + auto const loans = setupLoansAndClawback(env, c); + if (!loans) + return; + BEAST_EXPECT(feeGoesToPseudo(env, c, loans->big) == !withAmendment); + }); + + // Pay the TINY loan in its own Env. Fee goes to the owner + // either way: + // - With the fix: LoanPay uses vaultScale=-11 (same as + // clawback) => owner. + // - Without the fix: LoanPay uses tinyLoanScale=-12, rounds + // up at -12 (a no-op) => min == cover => owner. + runTest(/*vaultDeposit=*/1'000, [&](Env& env, Ctx const& c) { + auto const loans = setupLoansAndClawback(env, c); + if (!loans) + return; + BEAST_EXPECT(!feeGoesToPseudo(env, c, loans->tiny)); + }); + } + + // Scenario 2 — LoanBrokerCoverWithdraw + // + // Verify that CoverWithdraw's minimum cover check uses vault scale + // (not scale(debtTotal, asset)). Before the amendment, CoverWithdraw + // used: + // roundToAsset(asset, tenthBipsOfValue(debt, rate), scale(debt, asset)) + // which could disagree with LoanPay's minimum (which used loanScale). + // + // Use a large vault deposit so that vaultScale (from AssetsTotal) is + // strictly larger than debtScale (from DebtTotal). With + // vaultDeposit = 100,000: after the big loan + // AssetsTotal ≈ 109,500 → vaultScale = -10 + // DebtTotal ≈ 10,000 → debtScale = -11 + // The one-order-of-magnitude gap makes roundToAsset at -10 truncate + // more aggressively than at -11, exposing the bug. + testcase("CoverWithdraw minimum cover scale consistency"); + runTest( + /*vaultDeposit=*/100'000, [&](Env& env, Ctx const& c) { + Asset const asset{c.iou}; + + // Create only the big loan to push DebtTotal up to ~10,000 + // while AssetsTotal stays around 109,500 (dominated by the + // large vault deposit). + env(set(c.borrower, c.broker.brokerID, Number{500}), + Sig(sfCounterpartySignature, c.lender), + kInterestRate(TenthBips32{100'000}), + kPaymentTotal(20), + kPaymentInterval(86400 * 365), + Fee(XRP(10))); + env.close(); + + // Read broker state and compute both old and new minimums. + auto const brokerSle = env.le(keylet::loanbroker(c.broker.brokerID)); + auto const vaultSle = env.le(keylet::vault(c.broker.vaultID)); + if (!BEAST_EXPECT(brokerSle) || !BEAST_EXPECT(vaultSle)) + return; + + auto const coverAvail = brokerSle->at(sfCoverAvailable); + auto const debtTotal = brokerSle->at(sfDebtTotal); + auto const vaultScale = getAssetsTotalScale(vaultSle); + auto const debtScale = scale(debtTotal, asset); + + // Sanity: debt scale differs from vault scale for this setup. + BEAST_EXPECT(debtScale < vaultScale); + + auto const oldMin = [&]() { + NumberRoundModeGuard const mg(Number::RoundingMode::Upward); + return roundToAsset( + asset, + tenthBipsOfValue(debtTotal, TenthBips32{c.brokerParams.coverRateMin}), + debtScale); + }(); + auto const newMin = minimumBrokerCover( + debtTotal, TenthBips32{c.brokerParams.coverRateMin}, vaultSle); + + // The new (vaultScale) minimum must be strictly larger than + // the old (debtScale) minimum — that is the gap the amendment + // closes. + Number const expectedNewMin{1330650518688500000, -15}; + Number const expectedOldMin{1330650518688472000, -15}; + BEAST_EXPECT(newMin == expectedNewMin); + BEAST_EXPECT(oldMin == expectedOldMin); + + // Try to withdraw so that remaining cover lands between the + // two minimums: oldMin < target < newMin. + auto const target = oldMin + (newMin - oldMin) / 2; + auto const withdrawAmount = STAmount{asset, coverAvail - target}; + + if (withAmendment) + { + // CoverWithdraw now uses vaultScale: target < newMin + // => FAILS. + env(coverWithdraw(c.lender, c.broker.brokerID, withdrawAmount), + Ter(tecINSUFFICIENT_FUNDS)); + } + else + { + // Old CoverWithdraw uses debtScale: target > oldMin + // => SUCCEEDS. + env(coverWithdraw(c.lender, c.broker.brokerID, withdrawAmount)); + } + env.close(); + }); + + // Scenario 3 — LoanSet + // + // Verify that LoanSet's minimum cover check uses vault scale (not the + // raw unrounded tenthBipsOfValue). Before the amendment, LoanSet + // used tenthBipsOfValue(newDebtTotal, coverRateMinimum) (no + // roundToAsset), while clawback/withdraw used different formulas. + // After the amendment all use minimumBrokerCover at vaultScale, and + // rounding at a coarser scale can absorb a tiny debt increase — + // allowing a loan that would otherwise be rejected. + testcase("LoanSet minimum cover scale consistency"); + runTest( + /*vaultDeposit=*/1'000, [&](Env& env, Ctx const& c) { + // Create the tiny loan (scale -12) AND the big loan (scale + // -11). Both loans are needed so that DebtTotal has a full + // 16-digit mantissa — a "messy" value where roundToAsset at + // vaultScale actually truncates digits and produces a + // different result from the raw tenthBipsOfValue. With only + // the big loan, DebtTotal has ~4 significant digits and + // rounding at scale -11 is a no-op, masking the amendment's + // effect. + env(set(c.borrower, c.broker.brokerID, Number{1, -2}), + Sig(sfCounterpartySignature, c.lender), + kInterestRate(TenthBips32{0}), + kPaymentTotal(1), + kPaymentInterval(86400 * 365), + Fee(XRP(10))); + env.close(); + + env(set(c.borrower, c.broker.brokerID, Number{500}), + Sig(sfCounterpartySignature, c.lender), + kInterestRate(TenthBips32{100'000}), + kPaymentTotal(20), + kPaymentInterval(86400 * 365), + Fee(XRP(10))); + env.close(); + + // Clawback to reduce cover to the clawback transactor's + // minimum. Pass the exact amount rather than relying on the + // transactor to clip down; the setup matches Scenario 1 so + // the same residual-cover values apply. + Number const expectedCoverAfter = withAmendment ? Number{1330651855688460000, -15} + : Number{1330651855688458000, -15}; + Number const clawbackAmount = + Number{c.brokerParams.coverDeposit} - expectedCoverAfter; + env(coverClawback(c.issuer), + kLoanBrokerId(c.broker.brokerID), + kAmount(c.iou(clawbackAmount))); + env.close(); + + // Verify scales. + auto const vaultSle = env.le(keylet::vault(c.broker.vaultID)); + if (!BEAST_EXPECT(vaultSle)) + return; + auto const vaultScale = getAssetsTotalScale(vaultSle); + BEAST_EXPECT(vaultScale == -11); + + // Now try to create a tiny additional loan. Principal is + // 1e-11 (the smallest value that survives the precision + // check at loanScale = vaultScale = -11), with 0% interest + // and 1 payment. + // + // The tiny debt increase adds ~1.337e-12 to the unrounded + // minimum. + // - Without the amendment: the old LoanSet formula rounds + // up during tenthBipsOfValue (16-digit Number + // normalisation), pushing the minimum past the cover left + // by clawback => tecINSUFFICIENT_FUNDS. + // - With the amendment: minimumBrokerCover rounds at + // vaultScale=-11, which absorbs the tiny increase — the + // rounded minimum stays the same => tesSUCCESS. + auto const tinyPrincipal = Number{1, -11}; + + if (withAmendment) + { + env(set(c.borrower, c.broker.brokerID, tinyPrincipal), + Sig(sfCounterpartySignature, c.lender), + kInterestRate(TenthBips32{0}), + kPaymentTotal(1), + kPaymentInterval(86400 * 365), + Fee(XRP(10))); + } + else + { + env(set(c.borrower, c.broker.brokerID, tinyPrincipal), + Sig(sfCounterpartySignature, c.lender), + kInterestRate(TenthBips32{0}), + kPaymentTotal(1), + kPaymentInterval(86400 * 365), + Fee(XRP(10)), + Ter(tecINSUFFICIENT_FUNDS)); + } + env.close(); + }); + } + void runAmendmentIndependent() { @@ -7919,6 +8327,7 @@ protected: testOverpaymentManagementFee(features); testIssuerIsBorrower(features); testIntegerScalePrincipalSticks(features); + testMinimumBrokerCoverConsistency(features); // RIPD regressions testRIPD3831(features); From e9d885bd9b8d7b6f0fb0fbcd3db7ff39df34e68b Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Tue, 26 May 2026 14:50:18 +0100 Subject: [PATCH 024/135] fix: Fix clang-tidy pre-commit hook to locate compile_commands.json from repo root (#7325) Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> --- .github/scripts/levelization/generate.py | 0 bin/pre-commit/clang_tidy_check.py | 8 +++++++- 2 files changed, 7 insertions(+), 1 deletion(-) mode change 100644 => 100755 .github/scripts/levelization/generate.py diff --git a/.github/scripts/levelization/generate.py b/.github/scripts/levelization/generate.py old mode 100644 new mode 100755 diff --git a/bin/pre-commit/clang_tidy_check.py b/bin/pre-commit/clang_tidy_check.py index 7fb51d1c46..f134660671 100755 --- a/bin/pre-commit/clang_tidy_check.py +++ b/bin/pre-commit/clang_tidy_check.py @@ -168,7 +168,13 @@ def main(): if not os.environ.get("TIDY"): return 0 - repo_root = Path(__file__).parent.parent + repo_root = Path( + subprocess.check_output( + ["git", "rev-parse", "--show-toplevel"], + cwd=Path(__file__).parent, + text=True, + ).strip() + ) files = staged_files(repo_root) if not files: return 0 From 22a21b175efbf95a3435360ba5d6a85ffbfcbcd2 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Tue, 26 May 2026 16:01:52 +0200 Subject: [PATCH 025/135] fix: Include management-fee delta in doOverpayment assertion (#7039) --- src/libxrpl/ledger/helpers/LendingHelpers.cpp | 117 ++++++++++------- src/test/app/Loan_test.cpp | 118 ++++++++++++++---- 2 files changed, 168 insertions(+), 67 deletions(-) diff --git a/src/libxrpl/ledger/helpers/LendingHelpers.cpp b/src/libxrpl/ledger/helpers/LendingHelpers.cpp index 1fedbb5f13..9cda5905c9 100644 --- a/src/libxrpl/ledger/helpers/LendingHelpers.cpp +++ b/src/libxrpl/ledger/helpers/LendingHelpers.cpp @@ -769,9 +769,6 @@ doOverpayment( "xrpl::detail::doOverpayment", "principal change agrees"); - // I'm not 100% sure the following asserts are correct. If in doubt, and - // everything else works, remove any that cause trouble. - JLOG(j.debug()) << "valueChange: " << loanPaymentParts.valueChange << ", totalValue before: " << *totalValueOutstandingProxy << ", totalValue after: " << newRoundedLoanState.valueOutstanding @@ -783,11 +780,28 @@ doOverpayment( << overpaymentComponents.trackedPrincipalDelta - (totalValueOutstandingProxy - newRoundedLoanState.valueOutstanding); + // The valueChange returned by tryOverpayment satisfies + // valueChange = (newInterestDue - oldInterestDue) + untrackedInterest. + // Using the loan-state identity v = p + i + m and the adjacent + // `principal change agrees` assertion (dp = oldP - newP), this + // rearranges into three independently-computable terms: + // + // 1. TVO change beyond what principal repayment alone explains: + // newTVO - (oldTVO - dp) + // 2. Management fee released by re-amortization (positive when + // mfee decreased; zero when managementFeeRate == 0): + // oldMfee - newMfee + // 3. The overpayment's penalty interest part (= untrackedInterest + // for the overpayment path; see computeOverpaymentComponents): + // trackedInterestPart() + [[maybe_unused]] Number const tvoChange = newRoundedLoanState.valueOutstanding - + (totalValueOutstandingProxy - overpaymentComponents.trackedPrincipalDelta); + [[maybe_unused]] Number const managementFeeReleased = + managementFeeOutstandingProxy - newRoundedLoanState.managementFeeDue; + [[maybe_unused]] Number const interestPart = overpaymentComponents.trackedInterestPart(); + XRPL_ASSERT_PARTS( - loanPaymentParts.valueChange == - newRoundedLoanState.valueOutstanding - - (totalValueOutstandingProxy - overpaymentComponents.trackedPrincipalDelta) + - overpaymentComponents.trackedInterestPart(), + loanPaymentParts.valueChange == tvoChange + managementFeeReleased + interestPart, "xrpl::detail::doOverpayment", "interest paid agrees"); @@ -2027,51 +2041,62 @@ loanMakePayment( // It shouldn't be possible for the overpayment to be greater than // totalValueOutstanding, because that would have been processed as // another normal payment. But cap it just in case. - Number const overpayment = std::min(roundedAmount - totalPaid, *totalValueOutstandingProxy); + Number const overpaymentRaw = + std::min(roundedAmount - totalPaid, *totalValueOutstandingProxy); - detail::ExtendedPaymentComponents const overpaymentComponents = - detail::computeOverpaymentComponents( - asset, - loanScale, - overpayment, - overpaymentInterestRate, - overpaymentFeeRate, - managementFeeRate); + bool const fixEnabled = view.rules().enabled(fixCleanup3_2_0); + Number const overpayment = fixEnabled + ? roundToAsset(asset, overpaymentRaw, loanScale, Number::RoundingMode::Downward) + : overpaymentRaw; - // Don't process an overpayment if the whole amount (or more!) - // gets eaten by fees and interest. - if (overpaymentComponents.trackedPrincipalDelta > 0) + // Post-amendment, the rounded overpayment can be zero; pre-amendment + // it's always positive given the surrounding guards. + if (!fixEnabled || overpayment > 0) { - XRPL_ASSERT_PARTS( - overpaymentComponents.untrackedInterest >= beast::kZero, - "xrpl::loanMakePayment", - "overpayment penalty did not reduce value of loan"); - // Can't just use `periodicPayment` here, because it might - // change - auto periodicPaymentProxy = loan->at(sfPeriodicPayment); - if (auto const overResult = detail::doOverpayment( - view.rules(), + detail::ExtendedPaymentComponents const overpaymentComponents = + detail::computeOverpaymentComponents( asset, loanScale, - overpaymentComponents, - totalValueOutstandingProxy, - principalOutstandingProxy, - managementFeeOutstandingProxy, - periodicPaymentProxy, - periodicRate, - paymentRemainingProxy, - managementFeeRate, - j)) + overpayment, + overpaymentInterestRate, + overpaymentFeeRate, + managementFeeRate); + + // Don't process an overpayment if the whole amount (or more!) + // gets eaten by fees and interest. + if (overpaymentComponents.trackedPrincipalDelta > 0) { - totalParts += *overResult; - } - else if (overResult.error()) - { - // error() will be the TER returned if a payment is not - // made. It will only evaluate to true if it's unsuccessful. - // Otherwise, tesSUCCESS means nothing was done, so - // continue. - return Unexpected(overResult.error()); + XRPL_ASSERT_PARTS( + overpaymentComponents.untrackedInterest >= beast::kZero, + "xrpl::loanMakePayment", + "overpayment penalty did not reduce value of loan"); + // Can't just use `periodicPayment` here, because it might + // change + auto periodicPaymentProxy = loan->at(sfPeriodicPayment); + if (auto const overResult = detail::doOverpayment( + view.rules(), + asset, + loanScale, + overpaymentComponents, + totalValueOutstandingProxy, + principalOutstandingProxy, + managementFeeOutstandingProxy, + periodicPaymentProxy, + periodicRate, + paymentRemainingProxy, + managementFeeRate, + j)) + { + totalParts += *overResult; + } + else if (overResult.error()) + { + // error() will be the TER returned if a payment is not + // made. It will only evaluate to true if it's unsuccessful. + // Otherwise, tesSUCCESS means nothing was done, so + // continue. + return Unexpected(overResult.error()); + } } } } diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index 5e8e89cefa..c380655563 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -169,6 +169,10 @@ protected: TenthBips32 coverRateLiquidation = percentageToTenthBips(25); std::string data = {}; // NOLINT(readability-redundant-member-init) std::uint32_t flags = 0; + // If set, the vault is created with this sfScale value. Useful for + // tests that need finer loanScale to exercise rounding edge cases. + std::optional vaultScale = + std::nullopt; // NOLINT(readability-redundant-member-init) [[nodiscard]] Number maxCoveredLoanValue(Number const& currentDebt) const @@ -522,6 +526,8 @@ protected: auto const coverRateMinValue = params.coverRateMin; auto [tx, vaultKeylet] = vault.create({.owner = lender, .asset = asset}); + if (params.vaultScale) + tx[sfScale] = *params.vaultScale; env(tx); env.close(); BEAST_EXPECT(env.le(vaultKeylet)); @@ -2157,21 +2163,23 @@ protected: // If the loan does not allow overpayments, send a payment that // tries to make an overpayment. Do not include `txFlags`, so we // don't end up duplicating the next test transaction. - env(pay(borrower, - loanKeylet.key, - STAmount{broker.asset, state.periodicPayment * Number{15, -1}}, - tfLoanOverpayment), - Fee(XRPAmount{baseFee * (Number{15, -1} / kLoanPaymentsPerFeeIncrement + 1)}), - Ter(tecNO_PERMISSION)); + // + // fixCleanup3_1_3 gates tfLoanOverpayment as a valid flag: + // with fix on → preflight passes, apply returns tecNO_PERMISSION; + // with fix off → preflight rejects the flag, returns temINVALID_FLAG. + bool const hasFix313 = env.current()->rules().enabled(fixCleanup3_1_3); + STAmount const overpayAmount{broker.asset, state.periodicPayment * Number{15, -1}}; + XRPAmount const overpayFee{ + baseFee * (Number{15, -1} / kLoanPaymentsPerFeeIncrement + 1)}; + env(pay(borrower, loanKeylet.key, overpayAmount, tfLoanOverpayment), + Fee(overpayFee), + Ter(hasFix313 ? TER{tecNO_PERMISSION} : TER{temINVALID_FLAG})); + if (hasFix313) { env.disableFeature(fixCleanup3_1_3); - env(pay(borrower, - loanKeylet.key, - STAmount{broker.asset, state.periodicPayment * Number{15, -1}}, - tfLoanOverpayment), - Fee(XRPAmount{ - baseFee * (Number{15, -1} / kLoanPaymentsPerFeeIncrement + 1)}), + env(pay(borrower, loanKeylet.key, overpayAmount, tfLoanOverpayment), + Fee(overpayFee), Ter(temINVALID_FLAG)); env.enableFeature(fixCleanup3_1_3); } @@ -7027,7 +7035,7 @@ protected: auto credType = "credential1"; - pdomain::Credentials const credentials1{{.issuer = issuer, .credType = credType}}; + pdomain::Credentials const credentials1 = {{.issuer = issuer, .credType = credType}}; env(pdomain::setTx(issuer, credentials1)); env.close(); @@ -7572,6 +7580,74 @@ protected: attemptWithdrawShares(depositorB, sharesLpB, tesSUCCESS); } + // An overpayment whose residual amount has more precision than loanScale + // fires the isRounded(asset, overpayment, loanScale) assertion in + // computeOverpaymentComponents (and a downstream "interest paid agrees" + // assertion in doOverpayment). fixCleanup3_2_0 rounds the residual down + // to loanScale before passing it in. The pre-amendment path can't be + // tested here because the assertion fires in Debug builds and aborts + // the test process — see the PR description for context. + void + testBugOverpayUnroundedAmount() + { + testcase("bug: computeOverpaymentComponents isRounded assertion"); + + using namespace jtx; + using namespace loan; + Env env(*this, all_); + + Account const issuer{"issuer"}; + Account const lender{"vaultOwner"}; + Account const borrower{"borrower"}; + + env.fund(XRP(1'000'000), issuer, lender, borrower); + env(fset(issuer, asfDefaultRipple)); + env.close(); + + PrettyAsset const iouAsset = issuer["USD"]; + STAmount const iouLimit{iouAsset.raw(), Number{9'999'999'999'999'999LL}}; + env(trust(lender, iouLimit)); + env(trust(borrower, iouLimit)); + env(pay(issuer, lender, iouAsset(1'000'000))); + env(pay(issuer, borrower, iouAsset(1'000'000))); + env.close(); + + auto const broker = createVaultAndBroker( + env, + iouAsset, + lender, + {.vaultDeposit = 100'000, + .debtMax = 5000, + .managementFeeRate = TenthBips16{1000}, + .vaultScale = 1}); + + auto const sleBroker = env.le(broker.brokerKeylet()); + if (!BEAST_EXPECT(sleBroker)) + return; + auto const loanSequence = sleBroker->at(sfLoanSequence); + auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence); + + using namespace loan; + env(set(borrower, broker.brokerID, Number{1000}, tfLoanOverpayment), + Sig(sfCounterpartySignature, lender), + kInterestRate(TenthBips32{10000}), + kPaymentTotal(12), + kPaymentInterval(60), + kGracePeriod(60), + kOverpaymentFee(TenthBips32{1000}), + kOverpaymentInterestRate(TenthBips32{1000}), + Fee(env.current()->fees().base * 2), + Ter(tesSUCCESS)); + env.close(); + + // periodic * 1.5 at 15-sig-digit precision: 125.000154585042. This + // has too many digits to round cleanly to loanScale=-10, so the + // overpayment residual fails the isRounded check. + STAmount const payAmount{iouAsset.raw(), Number{125'000'154'585'042LL, -12}}; + env(pay(borrower, loanKeylet.key, payAmount), Txflags(tfLoanOverpayment), Ter(tesSUCCESS)); + env.close(); + } + // Regression for the dual-rounding fix at coarse (integer-MPT) scale. // // Loan: P=1, r=50% (50000 tenth-bips), n=3, yearly interval. The @@ -8280,6 +8356,9 @@ protected: testRIPD3901(); testBorrowerIsBroker(); testLimitExceeded(); + testLoanSetBlockedLoanPayAllowedWhenCanTransferCleared(); + testLendingCanTradeClearedNoImpact(); + testBugOverpayUnroundedAmount(); for (auto const flags : {0u, tfLoanOverpayment}) testYieldTheftRounding(flags); @@ -8295,11 +8374,11 @@ protected: testLoanPayLateFullPaymentBypassesPenalties(features); testLoanCoverMinimumRoundingExploit(features); #endif - // Lifecycle - testSelfLoan(features); - testLoanSet(features); testLifecycle(features); + testLoanSet(features); + testDosLoanPay(features); + testSelfLoan(features); // Payment paths testWithdrawReflectsUnrealizedLoss(features); @@ -8346,11 +8425,8 @@ public: run() override { runAmendmentIndependent(); - testLoanSetBlockedLoanPayAllowedWhenCanTransferCleared(); - testLendingCanTradeClearedNoImpact(); - testDosLoanPay(all_ | fixCleanup3_1_3); - testDosLoanPay(all_ - fixCleanup3_1_3); - for (auto const& features : amendmentCombinations({fixCleanup3_2_0, featureMPTokensV2})) + for (auto const& features : + amendmentCombinations({fixCleanup3_1_3, fixCleanup3_2_0, featureMPTokensV2})) runAmendmentSensitive(features); } }; From 49cb3f45a42d1f050212e1653a663789fb592d2a Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 26 May 2026 16:45:33 +0100 Subject: [PATCH 026/135] ci: Add clang to nix images (#7308) Co-authored-by: semgrep-companion-app[bot] <218312740+semgrep-companion-app[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/build-nix-image.yml | 114 ++++++++++-------- .../workflows/reusable-build-docker-image.yml | 89 ++++++++++++++ cspell.config.yaml | 3 + docker/check-sanitizers.sh | 42 +++++++ docker/cpp_files/asan.cpp | 28 +++++ docker/cpp_files/tsan.cpp | 26 ++++ docker/cpp_files/ubsan.cpp | 13 ++ docker/nix.Dockerfile | 29 +++++ flake.lock | 4 +- flake.nix | 6 +- nix/ci-env.nix | 108 +++++++++++++---- nix/packages.nix | 1 + nix/utils.nix | 8 +- 13 files changed, 387 insertions(+), 84 deletions(-) create mode 100644 .github/workflows/reusable-build-docker-image.yml create mode 100755 docker/check-sanitizers.sh create mode 100644 docker/cpp_files/asan.cpp create mode 100644 docker/cpp_files/tsan.cpp create mode 100644 docker/cpp_files/ubsan.cpp diff --git a/.github/workflows/build-nix-image.yml b/.github/workflows/build-nix-image.yml index 6554cf6c08..a8fb6eec86 100644 --- a/.github/workflows/build-nix-image.yml +++ b/.github/workflows/build-nix-image.yml @@ -6,14 +6,16 @@ on: - develop paths: - ".github/workflows/build-nix-image.yml" - - "docker/nix.Dockerfile" + - ".github/workflows/reusable-build-docker-image.yml" + - "docker/**" - "flake.nix" - "flake.lock" - "nix/**" pull_request: paths: - ".github/workflows/build-nix-image.yml" - - "docker/nix.Dockerfile" + - ".github/workflows/reusable-build-docker-image.yml" + - "docker/**" - "flake.nix" - "flake.lock" - "nix/**" @@ -27,75 +29,81 @@ defaults: run: shell: bash -env: - UBUNTU_VERSION: "20.04" - RHEL_VERSION: "9" - DEBIAN_VERSION: "bookworm" - jobs: build: - name: Build and push Nix image (${{ matrix.distro }}) + name: Build ${{ matrix.distro.name }} (${{ matrix.target.platform }}) + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + # The base images are the oldest supported version of each distro + # that we want to build images for. + distro: + - name: nixos + base_image: nixos/nix:latest + - name: ubuntu + base_image: ubuntu:20.04 + - name: rhel + base_image: registry.access.redhat.com/ubi9/ubi:latest + - name: debian + base_image: debian:bookworm + target: + - platform: linux/amd64 + runner: ubuntu-latest + - platform: linux/arm64 + runner: ubuntu-24.04-arm + uses: ./.github/workflows/reusable-build-docker-image.yml + with: + image_name: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro.name }} + dockerfile: docker/nix.Dockerfile + base_image: ${{ matrix.distro.base_image }} + platform: ${{ matrix.target.platform }} + runner: ${{ matrix.target.runner }} + push: ${{ github.event_name == 'push' }} + + merge: + name: Merge ${{ matrix.distro }} manifest + needs: build + if: github.event_name == 'push' runs-on: ubuntu-latest permissions: contents: read packages: write strategy: + fail-fast: false matrix: - include: - - distro: nixos - - distro: ubuntu - - distro: rhel - - distro: debian + distro: [nixos, ubuntu, rhel, debian] + env: + IMAGE_NAME: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro }} steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Determine base image - id: vars - run: | - case "${{ matrix.distro }}" in - nixos) - echo "base_image=nixos/nix:latest" >> $GITHUB_OUTPUT - ;; - ubuntu) - echo "base_image=ubuntu:${UBUNTU_VERSION}" >> $GITHUB_OUTPUT - ;; - rhel) - echo "base_image=registry.access.redhat.com/ubi${RHEL_VERSION}/ubi:latest" >> $GITHUB_OUTPUT - ;; - debian) - echo "base_image=debian:${DEBIAN_VERSION}" >> $GITHUB_OUTPUT - ;; - esac - - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + - name: Docker metadata + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + with: + images: ${{ env.IMAGE_NAME }} + tags: | + type=sha,prefix=sha-,format=short + type=raw,value=latest + - name: Login to GitHub Container Registry - if: github.event_name == 'push' uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Docker metadata - id: meta - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 - with: - images: ghcr.io/xrplf/ci/nix-${{ matrix.distro }} - tags: | - type=sha,prefix=sha-,format=short - type=raw,value=latest + - name: Create multi-arch manifests + run: | + for tag in $(jq -cr '.tags[]' <<< "$DOCKER_METADATA_OUTPUT_JSON"); do + docker buildx imagetools create -t "$tag" "${tag}-amd64" "${tag}-arm64" + done - - name: Build and push - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 - with: - context: . - file: docker/nix.Dockerfile - platforms: linux/amd64 - push: ${{ github.event_name == 'push' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: BASE_IMAGE=${{ steps.vars.outputs.base_image }} + - name: Inspect image + run: | + docker buildx imagetools inspect "${IMAGE_NAME}:${{ steps.meta.outputs.version }}" diff --git a/.github/workflows/reusable-build-docker-image.yml b/.github/workflows/reusable-build-docker-image.yml new file mode 100644 index 0000000000..e631e02368 --- /dev/null +++ b/.github/workflows/reusable-build-docker-image.yml @@ -0,0 +1,89 @@ +# Build a single-platform Docker image. On push, the image is pushed to +# GHCR with arch-suffixed tags (e.g. `:latest-amd64`, `:sha-abc-amd64`) +# so the calling workflow can stitch per-arch builds into a multi-arch +# manifest without needing to pass digests around. +name: Reusable build Docker image (single platform) + +on: + workflow_call: + inputs: + image_name: + description: "Full image name without tag (e.g. 'ghcr.io/xrplf/xrpld/nix-ubuntu')" + required: true + type: string + dockerfile: + description: "Path to the Dockerfile, relative to the repository root" + required: true + type: string + base_image: + description: "Value passed to the Dockerfile as the BASE_IMAGE build arg" + required: true + type: string + platform: + description: "Docker platform string, e.g. linux/amd64" + required: true + type: string + runner: + description: "GitHub Actions runner label to build on" + required: true + type: string + push: + description: "Whether to push the image to GHCR" + required: true + type: boolean + +defaults: + run: + shell: bash + +jobs: + build: + name: Build (${{ inputs.platform }}) + runs-on: ${{ inputs.runner }} + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Determine arch + id: vars + env: + PLATFORM: ${{ inputs.platform }} + run: | + echo "arch=${PLATFORM##*/}" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Login to GitHub Container Registry + if: inputs.push + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker metadata + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + with: + images: ${{ inputs.image_name }} + tags: | + type=sha,prefix=sha-,format=short + type=raw,value=latest + flavor: | + suffix=-${{ steps.vars.outputs.arch }},onlatest=true + + - name: Build and push + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + with: + context: . + file: ${{ inputs.dockerfile }} + platforms: ${{ inputs.platform }} + push: ${{ inputs.push }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: BASE_IMAGE=${{ inputs.base_image }} diff --git a/cspell.config.yaml b/cspell.config.yaml index 275df41f58..82a6a20158 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -199,11 +199,13 @@ words: - nonxrp - noreplace - noripple + - nostdinc - notifempty - nudb - nullptr - nunl - Nyffenegger + - onlatest - ostr - pargs - partitioner @@ -298,6 +300,7 @@ words: - unauthorizing - unergonomic - unfetched + - unfindable - unflatten - unfund - unimpair diff --git a/docker/check-sanitizers.sh b/docker/check-sanitizers.sh new file mode 100755 index 0000000000..db51f11851 --- /dev/null +++ b/docker/check-sanitizers.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# Sanity-check that the sanitizer runtimes shipped with g++/clang++ work +# end-to-end against the system loader: compile each example with both +# compilers, run it, and confirm the expected diagnostic is emitted. + +set -eo pipefail + +cpp_files_dir="${1:?usage: $0 }" + +case "$(uname -m)" in + x86_64) loader=/lib64/ld-linux-x86-64.so.2 ;; + aarch64) loader=/lib/ld-linux-aarch64.so.1 ;; + *) echo "Unsupported arch: $(uname -m)" >&2; exit 1 ;; +esac + +declare -A sanitize=( + [asan]="-fsanitize=address" + [tsan]="-fsanitize=thread" + [ubsan]="-fsanitize=undefined" +) +declare -A expect=( + [asan]="heap-use-after-free" + [tsan]="data race" + [ubsan]="signed integer overflow" +) + +for compiler in g++ clang++; do + for name in asan tsan ubsan; do + bin="/tmp/${name}-${compiler}" + echo "=== Build ${name} with ${compiler} ===" + "$compiler" -std=c++20 -O1 -g ${sanitize[$name]} \ + -Wl,--dynamic-linker=$loader \ + "${cpp_files_dir}/${name}.cpp" -o "$bin" + echo "=== Run ${name}-${compiler} ===" + output=$("$bin" 2>&1) || true + echo "$output" + echo "$output" | grep -q "${expect[$name]}" \ + || { echo "expected '${expect[$name]}' from $bin"; exit 1; } + rm -f "$bin" + done +done diff --git a/docker/cpp_files/asan.cpp b/docker/cpp_files/asan.cpp new file mode 100644 index 0000000000..8347f58d37 --- /dev/null +++ b/docker/cpp_files/asan.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +#if defined(__clang__) || defined(__GNUC__) +__attribute__((noinline)) +#elif defined(_MSC_VER) +__declspec(noinline) +#endif +int +read_after_free(volatile int* array, std::size_t index) +{ + std::atomic_signal_fence(std::memory_order_seq_cst); + int value = array[index]; + std::atomic_signal_fence(std::memory_order_seq_cst); + return value; +} + +int +main() +{ + int* array = new int[5]{10, 20, 30, 40, 50}; + delete[] array; + + std::cout << "Value at index 2: " << read_after_free(array, 2) << std::endl; + + return 0; +} diff --git a/docker/cpp_files/tsan.cpp b/docker/cpp_files/tsan.cpp new file mode 100644 index 0000000000..34b0990a6d --- /dev/null +++ b/docker/cpp_files/tsan.cpp @@ -0,0 +1,26 @@ +#include +#include + +static int kCounter = 0; + +void +increment() +{ + for (int i = 0; i < 100'000; ++i) + { + ++kCounter; + } +} + +int +main() +{ + std::thread t1(increment); + std::thread t2(increment); + + t1.join(); + t2.join(); + + std::cout << "Final counter value: " << kCounter << std::endl; + return 0; +} diff --git a/docker/cpp_files/ubsan.cpp b/docker/cpp_files/ubsan.cpp new file mode 100644 index 0000000000..db86119070 --- /dev/null +++ b/docker/cpp_files/ubsan.cpp @@ -0,0 +1,13 @@ +#include +#include + +int +main() +{ + int maxInt = std::numeric_limits::max(); + int volatile one = 1; + std::cout << "Current max: " << maxInt << std::endl; + int overflowed = maxInt + one; + std::cout << "Overflowed result: " << overflowed << std::endl; + return 0; +} diff --git a/docker/nix.Dockerfile b/docker/nix.Dockerfile index 52faa8b8dc..690f0b76bd 100644 --- a/docker/nix.Dockerfile +++ b/docker/nix.Dockerfile @@ -45,8 +45,30 @@ COPY --from=builder /tmp/build/result /nix/ci-env ENV PATH="/nix/ci-env/bin:$PATH" +# Externally-built dynamically-linked ELF binaries hard-code the loader path +# (e.g. /lib64/ld-linux-x86-64.so.2) in their PT_INTERP header. Copy the +# loader from the Nix store to that path when the base image doesn't already +# provide one (i.e. on nixos/nix). +RUN <&2; exit 1 ;; +esac +if [ ! -e "$target" ]; then + # Use the loader from the same glibc that gcc links libc against, so + # ld-linux and libc/libpthread share GLIBC_PRIVATE symbols at runtime. + src="$(dirname "$(gcc -print-file-name=libc.so.6)")/$(basename "$target")" + [ -e "$src" ] || { echo "ld-linux not found at $src" >&2; exit 1; } + mkdir -p "$(dirname "$target")" + cp "$src" "$target" +fi +EOF + RUN </dev/null && /tmp/check-sanitizers.sh /tmp/cpp_files || true diff --git a/flake.lock b/flake.lock index 5ec053975d..3149f3feed 100644 --- a/flake.lock +++ b/flake.lock @@ -15,7 +15,7 @@ "type": "indirect" } }, - "nixpkgs-glibc231": { + "nixpkgs-custom-glibc": { "flake": false, "locked": { "lastModified": 1593520194, @@ -35,7 +35,7 @@ "root": { "inputs": { "nixpkgs": "nixpkgs", - "nixpkgs-glibc231": "nixpkgs-glibc231" + "nixpkgs-custom-glibc": "nixpkgs-custom-glibc" } } }, diff --git a/flake.nix b/flake.nix index 18671bdf31..3b3ec7ea08 100644 --- a/flake.nix +++ b/flake.nix @@ -6,16 +6,16 @@ # version — matches the system libc on Ubuntu 20.04 LTS. Imported # manually (flake = false) because this revision predates nixpkgs' # own flake.nix. - nixpkgs-glibc231 = { + nixpkgs-custom-glibc = { url = "github:NixOS/nixpkgs/9cd98386a38891d1074fc18036b842dc4416f562"; flake = false; }; }; outputs = - { nixpkgs, nixpkgs-glibc231, ... }: + { nixpkgs, nixpkgs-custom-glibc, ... }: let - forEachSystem = import ./nix/utils.nix { inherit nixpkgs nixpkgs-glibc231; }; + forEachSystem = import ./nix/utils.nix { inherit nixpkgs nixpkgs-custom-glibc; }; in { devShells = forEachSystem (import ./nix/devshell.nix); diff --git a/nix/ci-env.nix b/nix/ci-env.nix index d8021fe0bd..0d617913d9 100644 --- a/nix/ci-env.nix +++ b/nix/ci-env.nix @@ -1,39 +1,102 @@ { pkgs, - glibc231, + customGlibc, ... }: let inherit (import ./packages.nix { inherit pkgs; }) commonPackages; + inherit (pkgs) lib; - # binutils wrapped to emit binaries that reference glibc 2.31 (dynamic - # linker path, library search path, RPATH). - binutils231 = pkgs.wrapBintoolsWith { + # Underlying compiler toolchains to wrap. Bump these in one place to + # roll the whole environment forward. + customGccPackage = pkgs.gcc15; + customLlvmPackages = pkgs.llvmPackages_22; + customClangMajor = lib.versions.major (lib.getVersion customLlvmPackages.clang-unwrapped); + + # binutils wrapped to emit binaries that reference the custom glibc + # (dynamic linker path, library search path, RPATH). + customBinutils = pkgs.wrapBintoolsWith { bintools = pkgs.binutils-unwrapped; - libc = glibc231; + libc = customGlibc; }; - # Rebuild gcc 15 (specifically libstdc++ / libgcc_s) against glibc 2.31. - # The override swaps gcc15.cc's bootstrap stdenv for one that uses the - # existing gcc 15 binary but links against glibc 2.31, so the resulting - # compiler ships runtime libraries that only reference symbols available - # in glibc 2.31. - gcc15CcWithGlibc231 = pkgs.gcc15.cc.override { + # Rebuild gcc (specifically libstdc++ / libgcc_s) against the custom + # glibc. The override swaps gcc.cc's bootstrap stdenv for one that uses + # the existing gcc binary but links against the custom glibc, so the + # resulting compiler ships runtime libraries that only reference symbols + # available in that glibc. + customGccCc = customGccPackage.cc.override { stdenv = pkgs.stdenvAdapters.overrideCC pkgs.stdenv ( pkgs.wrapCCWith { - cc = pkgs.gcc15.cc; - libc = glibc231; - bintools = binutils231; + cc = customGccPackage.cc; + libc = customGlibc; + bintools = customBinutils; } ); }; - # cc-wrapper around the rebuilt compiler, pointing at glibc 2.31 headers - # and libraries. This is what we actually expose to users. - gcc15WithGlibc231 = pkgs.wrapCCWith { - cc = gcc15CcWithGlibc231; - libc = glibc231; - bintools = binutils231; + # cc-wrapper around the rebuilt compiler, pointing at the custom glibc + # headers and libraries. This is what we actually expose to users. + customGcc = pkgs.wrapCCWith { + cc = customGccCc; + libc = customGlibc; + bintools = customBinutils; + }; + + # stdenv built around the rebuilt gcc / custom glibc. Used to rebuild + # compiler-rt below so its sanitizer runtimes see the custom glibc + # headers. + customStdenv = pkgs.stdenvAdapters.overrideCC pkgs.stdenv customGcc; + + # Rebuild compiler-rt against the custom glibc so the sanitizer runtimes + # don't use glibc symbols (or sysconf constants like _SC_SIGSTKSZ) that + # only exist in newer glibc versions. scudo is dropped because its CMake + # includes CheckAtomic with -nostdinc++ in CMAKE_REQUIRED_FLAGS, which + # makes std::atomic unfindable in our stdenv; we don't use scudo (only + # asan/ubsan/tsan etc.). + customCompilerRt = + (customLlvmPackages.compiler-rt.override { + stdenv = customStdenv; + }).overrideAttrs + (old: { + postPatch = (old.postPatch or "") + '' + substituteInPlace lib/CMakeLists.txt \ + --replace-quiet 'add_subdirectory(scudo/standalone)' \ + '# scudo/standalone disabled in xrpld ci-env' + ''; + }); + + # cc-wrapper around clang, pointing at the custom glibc headers and + # libraries. Reuses the rebuilt gcc for libstdc++ / libgcc_s so that + # C++ binaries produced by clang also only reference symbols available + # in the custom glibc. compiler-rt is wired into a resource-root so + # sanitizer runtimes (libclang_rt.*.a) are found at link time; this + # mirrors what nixpkgs does internally when building llvmPackages.clang. + customClang = pkgs.wrapCCWith { + cc = customLlvmPackages.clang-unwrapped; + libc = customGlibc; + bintools = customBinutils; + gccForLibs = customGccCc; + extraPackages = [ customCompilerRt ]; + extraBuildCommands = '' + rsrc="$out/resource-root" + mkdir "$rsrc" + ln -s "${customLlvmPackages.clang-unwrapped.lib}/lib/clang/${customClangMajor}/include" "$rsrc/include" + ln -s "${customCompilerRt.out}/lib" "$rsrc/lib" + ln -s "${customCompilerRt.out}/share" "$rsrc/share" || true + echo "-resource-dir=$rsrc" >> $out/nix-support/cc-cflags + ''; + }; + + # Strip the generic cc/c++/cpp symlinks from the clang wrapper so it can + # coexist with the gcc wrapper in buildEnv. gcc remains the default + # compiler (cc/c++/cpp); clang is invoked explicitly as clang/clang++. + customClangForCiEnv = pkgs.symlinkJoin { + name = "clang-wrapper-custom-for-ci-env"; + paths = [ customClang ]; + postBuild = '' + rm -f $out/bin/cc $out/bin/c++ $out/bin/cpp + ''; }; in @@ -41,8 +104,9 @@ in default = pkgs.buildEnv { name = "xrpld-ci-env"; paths = commonPackages ++ [ - gcc15WithGlibc231 - binutils231 + customGcc + customClangForCiEnv + customBinutils ]; pathsToLink = [ "/bin" diff --git a/nix/packages.nix b/nix/packages.nix index edfe302ec9..d209620a68 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -17,6 +17,7 @@ in llvmPackages_22.clang-tools mold ninja + patchelf perl # needed for openssl pkg-config pre-commit diff --git a/nix/utils.nix b/nix/utils.nix index 07ff169c44..d83e612c16 100644 --- a/nix/utils.nix +++ b/nix/utils.nix @@ -1,4 +1,4 @@ -{ nixpkgs, nixpkgs-glibc231 }: +{ nixpkgs, nixpkgs-custom-glibc }: function: nixpkgs.lib.genAttrs [ @@ -12,10 +12,10 @@ nixpkgs.lib.genAttrs function { pkgs = import nixpkgs { inherit system; }; # glibc 2.31 — matches the system libc on Ubuntu 20.04 LTS. Sourced - # from the nixpkgs snapshot pinned via the `nixpkgs-glibc231` flake - # input, so the build uses the compiler from that snapshot + # from the nixpkgs snapshot pinned via the `nixpkgs-custom-glibc` + # flake input, so the build uses the compiler from that snapshot # (gcc 9.3.0) along with the matching patches, configure flags, and # hardening defaults. - glibc231 = (import nixpkgs-glibc231 { inherit system; }).glibc; + customGlibc = (import nixpkgs-custom-glibc { inherit system; }).glibc; } ) From 633ef4706ffdd0bed445032d2de92e86e59985d6 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Tue, 26 May 2026 18:32:44 +0200 Subject: [PATCH 027/135] fix: Fix `VaultInvariant` and `VaultDeposit` precision bugs at IOU scale boundaries (#7272) Co-authored-by: Bart --- include/xrpl/protocol/STAmount.h | 1 - include/xrpl/tx/invariants/VaultInvariant.h | 77 ++- src/libxrpl/tx/invariants/VaultInvariant.cpp | 345 ++++++------ .../tx/transactors/vault/VaultDeposit.cpp | 87 +++- src/test/app/Vault_test.cpp | 489 ++++++++++++++++++ src/test/protocol/STAmount_test.cpp | 2 - 6 files changed, 820 insertions(+), 181 deletions(-) diff --git a/include/xrpl/protocol/STAmount.h b/include/xrpl/protocol/STAmount.h index bf3e25eedb..a4fffad40c 100644 --- a/include/xrpl/protocol/STAmount.h +++ b/include/xrpl/protocol/STAmount.h @@ -189,7 +189,6 @@ public: /** * Checks if this amount evaluates to zero when constrained to a specific * accounting scale. - * * For XRP and MPT `roundToScale` is a no-op, returns true only when the amount itself is zero. * The `scale` argument is ignored in that case. * For IOU, the amount is rounded to the given scale using Number::RoundingMode::ToNearest mode diff --git a/include/xrpl/tx/invariants/VaultInvariant.h b/include/xrpl/tx/invariants/VaultInvariant.h index ab55cd086a..abc256c880 100644 --- a/include/xrpl/tx/invariants/VaultInvariant.h +++ b/include/xrpl/tx/invariants/VaultInvariant.h @@ -4,9 +4,11 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -79,16 +81,83 @@ private: std::vector beforeMPTs_; std::unordered_map deltas_; + /** + * @brief Compute the minimum STAmount scale for rounding invariant + * calculations. + * + * Post-amendment (@c fixCleanup3_2_0) this is simply the posterior + * @c assetsTotal scale. Pre-amendment it is the coarsest scale across + * @p vaultDelta and both asset-field deltas. + * + * @param vaultDelta Delta of the vault's asset balance for this transaction. + * @param rules Active ledger rules (used to check the amendment). + * @returns The minimum scale to apply when rounding vault-related amounts. + */ + [[nodiscard]] std::int32_t + computeVaultMinScale(DeltaInfo const& vaultDelta, Rules const& rules) const; + + /** + * @brief Return the vault-asset balance-change delta for an account. + * + * Looks up the ledger-entry delta recorded during @c visitEntry for the + * account entry (XRP), trust line (IOU), or MPToken (MPT) that corresponds + * to the vault asset held by @p id. + * + * @param id Account whose asset delta is requested. + * @returns The delta, or @c std::nullopt if the entry was not touched. + */ + [[nodiscard]] std::optional + deltaAssets(AccountID const& id) const; + + /** + * @brief Return the vault-asset delta for the transaction's sending + * account, adjusted for the fee. + * + * Calls @c deltaAssets for @c tx[sfAccount] and, for non-delegated XRP + * transactions, adds the consumed fee back so the invariant sees the net + * asset movement rather than the fee-reduced balance change. + * + * @param tx The transaction being applied. + * @param fee Fee charged by this transaction. + * @returns The fee-adjusted delta, or @c std::nullopt if the net delta is + * zero or the account entry was not touched. + */ + [[nodiscard]] std::optional + deltaAssetsTxAccount(STTx const& tx, XRPAmount fee) const; + + /** + * @brief Return the vault-share balance-change delta for an account. + * + * For the vault's pseudo-account the @c MPTokenIssuance outstanding-amount + * delta is returned; for all other accounts the @c MPToken delta is + * returned. + * + * @param id Account whose share delta is requested. + * @returns The delta, or @c std::nullopt if the entry was not touched. + */ + [[nodiscard]] std::optional + deltaShares(AccountID const& id) const; + + /** + * @brief Check whether a vault holds no assets. + * + * @param vault Snapshot of the vault to test. + * @returns @c true when both @c assetsAvailable and @c assetsTotal are + * zero. + */ + [[nodiscard]] static bool + isVaultEmpty(Vault const& vault); + public: + // Compute the coarsest scale required to represent all numbers + [[nodiscard]] static std::int32_t + computeCoarsestScale(std::vector const& numbers); + void visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); - - // Compute the coarsest scale required to represent all numbers - [[nodiscard]] static std::int32_t - computeCoarsestScale(std::vector const& numbers); }; } // namespace xrpl diff --git a/src/libxrpl/tx/invariants/VaultInvariant.cpp b/src/libxrpl/tx/invariants/VaultInvariant.cpp index 2947be3bd4..4aa79279a1 100644 --- a/src/libxrpl/tx/invariants/VaultInvariant.cpp +++ b/src/libxrpl/tx/invariants/VaultInvariant.cpp @@ -186,6 +186,101 @@ ValidVault::visitEntry( } } +std::optional +ValidVault::deltaAssets(AccountID const& id) const +{ + auto const& vaultAsset = afterVault_[0].asset; + auto const lookup = [&](uint256 const& key) -> std::optional { + auto const it = deltas_.find(key); + if (it == deltas_.end()) + return std::nullopt; + return it->second; + }; + + return std::visit( + [&](TIss const& issue) -> std::optional { + if constexpr (std::is_same_v) + { + if (isXRP(issue)) + return lookup(keylet::account(id).key); + auto result = lookup(keylet::line(id, issue).key); + // Trust-line balance is stored from the low-account's perspective; + // negate if id is the high account so the delta is in id's terms. + if (result && id > issue.getIssuer()) + result->delta = -result->delta; + return result; + } + else if constexpr (std::is_same_v) + { + return lookup(keylet::mptoken(issue.getMptID(), id).key); + } + }, + vaultAsset.value()); +} + +std::optional +ValidVault::deltaAssetsTxAccount(STTx const& tx, XRPAmount fee) const +{ + auto const& vaultAsset = afterVault_[0].asset; + auto ret = deltaAssets(tx[sfAccount]); + if (!ret.has_value() || !vaultAsset.native()) + return ret; + + if (auto const delegate = tx[~sfDelegate]; delegate.has_value() && *delegate != tx[sfAccount]) + return ret; + + ret->delta += fee.drops(); + if (ret->delta == kZero) + return std::nullopt; + + return ret; +} + +std::optional +ValidVault::deltaShares(AccountID const& id) const +{ + auto const& afterVault = afterVault_[0]; + auto const it = [&]() { + if (id == afterVault.pseudoId) + return deltas_.find(keylet::mptIssuance(afterVault.shareMPTID).key); + return deltas_.find(keylet::mptoken(afterVault.shareMPTID, id).key); + }(); + + return it != deltas_.end() ? std::optional(it->second) : std::nullopt; +} + +bool +ValidVault::isVaultEmpty(Vault const& vault) +{ + return vault.assetsAvailable == 0 && vault.assetsTotal == 0; +} + +std::int32_t +ValidVault::computeVaultMinScale(DeltaInfo const& vaultDelta, Rules const& rules) const +{ + // Returns the posterior `assetsTotal` scale. + // + // 1. Because STAmounts are normalized, `assetsTotal` (being >= `assetsAvailable`) + // safely represents the coarsest exponent needed for both fields. + // + // 2. The scale may decrease (withdraw/clawback) or increase (deposit). In both cases + // we ensure the vault is in a legitimate state in the post-transaction scale. + auto const& afterVault = afterVault_[0]; + auto const& vaultAsset = afterVault.asset; + if (rules.enabled(fixCleanup3_2_0)) + { + NumberRoundModeGuard const roundGuard(Number::RoundingMode::ToNearest); + return scale(afterVault.assetsTotal, vaultAsset); + } + + auto const& beforeVault = beforeVault_[0]; + auto const totalDelta = + DeltaInfo::makeDelta(beforeVault.assetsTotal, afterVault.assetsTotal, vaultAsset); + auto const availableDelta = + DeltaInfo::makeDelta(beforeVault.assetsAvailable, afterVault.assetsAvailable, vaultAsset); + return computeCoarsestScale({vaultDelta, totalDelta, availableDelta}); +} + bool ValidVault::finalize( STTx const& tx, @@ -445,61 +540,6 @@ ValidVault::finalize( } auto const& vaultAsset = afterVault.asset; - auto const deltaAssets = [&](AccountID const& id) -> std::optional { - auto const get = // - [&](auto const& it, std::int8_t sign = 1) -> std::optional { - if (it == deltas_.end()) - return std::nullopt; - - return DeltaInfo{it->second.delta * sign, it->second.scale}; - }; - - return std::visit( - [&](TIss const& issue) { - if constexpr (std::is_same_v) - { - if (isXRP(issue)) - return get(deltas_.find(keylet::account(id).key)); - return get( - deltas_.find(keylet::line(id, issue).key), id > issue.getIssuer() ? -1 : 1); - } - else if constexpr (std::is_same_v) - { - return get(deltas_.find(keylet::mptoken(issue.getMptID(), id).key)); - } - }, - vaultAsset.value()); - }; - auto const deltaAssetsTxAccount = [&]() -> std::optional { - auto ret = deltaAssets(tx[sfAccount]); - // Nothing returned or not XRP transaction - if (!ret.has_value() || !vaultAsset.native()) - return ret; - - // Delegated transaction; no need to compensate for fees - if (auto const delegate = tx[~sfDelegate]; - delegate.has_value() && *delegate != tx[sfAccount]) - return ret; - - ret->delta += fee.drops(); - if (ret->delta == kZero) - return std::nullopt; - - return ret; - }; - auto const deltaShares = [&](AccountID const& id) -> std::optional { - auto const it = [&]() { - if (id == afterVault.pseudoId) - return deltas_.find(keylet::mptIssuance(afterVault.shareMPTID).key); - return deltas_.find(keylet::mptoken(afterVault.shareMPTID, id).key); - }(); - - return it != deltas_.end() ? std::optional(it->second) : std::nullopt; - }; - - auto const vaultHoldsNoAssets = [&](Vault const& vault) { - return vault.assetsAvailable == 0 && vault.assetsTotal == 0; - }; // Technically this does not need to be a lambda, but it's more // convenient thanks to early "return false"; the not-so-nice @@ -629,16 +669,8 @@ ValidVault::finalize( return false; // That's all we can do } - // Get the coarsest scale to round calculations to - auto const totalDelta = DeltaInfo::makeDelta( - beforeVault.assetsTotal, afterVault.assetsTotal, vaultAsset); - auto const availableDelta = DeltaInfo::makeDelta( - beforeVault.assetsAvailable, afterVault.assetsAvailable, vaultAsset); - auto const minScale = computeCoarsestScale({ - *maybeVaultDeltaAssets, - totalDelta, - availableDelta, - }); + // Get the posterior scale to round calculations to + auto const minScale = computeVaultMinScale(*maybeVaultDeltaAssets, view.rules()); auto const vaultDeltaAssets = roundToAsset(vaultAsset, maybeVaultDeltaAssets->delta, minScale); @@ -669,12 +701,11 @@ ValidVault::finalize( if (!issuerDeposit) { - auto const maybeAccDeltaAssets = deltaAssetsTxAccount(); + auto const maybeAccDeltaAssets = deltaAssetsTxAccount(tx, fee); if (!maybeAccDeltaAssets) { - JLOG(j.fatal()) << // - "Invariant failed: deposit must change depositor " - "balance"; + JLOG(j.fatal()) + << "Invariant failed: deposit must change depositor balance"; return false; } auto const localMinScale = @@ -685,19 +716,20 @@ ValidVault::finalize( auto const localVaultDeltaAssets = roundToAsset(vaultAsset, vaultDeltaAssets, localMinScale); + // For IOUs, if the deposit amount is not-representable at depositor trustline + // scale deposit amount could round to zero, giving depositor shares for no + // assets. Unlike withdrawal, we do not allow that. if (accountDeltaAssets >= kZero) { - JLOG(j.fatal()) << // - "Invariant failed: deposit must decrease depositor " - "balance"; + JLOG(j.fatal()) + << "Invariant failed: deposit must decrease depositor balance"; result = false; } if (localVaultDeltaAssets * -1 != accountDeltaAssets) { - JLOG(j.fatal()) << // - "Invariant failed: deposit must change vault and " - "depositor balance by equal amount"; + JLOG(j.fatal()) << "Invariant failed: " << // + "deposit must change vault and depositor balance by equal amount"; result = false; } } @@ -705,45 +737,38 @@ ValidVault::finalize( if (afterVault.assetsMaximum > kZero && afterVault.assetsTotal > afterVault.assetsMaximum) { - JLOG(j.fatal()) << // - "Invariant failed: deposit assets outstanding must not " - "exceed assets maximum"; + JLOG(j.fatal()) << "Invariant failed: " << // + "deposit assets outstanding must not exceed assets maximum"; result = false; } auto const maybeAccDeltaShares = deltaShares(tx[sfAccount]); if (!maybeAccDeltaShares) { - JLOG(j.fatal()) << // - "Invariant failed: deposit must change depositor " - "shares"; + JLOG(j.fatal()) << "Invariant failed: deposit must change depositor shares"; return false; // That's all we can do } - // We don't need to round shares, they are integral MPT + // We don't round shares, they are integral MPT auto const& accountDeltaShares = *maybeAccDeltaShares; if (accountDeltaShares.delta <= kZero) { - JLOG(j.fatal()) << // - "Invariant failed: deposit must increase depositor " - "shares"; + JLOG(j.fatal()) << "Invariant failed: deposit must increase depositor shares"; result = false; } auto const maybeVaultDeltaShares = deltaShares(afterVault.pseudoId); if (!maybeVaultDeltaShares || maybeVaultDeltaShares->delta == kZero) { - JLOG(j.fatal()) << // - "Invariant failed: deposit must change vault shares"; + JLOG(j.fatal()) << "Invariant failed: deposit must change vault shares"; return false; // That's all we can do } - // We don't need to round shares, they are integral MPT + // We don't round shares, they are integral MPT auto const& vaultDeltaShares = *maybeVaultDeltaShares; if (vaultDeltaShares.delta * -1 != accountDeltaShares.delta) { - JLOG(j.fatal()) << // - "Invariant failed: deposit must change depositor and " - "vault shares by equal amount"; + JLOG(j.fatal()) << "Invariant failed: " << // + "deposit must change depositor and vault shares by equal amount"; result = false; } @@ -751,8 +776,8 @@ ValidVault::finalize( vaultAsset, afterVault.assetsTotal - beforeVault.assetsTotal, minScale); if (assetTotalDelta != vaultDeltaAssets) { - JLOG(j.fatal()) << "Invariant failed: deposit and assets " - "outstanding must add up"; + JLOG(j.fatal()) + << "Invariant failed: deposit and assets outstanding must add up"; result = false; } @@ -760,8 +785,7 @@ ValidVault::finalize( vaultAsset, afterVault.assetsAvailable - beforeVault.assetsAvailable, minScale); if (assetAvailableDelta != vaultDeltaAssets) { - JLOG(j.fatal()) << "Invariant failed: deposit and assets " - "available must add up"; + JLOG(j.fatal()) << "Invariant failed: deposit and assets available must add up"; result = false; } @@ -772,34 +796,25 @@ ValidVault::finalize( XRPL_ASSERT( !beforeVault_.empty(), - "xrpl::ValidVault::finalize : withdrawal updated a " - "vault"); + "xrpl::ValidVault::finalize : withdrawal updated a vault"); auto const& beforeVault = beforeVault_[0]; auto const maybeVaultDeltaAssets = deltaAssets(afterVault.pseudoId); - if (!maybeVaultDeltaAssets) { - JLOG(j.fatal()) << "Invariant failed: withdrawal must " - "change vault balance"; + JLOG(j.fatal()) << "Invariant failed: withdrawal must change vault balance"; return false; // That's all we can do } - // Get the most coarse scale to round calculations to - auto const totalDelta = DeltaInfo::makeDelta( - beforeVault.assetsTotal, afterVault.assetsTotal, vaultAsset); - auto const availableDelta = DeltaInfo::makeDelta( - beforeVault.assetsAvailable, afterVault.assetsAvailable, vaultAsset); - auto const minScale = - computeCoarsestScale({*maybeVaultDeltaAssets, totalDelta, availableDelta}); + // Get the posterior scale to round calculations to + auto const minScale = computeVaultMinScale(*maybeVaultDeltaAssets, view.rules()); auto const vaultPseudoDeltaAssets = roundToAsset(vaultAsset, maybeVaultDeltaAssets->delta, minScale); if (vaultPseudoDeltaAssets >= kZero) { - JLOG(j.fatal()) << "Invariant failed: withdrawal must " - "decrease vault balance"; + JLOG(j.fatal()) << "Invariant failed: withdrawal must decrease vault balance"; result = false; } @@ -814,7 +829,7 @@ ValidVault::finalize( if (!issuerWithdrawal) { - auto const maybeAccDelta = deltaAssetsTxAccount(); + auto const maybeAccDelta = deltaAssetsTxAccount(tx, fee); auto const maybeOtherAccDelta = [&]() -> std::optional { if (auto const destination = tx[~sfDestination]; destination && *destination != tx[sfAccount]) @@ -825,8 +840,7 @@ ValidVault::finalize( if (maybeAccDelta.has_value() == maybeOtherAccDelta.has_value()) { JLOG(j.fatal()) << // - "Invariant failed: withdrawal must change one " - "destination balance"; + "Invariant failed: withdrawal must change one destination balance"; return false; } @@ -835,63 +849,83 @@ ValidVault::finalize( // the scale of destinationDelta can be coarser than // minScale, so we take that into account when rounding - auto const localMinScale = - std::max(minScale, computeCoarsestScale({destinationDelta})); + auto const destinationScale = computeCoarsestScale({destinationDelta}); + auto const localMinScale = std::max(minScale, destinationScale); auto const roundedDestinationDelta = roundToAsset(vaultAsset, destinationDelta.delta, localMinScale); - if (roundedDestinationDelta <= kZero) + // Post-fixCleanup3_2_0: Tolerate zero-rounded destination deltas for IOUs only. + // If the receiver's trust line sits at a coarser scale, the inflow may + // safely round down to zero. + // + // XRP and MPT remain strict. Because they are integer-exact, a zero + // destination delta indicates a true accounting bug, not a rounding artifact. + bool const tolerateZeroDelta = + view.rules().enabled(fixCleanup3_2_0) && !vaultAsset.integral(); + auto const invalidBalanceChange = tolerateZeroDelta + ? roundedDestinationDelta < kZero + : roundedDestinationDelta <= kZero; + if (invalidBalanceChange) { JLOG(j.fatal()) << // - "Invariant failed: withdrawal must increase " - "destination balance"; + "Invariant failed: withdrawal must increase destination balance"; result = false; } auto const localPseudoDeltaAssets = roundToAsset(vaultAsset, vaultPseudoDeltaAssets, localMinScale); - if (localPseudoDeltaAssets * -1 != roundedDestinationDelta) + // For IOU assets near a precision boundary the destination's STAmount + // exponent can shift, making part of the sent value unrepresentable at the + // receiver's new scale — that portion is irreversibly absorbed by the IOU + // rail. Tolerate the mismatch only when the destroyed amount (vault outflow + // minus destination inflow, in Number space) is itself sub-ULP at the + // destination's scale. Floor rounding is used so that values exactly at the + // step boundary are not mistakenly dismissed. Any representable discrepancy + // indicates a real accounting bug and must be caught. + auto const destroyedIsSubUlp = tolerateZeroDelta && + roundToAsset( + vaultAsset, + maybeVaultDeltaAssets->delta * -1 - destinationDelta.delta, + destinationScale, + Number::RoundingMode::Downward) == kZero; + if (!destroyedIsSubUlp && + localPseudoDeltaAssets * -1 != roundedDestinationDelta) { - JLOG(j.fatal()) << // - "Invariant failed: withdrawal must change vault " - "and destination balance by equal amount"; + JLOG(j.fatal()) << "Invariant failed: " << // + "withdrawal must change vault and destination balance by equal " + "amount"; result = false; } } - // We don't need to round shares, they are integral MPT + // We don't round shares, they are integral MPT auto const accountDeltaShares = deltaShares(tx[sfAccount]); if (!accountDeltaShares) { - JLOG(j.fatal()) << // - "Invariant failed: withdrawal must change depositor " - "shares"; + JLOG(j.fatal()) << "Invariant failed: withdrawal must change depositor shares"; return false; } if (accountDeltaShares->delta >= kZero) { - JLOG(j.fatal()) << // - "Invariant failed: withdrawal must decrease depositor " - "shares"; + JLOG(j.fatal()) + << "Invariant failed: withdrawal must decrease depositor shares"; result = false; } - // We don't need to round shares, they are integral MPT + // We don't round shares, they are integral MPT auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); if (!vaultDeltaShares || vaultDeltaShares->delta == kZero) { - JLOG(j.fatal()) << // - "Invariant failed: withdrawal must change vault shares"; + JLOG(j.fatal()) << "Invariant failed: withdrawal must change vault shares"; return false; // That's all we can do } if (vaultDeltaShares->delta * -1 != accountDeltaShares->delta) { - JLOG(j.fatal()) << // - "Invariant failed: withdrawal must change depositor " - "and vault shares by equal amount"; + JLOG(j.fatal()) << "Invariant failed: " << // + "withdrawal must change depositor and vault shares by equal amount"; result = false; } @@ -900,8 +934,8 @@ ValidVault::finalize( // Note, vaultBalance is negative (see check above) if (assetTotalDelta != vaultPseudoDeltaAssets) { - JLOG(j.fatal()) << "Invariant failed: withdrawal and " - "assets outstanding must add up"; + JLOG(j.fatal()) + << "Invariant failed: withdrawal and assets outstanding must add up"; result = false; } @@ -910,8 +944,8 @@ ValidVault::finalize( if (assetAvailableDelta != vaultPseudoDeltaAssets) { - JLOG(j.fatal()) << "Invariant failed: withdrawal and " - "assets available must add up"; + JLOG(j.fatal()) + << "Invariant failed: withdrawal and assets available must add up"; result = false; } @@ -929,12 +963,11 @@ ValidVault::finalize( // The owner can use clawback to force-burn shares when the // vault is empty but there are outstanding shares if (!(beforeShares && beforeShares->sharesTotal > 0 && - vaultHoldsNoAssets(beforeVault) && beforeVault.owner == tx[sfAccount])) + isVaultEmpty(beforeVault) && beforeVault.owner == tx[sfAccount])) { - JLOG(j.fatal()) << // - "Invariant failed: clawback may only be performed " - "by the asset issuer, or by the vault owner of an " - "empty vault"; + JLOG(j.fatal()) << "Invariant failed: " << // + "clawback may only be performed by the asset issuer, or by the vault " + "owner of an empty vault"; return false; // That's all we can do } } @@ -942,19 +975,13 @@ ValidVault::finalize( auto const maybeVaultDeltaAssets = deltaAssets(afterVault.pseudoId); if (maybeVaultDeltaAssets) { - auto const totalDelta = DeltaInfo::makeDelta( - beforeVault.assetsTotal, afterVault.assetsTotal, vaultAsset); - auto const availableDelta = DeltaInfo::makeDelta( - beforeVault.assetsAvailable, afterVault.assetsAvailable, vaultAsset); auto const minScale = - computeCoarsestScale({*maybeVaultDeltaAssets, totalDelta, availableDelta}); + computeVaultMinScale(*maybeVaultDeltaAssets, view.rules()); auto const vaultDeltaAssets = roundToAsset(vaultAsset, maybeVaultDeltaAssets->delta, minScale); if (vaultDeltaAssets >= kZero) { - JLOG(j.fatal()) << // - "Invariant failed: clawback must decrease vault " - "balance"; + JLOG(j.fatal()) << "Invariant failed: clawback must decrease vault balance"; result = false; } @@ -963,8 +990,7 @@ ValidVault::finalize( if (assetsTotalDelta != vaultDeltaAssets) { JLOG(j.fatal()) << // - "Invariant failed: clawback and assets outstanding " - "must add up"; + "Invariant failed: clawback and assets outstanding must add up"; result = false; } @@ -975,12 +1001,11 @@ ValidVault::finalize( if (assetAvailableDelta != vaultDeltaAssets) { JLOG(j.fatal()) << // - "Invariant failed: clawback and assets available " - "must add up"; + "Invariant failed: clawback and assets available must add up"; result = false; } } - else if (!vaultHoldsNoAssets(beforeVault)) + else if (!isVaultEmpty(beforeVault)) { JLOG(j.fatal()) << // "Invariant failed: clawback must change vault balance"; @@ -998,8 +1023,7 @@ ValidVault::finalize( if (maybeAccountDeltaShares->delta >= kZero) { JLOG(j.fatal()) << // - "Invariant failed: clawback must decrease holder " - "shares"; + "Invariant failed: clawback must decrease holder shares"; result = false; } @@ -1014,9 +1038,8 @@ ValidVault::finalize( if (vaultDeltaShares->delta * -1 != maybeAccountDeltaShares->delta) { - JLOG(j.fatal()) << // - "Invariant failed: clawback must change holder and " - "vault shares by equal amount"; + JLOG(j.fatal()) << "Invariant failed: " << // + "clawback must change holder and vault shares by equal amount"; result = false; } diff --git a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp index aaaf3cced8..50d165a2ba 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include // IWYU pragma: keep #include @@ -26,6 +28,24 @@ namespace xrpl { +[[nodiscard]] +static STAmount +roundToVaultScale(STAmount const& amount, SLE::const_ref vault) +{ + XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::roundToVaultScale : valid vault sle"); + XRPL_ASSERT( + amount.asset() == vault->at(sfAsset), "xrpl::roundToVaultScale : valid vault asset"); + + if (amount.integral()) + return amount; + + int const postScale = [&]() { + NumberRoundModeGuard const rg(Number::RoundingMode::ToNearest); + return scale(vault->at(sfAssetsTotal) + amount, vault->at(sfAsset)); + }(); + return roundToScale(amount, postScale, Number::RoundingMode::Downward); +} + NotTEC VaultDeposit::preflight(PreflightContext const& ctx) { @@ -49,9 +69,9 @@ VaultDeposit::preclaim(PreclaimContext const& ctx) return tecNO_ENTRY; auto const& account = ctx.tx[sfAccount]; - auto const assets = ctx.tx[sfAmount]; + auto const amount = ctx.tx[sfAmount]; auto const vaultAsset = vault->at(sfAsset); - if (assets.asset() != vaultAsset) + if (amount.asset() != vaultAsset) return tecWRONG_ASSET; auto const& vaultAccount = vault->at(sfAccount); @@ -63,7 +83,7 @@ VaultDeposit::preclaim(PreclaimContext const& ctx) auto const mptIssuanceID = vault->at(sfShareMPTID); auto const vaultShare = MPTIssue(mptIssuanceID); - if (vaultShare == assets.asset()) + if (vaultShare == amount.asset()) { // LCOV_EXCL_START JLOG(ctx.j.error()) << "VaultDeposit: vault shares and assets cannot be same."; @@ -122,28 +142,69 @@ VaultDeposit::preclaim(PreclaimContext const& ctx) if (auto const ter = requireAuth(ctx.view, vaultAsset, account); !isTesSuccess(ter)) return ter; - if (accountHolds( - ctx.view, - account, - vaultAsset, - FreezeHandling::ZeroIfFrozen, - AuthHandling::ZeroIfUnauthorized, - ctx.j, - SpendableHandling::FullBalance) < assets) + bool const fix320Enabled = ctx.view.rules().enabled(fixCleanup3_2_0); + auto const roundedAmount = fix320Enabled ? roundToVaultScale(amount, vault) : amount; + + if (fix320Enabled && roundedAmount == beast::kZero) + { + JLOG(ctx.j.warn()) << "VaultDeposit: deposit amount: " << ctx.tx[sfAmount] + << " is zero at vault scale"; + return tecPRECISION_LOSS; + } + + auto const accountBalance = accountHolds( + ctx.view, + account, + vaultAsset, + FreezeHandling::ZeroIfFrozen, + AuthHandling::ZeroIfUnauthorized, + ctx.j, + SpendableHandling::FullBalance); + + if (accountBalance < roundedAmount) return tecINSUFFICIENT_FUNDS; + // IOU precision checks + if (fix320Enabled && !roundedAmount.integral()) + { + // reject deposits that would canonicalize to a no-op at the depositor's trustline scale. + // Skipped for issuer-as-depositor: accountHolds returns (kMaxValue @ kMaxOffset) which + // would always trip the predicate. + if (account != amount.getIssuer() && + amount.isZeroAtScale(scale(accountBalance, vaultAsset))) + { + JLOG(ctx.j.warn()) << "VaultDeposit: amount " << amount.getFullText() + << " rounds to zero at counterparty trust-line scale"; + return tecPRECISION_LOSS; + } + } + return tesSUCCESS; } TER VaultDeposit::doApply() { + bool const fix320Enabled = view().rules().enabled(fixCleanup3_2_0); auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID])); if (!vault) return tefINTERNAL; // LCOV_EXCL_LINE auto const vaultAsset = vault->at(sfAsset); - auto const amount = ctx_.tx[sfAmount]; + // Post-amendment IOU only: round Downward to the AssetsTotal precision so + // a sub-ULP tail can't be silently absorbed by one rail and not the other. + auto const amount = + fix320Enabled ? roundToVaultScale(ctx_.tx[sfAmount], vault) : ctx_.tx[sfAmount]; + + // We validated zero-amount in preclaim, if we ended up with zero now, fail hard. + if (amount == beast::kZero) + { + // LCOV_EXCL_START + JLOG(j_.error()) << "VaultDeposit: deposit amount: " << ctx_.tx[sfAmount] << " is zero"; + return tecINTERNAL; + // LCOV_EXCL_STOP + } + // Make sure the depositor can hold shares. auto const mptIssuanceID = (*vault)[sfShareMPTID]; auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID)); @@ -259,7 +320,7 @@ VaultDeposit::doApply() // trust line into debt the exact case preclaim authorizes via SpendableHandling::FullBalance. // The check thus converts a preclaim- authorized deposit into tefINTERNAL after the asset // transfer. - if (!view().rules().enabled(fixCleanup3_2_0)) + if (!fix320Enabled) { // Sanity check if (accountHolds( diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index c6a9a54a53..d8527876f1 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -6457,6 +6457,489 @@ class Vault_test : public beast::unit_test::Suite runTest(amendments); } + // Bug: DeltaInfo::makeDelta uses max(scale(after), scale(before)) for the + // sfAssetsTotal and sfAssetsAvailable deltas, and visitEntry applies the + // same max() for the vault pseudo-account RippleState. When + // sfAssetsTotal sits exactly at 1e16 (IOU exponent 1, ULP = 10) and a + // withdrawal of 5 USD brings it to 9.999...995e15 (IOU exponent 0, + // ULP = 1), all three computations pick the anterior coarser scale 1. + // roundToAsset(-5, scale=1) collapses to 0, so the invariant check + // vaultPseudoDeltaAssets >= kZero fires even though the state change is + // valid and fully consistent at IOU precision. + // + // Fix (fixCleanup3_2_0): finalize compares the vault pseudo-account and + // sfAssetsTotal/Available deltas directly in Number space, bypassing + // scale-coarsened rounding. + void + testBugMakeDeltaAnteriorScale() + { + using namespace test::jtx; + + auto runScenario = [this](FeatureBitset features, TER expected) { + Env env(*this, features); + + Account const issuer{"issuer"}; + Account const alice{"alice"}; + + env.fund(XRP(100'000), issuer, alice); + env.close(); + env(fset(issuer, asfDefaultRipple)); + env.close(); + + PrettyAsset const usd{issuer["USD"]}; + // Trust limit of 2e16, fund exactly 1e16 so deposit lands at the + // IOU scale-1 boundary (exponent 1, ULP = 10). + STAmount const fundAndDeposit{usd.raw(), Number{1, 16}}; + + env(trust(alice, STAmount{usd.raw(), 2, 16})); + env.close(); + env(pay(issuer, alice, fundAndDeposit)); + env.close(); + + Vault const vault{env}; + auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = usd}); + vaultTx[sfScale] = 0; + env(vaultTx); + env.close(); + + // sfAssetsTotal = sfAssetsAvailable = 1e16 (exponent 1, ULP = 10). + env(vault.deposit( + {.depositor = alice, .id = vaultKeylet.key, .amount = fundAndDeposit})); + env.close(); + + // Withdraw 5 USD: -5 is sub-ULP at the anterior scale (ULP = 10) + // but exact at the posterior scale (ULP = 1). The state change is + // consistent; only the invariant's scale selection is wrong. + env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = usd(5)}), + Ter(expected)); + env.close(); + }; + + { + testcase( + "bug: VaultWithdraw across IOU scale boundary fires invariant " + "(pre-fixCleanup3_2_0)"); + runScenario(testableAmendments() - fixCleanup3_2_0, tecINVARIANT_FAILED); + } + { + testcase( + "bug: VaultWithdraw across IOU scale boundary succeeds " + "(post-fixCleanup3_2_0)"); + runScenario(testableAmendments(), tesSUCCESS); + } + } + + // Bug: DeltaInfo::makeDelta uses max(scale(after), scale(before)) for + // sfAssetsTotal/Available deltas. This is symmetric to + // testBugMakeDeltaAnteriorScale but in the opposite direction: a deposit + // pushes assetsTotal from just below 1e16 (IOU exponent 0, ULP = 1) to just + // above it (exponent 1, ULP = 10). makeDelta picks the coarser *posterior* + // scale 1. The trust line balance rounds from atEdge + 2 = 10,000,000,000,000,001 + // → 1e16, so the pseudo-account delta is only +1 in IOU space. + // roundToAsset(+1, scale=1) = 0 fires "deposit must increase vault balance" + // even though the state change is consistent at every precision boundary. + // + // Fix (fixCleanup3_2_0): computeVaultMinScale uses the posterior Number-space + // scale of sfAssetsTotal (which retains the full value 10,000,000,000,000,001, + // exponent 0), giving minScale = 0. roundToAsset(+1, scale=0) = 1 > 0 and + // the invariant passes. However the transactor's own precision guard fires + // first (bob pays 2 USD, vault receives only 1 due to IOU rounding), so the + // post-amendment result is tecPRECISION_LOSS rather than tesSUCCESS — + // the depositor is protected from silently losing 1 USD to rounding. + void + testBugMakeDeltaPosteriorScale() + { + using namespace test::jtx; + + auto runScenario = [this](FeatureBitset features, TER expected) { + Env env(*this, features); + + Account const issuer{"issuer"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + + env.fund(XRP(100'000), issuer, alice, bob); + env.close(); + env(fset(issuer, asfDefaultRipple)); + env.close(); + + PrettyAsset const usd{issuer["USD"]}; + // atEdge is the largest IOU value with exponent 0 (ULP = 1). + // A deposit of 2 USD brings assetsTotal to 10,000,000,000,000,001 + // in Number space, crossing the 1e16 boundary in IOU space. + STAmount const atEdge{usd.raw(), Number{9'999'999'999'999'999LL}}; + + env(trust(alice, STAmount{usd.raw(), 2, 16})); + env(trust(bob, usd(100))); + env.close(); + env(pay(issuer, alice, atEdge)); + env(pay(issuer, bob, usd(2))); + env.close(); + + Vault const vault{env}; + auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = usd}); + vaultTx[sfScale] = 0; + env(vaultTx); + env.close(); + + // sfAssetsTotal = sfAssetsAvailable = atEdge (exponent 0, ULP = 1) + env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = atEdge})); + env.close(); + + // Deposit 2 USD: +2 is sub-ULP at the posterior IOU scale (ULP = 10) + // but exact at the Number scale retained by sfAssetsTotal. + env(vault.deposit({.depositor = bob, .id = vaultKeylet.key, .amount = usd(2)}), + Ter(expected)); + env.close(); + }; + + { + testcase( + "bug: VaultDeposit across IOU scale boundary fires invariant " + "(pre-fixCleanup3_2_0)"); + runScenario(testableAmendments() - fixCleanup3_2_0, tecINVARIANT_FAILED); + } + { + testcase( + "bug: VaultDeposit across IOU scale boundary succeeds " + "(post-fixCleanup3_2_0)"); + runScenario(testableAmendments(), tecPRECISION_LOSS); + } + } + + // Bug: ValidVault::visitEntry computes destinationDelta.scale as + // max(before_exponent, after_exponent) for RippleState entries. When a + // withdrawal credits a destination whose IOU balance sits just below a + // power-of-10 boundary (atEdge = 9'999'999'999'999'999), the post-credit + // STAmount rounds up one exponent (exponent 0 → 1), making + // destinationDelta.scale = 1. The invariant then calls + // roundToAsset(+2 USD, scale=1) = 0 and incorrectly fires + // "withdrawal must increase destination balance". + // + // Fix (fixCleanup3_2_0): finalize compares destination delta directly in + // Number space, bypassing scale-coarsened rounding. The transaction + // itself succeeds because the effective IOU credit is non-trivial at + // Number precision even though the STAmount exponent shifted. + void + testVaultWithdrawCanonicalizeToZero() + { + using namespace test::jtx; + + enum class DestKind : bool { ThirdParty = false, Self = true }; + + auto runScenario = [this](FeatureBitset features, DestKind destKind, TER expected) { + Env env(*this, features); + + Account const issuer{"issuer"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + + env.fund(XRP(100'000), issuer, alice, bob); + env.close(); + env(fset(issuer, asfDefaultRipple)); + env.close(); + + PrettyAsset const usd{issuer["USD"]}; + STAmount const aliceLimit{usd.raw(), 2, 16}; + STAmount const bobLimit{usd.raw(), 2, 16}; + STAmount const atEdge{usd.raw(), Number{9'999'999'999'999'999LL}}; + + env(trust(alice, aliceLimit)); + if (destKind == DestKind::ThirdParty) + env(trust(bob, bobLimit)); + env.close(); + + env(pay(issuer, alice, usd(1'000))); + if (destKind == DestKind::ThirdParty) + env(pay(issuer, bob, atEdge)); + env.close(); + + Vault const vault{env}; + auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = usd}); + vaultTx[sfScale] = 0; + env(vaultTx); + env.close(); + + env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = usd(1'000)})); + env.close(); + + // For the self-destination case, push alice's own trust line to + // the IOU edge so the next withdraw inflow crosses the boundary. + if (destKind == DestKind::Self) + { + env(pay(issuer, alice, atEdge)); + env.close(); + } + + auto tx = vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = usd(2)}); + if (destKind == DestKind::ThirdParty) + tx[sfDestination] = bob.human(); + env(tx, Ter(expected)); + env.close(); + }; + + { + testcase( + "bug: VaultWithdraw to third-party at IOU edge fires invariant " + "(pre-fixCleanup3_2_0)"); + runScenario( + testableAmendments() - fixCleanup3_2_0, DestKind::ThirdParty, tecINVARIANT_FAILED); + } + { + testcase( + "bug: VaultWithdraw to third-party at IOU edge succeeds " + "(post-fixCleanup3_2_0)"); + runScenario(testableAmendments(), DestKind::ThirdParty, tesSUCCESS); + } + { + testcase( + "bug: VaultWithdraw to self at IOU edge fires invariant " + "(pre-fixCleanup3_2_0)"); + runScenario( + testableAmendments() - fixCleanup3_2_0, DestKind::Self, tecINVARIANT_FAILED); + } + { + testcase( + "bug: VaultWithdraw to self at IOU edge succeeds " + "(post-fixCleanup3_2_0)"); + runScenario(testableAmendments(), DestKind::Self, tesSUCCESS); + } + } + + // Bug: the equality check (vault outflow == destination inflow) was + // skipped whenever the destination delta rounded to zero at localMinScale, + // including cases where the vault outflow rounded to a non-zero value and + // a representable amount of value was genuinely destroyed. + // + // Scenario: Bob's IOU balance sits 5 units below the 10^16 STAmount + // precision boundary (atEdge2 = 9,999,999,999,999,995). A withdrawal of + // 6 USD shifts his balance across that boundary: the exponent increments + // (0 → 1), so his effective inflow in Number space is only +5 — 1 USD is + // consumed by the precision-boundary rounding and cannot be credited. + // + // The destroyed amount (1 USD) is sub-ULP at destinationScale=1 (step=10), + // so the check treats it as an unavoidable IOU-precision artefact and + // lets the transaction succeed. + // + // Contrast: if 15 USD were destroyed at the same scale (destroyed ≥ step), + // floor(15/10)=1 ≠ 0 and the invariant would fire — that discrepancy IS + // representable and indicates a real accounting bug. + // + // Pre-fixCleanup3_2_0: the "must increase destination balance" check fires + // because roundedDestinationDelta = 0 ≤ 0. + void + testVaultWithdrawEqualityEnforced() + { + using namespace test::jtx; + + auto runScenario = [this](FeatureBitset features, TER expected) { + Env env(*this, features); + + Account const issuer{"issuer"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + + env.fund(XRP(100'000), issuer, alice, bob); + env.close(); + env(fset(issuer, asfDefaultRipple)); + env.close(); + + PrettyAsset const usd{issuer["USD"]}; + STAmount const aliceLimit{usd.raw(), 2, 16}; + STAmount const bobLimit{usd.raw(), 2, 16}; + // Bob's balance sits 5 units below the 10^16 STAmount precision + // boundary. Receiving 6 USD shifts his exponent 0 → 1; the + // STAmount records +5, not +6 (1 USD is lost to rounding). + STAmount const atEdge2{usd.raw(), Number{9'999'999'999'999'995LL}}; + + env(trust(alice, aliceLimit)); + env(trust(bob, bobLimit)); + env.close(); + + env(pay(issuer, alice, usd(1'000))); + env(pay(issuer, bob, atEdge2)); + env.close(); + + Vault const vault{env}; + auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = usd}); + vaultTx[sfScale] = 0; + env(vaultTx); + env.close(); + + env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = usd(1'000)})); + env.close(); + + // Withdraw 6 USD to Bob: vault loses 6, Bob gains only 5. + // Destroyed amount = 1 USD, which is sub-ULP at destinationScale=1. + auto tx = vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = usd(6)}); + tx[sfDestination] = bob.human(); + env(tx, Ter(expected)); + env.close(); + }; + + { + testcase( + "bug: VaultWithdraw to destination at IOU precision boundary fires " + "invariant (pre-fixCleanup3_2_0)"); + runScenario(testableAmendments() - fixCleanup3_2_0, tecINVARIANT_FAILED); + } + { + testcase( + "bug: VaultWithdraw to destination at IOU precision boundary succeeds " + "when destroyed amount is sub-ULP (post-fixCleanup3_2_0)"); + runScenario(testableAmendments(), tesSUCCESS); + } + } + + // Bug: when a depositor's IOU trustline balance is very large (e.g. + // ~1e17), adding a small deposit (e.g. 1 USD) leaves sfAssetsTotal + // unchanged at IOU precision because the increment is sub-ULP at the + // vault's current asset scale. The vault records the deposit, mints + // shares, and decrements the depositor's trustline, but sfAssetsTotal + // does not change — the conservation invariant fires because the rail + // delta is zero. + // + // Two sub-cases are exercised: + // 1. First-ever deposit into an empty vault: the depositor's own + // trustline has a large balance so 1 USD canonicalizes to zero + // when written back through the IOU rail. + // 2. Subsequent deposit after the vault already holds a large + // sfAssetsTotal: a different depositor (bob, with a small balance) + // sends 1 USD, which again rounds to zero at the vault's coarse + // asset scale. + // + // Fix (fixCleanup3_2_0): the deposit transactor checks whether + // roundToAsset(amount, vault_scale) == 0 and rejects early with + // tecPRECISION_LOSS before any state is modified. + void + testVaultDepositCanonicalizeToZero() + { + using namespace test::jtx; + auto runScenario = [this](FeatureBitset features, TER expected) { + Env env(*this, features); + + Account const issuer{"issuer"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + + env.fund(XRP(100'000), issuer, alice, bob); + env.close(); + + env(fset(issuer, asfDefaultRipple)); + env.close(); + + PrettyAsset const usd{issuer["USD"]}; + + STAmount const trustLimit{usd.raw(), Number{99'999'999'999'999'999LL}}; + STAmount const aliceFund{usd.raw(), Number{99'999'999'999'999'999LL}}; + + env(trust(alice, trustLimit)); + env(trust(bob, trustLimit)); + env.close(); + + env(pay(issuer, alice, aliceFund)); + env(pay(issuer, bob, usd(1000))); + env.close(); + + Vault const vault{env}; + + // Scale=0 so sfAssetsTotal stores whole USD + auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = usd}); + vaultTx[sfScale] = 0; + env(vaultTx); + env.close(); + + // Alice's deposit canonicalizes to zero at her own trustline scale + env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = usd(1)}), + Ter(expected)); + + // Increase vault-scale + env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = aliceFund})); + env.close(); + + env(vault.deposit({.depositor = bob, .id = vaultKeylet.key, .amount = usd(1)}), + Ter(expected)); + env.close(); + }; + + { + testcase( + "bug: VaultDeposit below Vault precision canonicalized to zero " + "(pre-fixCleanup3_2_0)"); + runScenario(testableAmendments() - fixCleanup3_2_0, tecINVARIANT_FAILED); + } + { + testcase( + "bug: VaultDeposit below Vault precision canonicalized to zero " + "(post-fixCleanup3_2_0)"); + runScenario(testableAmendments(), tecPRECISION_LOSS); + } + } + + // VaultDeposit by issuer with the vault parked at the IOU 16-digit + // edge (9.999e15). Issuer mints 2 more USD; the vault trust line + // goes 9.999e15 → 10^16, gaining 1 unit instead of 2 (canonicalization). + // + // Pre-fixCleanup3_2_0: the proactive check is absent; the deposit + // applies, then VaultInvariant's "deposit must increase vault + // balance" assertion fires at finalize time on the rounded vault + // delta of zero, returning tecINVARIANT_FAILED. + // Post-amendment: reject deposit that is not representable at Vault scale. + void + testBugIssuerVaultDepositAtEdge() + { + using namespace test::jtx; + + auto runScenario = [this](FeatureBitset features, TER expected) { + Env env(*this, features); + + Account const issuer{"issuer"}; + Account const owner{"owner"}; + + env.fund(XRP(100'000), issuer, owner); + env.close(); + env(fset(issuer, asfDefaultRipple)); + env.close(); + + PrettyAsset const usd{issuer["USD"]}; + STAmount const trustLimit{usd.raw(), 2, 16}; + STAmount const ownerFund{usd.raw(), Number{9'999'999'999'999'999LL}}; + + env(trust(owner, trustLimit)); + env.close(); + env(pay(issuer, owner, ownerFund)); + env.close(); + + Vault const vault{env}; + auto [vaultTx, vaultKeylet] = vault.create({.owner = owner, .asset = usd}); + vaultTx[sfScale] = 0; + env(vaultTx); + env.close(); + env(vault.deposit({.depositor = owner, .id = vaultKeylet.key, .amount = ownerFund})); + env.close(); + + // Vault pseudo-account is now at 9.999e15. Issuer mints 2 + // more USD. Pre: tecINVARIANT_FAILED at finalize. Post: + // tecPRECISION_LOSS proactively. Either way, no value moves. + env(vault.deposit({.depositor = issuer, .id = vaultKeylet.key, .amount = usd(2)}), + Ter(expected)); + env.close(); + }; + + { + testcase( + "bug: VaultDeposit by issuer at IOU edge fires " + "tecINVARIANT_FAILED at finalize (pre-fixCleanup3_2_0)"); + runScenario(testableAmendments() - fixCleanup3_2_0, tecINVARIANT_FAILED); + } + { + testcase( + "bug: VaultDeposit by issuer at IOU edge rejects with " + "tecPRECISION_LOSS proactively (post-fixCleanup3_2_0)"); + runScenario(testableAmendments(), tecPRECISION_LOSS); + } + } + void testReferenceHolding() { @@ -6940,6 +7423,12 @@ public: void run() override { + testVaultWithdrawEqualityEnforced(); + testBugIssuerVaultDepositAtEdge(); + testBugMakeDeltaPosteriorScale(); + testBugMakeDeltaAnteriorScale(); + testVaultDepositCanonicalizeToZero(); + testVaultWithdrawCanonicalizeToZero(); testVaultDepositNegativeBalanceFromOppositeLimit(); testSequences(); testPreflight(); diff --git a/src/test/protocol/STAmount_test.cpp b/src/test/protocol/STAmount_test.cpp index c7207589ff..720fafa8f2 100644 --- a/src/test/protocol/STAmount_test.cpp +++ b/src/test/protocol/STAmount_test.cpp @@ -1203,8 +1203,6 @@ public: } } - //-------------------------------------------------------------------------- - void testIsZeroAtScale() { From 49567e728311299dc7a264d30b0245d70d8f6e0a Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Tue, 26 May 2026 20:18:40 +0200 Subject: [PATCH 028/135] fix: Fix edge-case where vault-depositor may get stuck (#7139) --- include/xrpl/ledger/helpers/VaultHelpers.h | 35 +- src/libxrpl/ledger/helpers/VaultHelpers.cpp | 37 +- .../tx/transactors/vault/VaultWithdraw.cpp | 89 ++- src/test/app/Vault_test.cpp | 608 ++++++++++++++++++ 4 files changed, 753 insertions(+), 16 deletions(-) diff --git a/include/xrpl/ledger/helpers/VaultHelpers.h b/include/xrpl/ledger/helpers/VaultHelpers.h index 14b0c004cb..29270d913f 100644 --- a/include/xrpl/ledger/helpers/VaultHelpers.h +++ b/include/xrpl/ledger/helpers/VaultHelpers.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include @@ -43,6 +45,14 @@ sharesToAssetsDeposit( /** Controls whether to truncate shares instead of rounding. */ enum class TruncateShares : bool { No = false, Yes = true }; +/** Controls whether the withdraw conversion helpers + (assetsToSharesWithdraw and sharesToAssetsWithdraw) subtract + sfLossUnrealized from sfAssetsTotal before computing the exchange rate. + The default (No) applies the standard discounted rate; Yes is used when + the redeemer is the sole remaining shareholder. +*/ +enum class WaiveUnrealizedLoss : bool { No = false, Yes = true }; + /** From the perspective of a vault, return the number of shares to demand from the depositor when they ask to withdraw a fixed amount of assets. Since shares are MPT this number is integral, and it will be rounded to nearest @@ -52,6 +62,8 @@ enum class TruncateShares : bool { No = false, Yes = true }; @param issuance The MPTokenIssuance SLE for the vault's shares. @param assets The amount of assets to convert. @param truncate Whether to truncate instead of rounding. + @param waive Whether to waive the unrealized-loss discount when computing + the exchange rate. @return The number of shares, or nullopt on error. */ @@ -60,7 +72,8 @@ assetsToSharesWithdraw( std::shared_ptr const& vault, std::shared_ptr const& issuance, STAmount const& assets, - TruncateShares truncate = TruncateShares::No); + TruncateShares truncate = TruncateShares::No, + WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No); /** From the perspective of a vault, return the number of assets to give the depositor when they redeem a fixed amount of shares. Note, since shares are @@ -69,6 +82,8 @@ assetsToSharesWithdraw( @param vault The vault SLE. @param issuance The MPTokenIssuance SLE for the vault's shares. @param shares The amount of shares to convert. + @param waive Whether to waive (i.e. not subtract) the vault's unrealized + loss when computing the exchange rate. @return The number of assets, or nullopt on error. */ @@ -76,6 +91,22 @@ assetsToSharesWithdraw( sharesToAssetsWithdraw( std::shared_ptr const& vault, std::shared_ptr const& issuance, - STAmount const& shares); + STAmount const& shares, + WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No); + +/** Returns true iff `account` holds all of the vault's outstanding shares — + i.e. is the sole remaining shareholder. Returns false if the account + holds no shares or fewer than the total outstanding. + + @param view The ledger view. + @param account The candidate sole shareholder. + @param issuance The MPTokenIssuance SLE for the vault's shares; provides + both the share MPTID and the outstanding-amount total. +*/ +[[nodiscard]] bool +isSoleShareholder( + ReadView const& view, + AccountID const& account, + std::shared_ptr const& issuance); } // namespace xrpl diff --git a/src/libxrpl/ledger/helpers/VaultHelpers.cpp b/src/libxrpl/ledger/helpers/VaultHelpers.cpp index 8832e0078f..587923953d 100644 --- a/src/libxrpl/ledger/helpers/VaultHelpers.cpp +++ b/src/libxrpl/ledger/helpers/VaultHelpers.cpp @@ -2,11 +2,16 @@ #include #include +#include +#include +#include +#include #include #include #include #include // IWYU pragma: keep +#include #include #include @@ -70,7 +75,8 @@ assetsToSharesWithdraw( std::shared_ptr const& vault, std::shared_ptr const& issuance, STAmount const& assets, - TruncateShares truncate) + TruncateShares truncate, + WaiveUnrealizedLoss waive) { XRPL_ASSERT(!assets.negative(), "xrpl::assetsToSharesWithdraw : non-negative assets"); XRPL_ASSERT( @@ -80,7 +86,8 @@ assetsToSharesWithdraw( return std::nullopt; // LCOV_EXCL_LINE Number assetTotal = vault->at(sfAssetsTotal); - assetTotal -= vault->at(sfLossUnrealized); + if (waive == WaiveUnrealizedLoss::No) + assetTotal -= vault->at(sfLossUnrealized); STAmount shares{vault->at(sfShareMPTID)}; if (assetTotal == 0) return shares; @@ -96,7 +103,8 @@ assetsToSharesWithdraw( sharesToAssetsWithdraw( std::shared_ptr const& vault, std::shared_ptr const& issuance, - STAmount const& shares) + STAmount const& shares, + WaiveUnrealizedLoss waive) { XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsWithdraw : non-negative shares"); XRPL_ASSERT( @@ -106,7 +114,8 @@ sharesToAssetsWithdraw( return std::nullopt; // LCOV_EXCL_LINE Number assetTotal = vault->at(sfAssetsTotal); - assetTotal -= vault->at(sfLossUnrealized); + if (waive == WaiveUnrealizedLoss::No) + assetTotal -= vault->at(sfLossUnrealized); STAmount assets{vault->at(sfAsset)}; if (assetTotal == 0) return assets; @@ -115,4 +124,24 @@ sharesToAssetsWithdraw( return assets; } +[[nodiscard]] bool +isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref issuance) +{ + XRPL_ASSERT( + issuance && issuance->getType() == ltMPTOKEN_ISSUANCE, + "xrpl::isSoleShareholder : valid issuance SLE"); + + std::uint64_t const outstanding = issuance->at(sfOutstandingAmount); + if (outstanding == 0) + return false; + + auto const shareMPTID = + makeMptID(issuance->getFieldU32(sfSequence), issuance->getAccountID(sfIssuer)); + auto const sleToken = view.read(keylet::mptoken(shareMPTID, account)); + if (!sleToken) + return false; // LCOV_EXCL_LINE + + return sleToken->getFieldU64(sfMPTAmount) == outstanding; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp index ced82f6735..c52d94ac0b 100644 --- a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp @@ -4,12 +4,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -26,6 +28,18 @@ namespace xrpl { +static WaiveUnrealizedLoss +shouldWaiveWithdrawal(ReadView const& view, AccountID const& account, SLE::const_ref issuance) +{ + XRPL_ASSERT( + issuance && issuance->getType() == ltMPTOKEN_ISSUANCE, + "xrpl::shouldWaiveWithdrawal : valid issuance sle"); + + return view.rules().enabled(fixCleanup3_2_0) && isSoleShareholder(view, account, issuance) + ? WaiveUnrealizedLoss::Yes + : WaiveUnrealizedLoss::No; +} + NotTEC VaultWithdraw::preflight(PreflightContext const& ctx) { @@ -102,9 +116,14 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx) // LCOV_EXCL_STOP } + // When the user is the sole shareholder they own both the available and future value. + // We waive the unrealized-loss subtraction in this case to avoid user withdrawing all of + // their shares but keeping future value in the vault. + auto const waiveUnrealizedLoss = shouldWaiveWithdrawal(ctx.view, account, sleIssuance); try { - auto const maybeAssets = sharesToAssetsWithdraw(vault, sleIssuance, amount); + auto const maybeAssets = + sharesToAssetsWithdraw(vault, sleIssuance, amount, waiveUnrealizedLoss); if (!maybeAssets) return tefINTERNAL; // LCOV_EXCL_LINE @@ -182,13 +201,19 @@ VaultWithdraw::doApply() MPTIssue const share{mptIssuanceID}; STAmount sharesRedeemed = {share}; STAmount assetsWithdrawn; + + // When the user is the sole shareholder they own both the available and future value. + // We waive the unrealized-loss subtraction in this case to avoid user withdrawing all of their + // shares but keeping future value in the vault. + auto const waiveUnrealizedLoss = shouldWaiveWithdrawal(view(), accountID_, sleIssuance); try { if (amount.asset() == vaultAsset) { // Fixed assets, variable shares. { - auto const maybeShares = assetsToSharesWithdraw(vault, sleIssuance, amount); + auto const maybeShares = assetsToSharesWithdraw( + vault, sleIssuance, amount, TruncateShares::No, waiveUnrealizedLoss); if (!maybeShares) return tecINTERNAL; // LCOV_EXCL_LINE sharesRedeemed = *maybeShares; @@ -196,7 +221,8 @@ VaultWithdraw::doApply() if (sharesRedeemed == beast::kZero) return tecPRECISION_LOSS; - auto const maybeAssets = sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed); + auto const maybeAssets = + sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed, waiveUnrealizedLoss); if (!maybeAssets) return tecINTERNAL; // LCOV_EXCL_LINE assetsWithdrawn = *maybeAssets; @@ -205,7 +231,8 @@ VaultWithdraw::doApply() { // Fixed shares, variable assets. sharesRedeemed = amount; - auto const maybeAssets = sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed); + auto const maybeAssets = + sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed, waiveUnrealizedLoss); if (!maybeAssets) return tecINTERNAL; // LCOV_EXCL_LINE assetsWithdrawn = *maybeAssets; @@ -238,22 +265,64 @@ VaultWithdraw::doApply() auto assetsAvailable = vault->at(sfAssetsAvailable); auto assetsTotal = vault->at(sfAssetsTotal); - [[maybe_unused]] auto const lossUnrealized = vault->at(sfLossUnrealized); + auto const lossUnrealized = vault->at(sfLossUnrealized); XRPL_ASSERT( lossUnrealized <= (assetsTotal - assetsAvailable), "xrpl::VaultWithdraw::doApply : loss and assets do balance"); - // The vault must have enough assets on hand. The vault may hold assets - // that it has already pledged. That is why we look at AssetAvailable - // instead of the pseudo-account balance. + // The vault must have enough assets on hand. if (*assetsAvailable < assetsWithdrawn) { JLOG(j_.debug()) << "VaultWithdraw: vault doesn't hold enough assets"; return tecINSUFFICIENT_FUNDS; } - assetsTotal -= assetsWithdrawn; - assetsAvailable -= assetsWithdrawn; + // Post-fixCleanup3_2_0 "final withdrawal" rule: + // a transaction that would burn every outstanding share is only permitted when the vault is in + // a clean state — no outstanding receivables and no unrealized loss. Otherwise the resulting + // (shares == 0, assetsTotal > 0) state would violate the zero-sized-vault invariant. + // + // When the rule applies, the payout is the remaining sfAssetsAvailable; in a clean vault + // the helper result should already equal that value, and any mismatch is a rounding artifact + // worth logging. + bool const isFinalWithdrawal = + sharesRedeemed == STAmount{share, sleIssuance->at(sfOutstandingAmount)}; + if (view().rules().enabled(fixCleanup3_2_0) && isFinalWithdrawal) + { + // Unreachable: a final withdrawal with lossUnrealized > 0 has + // assetsWithdrawn == assetsTotal > assetsAvailable, which the + // insufficient-funds guard above already rejected. + if (*lossUnrealized != beast::kZero) + { + // LCOV_EXCL_START + UNREACHABLE( + "xrpl::VaultWithdraw::doApply : final withdrawal with non-zero unrealized loss"); + JLOG(j_.fatal()) + << "VaultWithdraw: " // + "Cannot burn all outstanding shares while unrealized loss is non-zero"; + return tefINTERNAL; + // LCOV_EXCL_END + } + + STAmount const allAvailable{vaultAsset, *assetsAvailable}; + if (assetsWithdrawn != allAvailable) + { + JLOG(j_.error()) // + << "VaultWithdraw: final withdrawal share-value mismatch;" + << " computed=" << assetsWithdrawn.getText() + << " assetsAvailable=" << allAvailable.getText(); + } + assetsWithdrawn = allAvailable; + + // Do not let dust accumulate in the Vault. + assetsTotal = 0; + assetsAvailable = 0; + } + else + { + assetsTotal -= assetsWithdrawn; + assetsAvailable -= assetsWithdrawn; + } view().update(vault); auto const& vaultAccount = vault->at(sfAccount); diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index d8527876f1..bf707afae9 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -6457,6 +6457,604 @@ class Vault_test : public beast::unit_test::Suite runTest(amendments); } + // ----------------------------------------------------------------------- + // Helpers and tests: sole-shareholder / stuck-depositor (XLS-0065 + + // fixCleanup3_2_0). The vault-level withdraw behavior is tested here; + // the loan-protocol setup is incidental. + // ----------------------------------------------------------------------- + + FeatureBitset const all_{test::jtx::testableAmendments()}; + std::string const iouCurrency_{"IOU"}; + + // design doc: + // AssetsAvailable ≈ 3,333.50 + // AssetsTotal ≈ 6,666.50 (3,333.50 cash + 3,333 receivable) + // LossUnrealized = 3,333 + // OutstandingShares = sharesLender (5e9 at IOU scale 1e6) + struct StuckDepositorFixture + { + test::jtx::Account issuer{"issuer"}; + test::jtx::Account lender{"lender"}; + test::jtx::Account bob{"bob"}; + test::jtx::Account borrower{"borrower"}; + std::optional asset; + std::optional vaultKeylet; + uint256 brokerID; + std::optional loanKeylet; + MPTID shareAsset; + std::uint64_t sharesLender = 0; + }; + + static constexpr std::int64_t kStuckFunding = 1'000'000; + static constexpr std::int64_t kStuckDepositorIOU = 1'000'000; + static constexpr std::int64_t kStuckBorrowerIOU = 100'000; + static constexpr std::int64_t kStuckDeposit = 5'000; + static constexpr std::int64_t kStuckPrincipal = 3'333; + static constexpr std::uint32_t kStuckPayInterval = 600; + static constexpr std::uint32_t kStuckPayTotal = 2; + + [[nodiscard]] StuckDepositorFixture + setupStuckDepositor(test::jtx::Env& env) + { + using namespace test::jtx; + + StuckDepositorFixture f; + f.asset = f.issuer[iouCurrency_]; + + env.fund(XRP(kStuckFunding), f.issuer, f.lender, f.bob, f.borrower); + env.close(); + + env(trust(f.lender, (*f.asset)(10'000'000))); + env(trust(f.bob, (*f.asset)(10'000'000))); + env(trust(f.borrower, (*f.asset)(10'000'000))); + env.close(); + + env(pay(f.issuer, f.lender, (*f.asset)(kStuckDepositorIOU))); + env(pay(f.issuer, f.bob, (*f.asset)(kStuckDepositorIOU))); + env(pay(f.issuer, f.borrower, (*f.asset)(kStuckBorrowerIOU))); + env.close(); + + // Vault: Lender creates and seeds it; Bob matches the deposit for a + // clean 50/50 split. + Vault const v{env}; + auto [createTx, vaultKeylet] = v.create({.owner = f.lender, .asset = *f.asset}); + env(createTx); + env.close(); + if (!BEAST_EXPECT(env.le(vaultKeylet))) + return f; + f.vaultKeylet = vaultKeylet; + + env(v.deposit({ + .depositor = f.lender, + .id = vaultKeylet.key, + .amount = (*f.asset)(kStuckDeposit), + }), + Ter(tesSUCCESS)); + env(v.deposit({ + .depositor = f.bob, + .id = vaultKeylet.key, + .amount = (*f.asset)(kStuckDeposit), + }), + Ter(tesSUCCESS)); + env.close(); + + // Loan broker: no cover, no management fee, debt cap 10x principal. + f.brokerID = keylet::loanbroker(f.lender.id(), env.seq(f.lender)).key; + { + using namespace loanBroker; + env(set(f.lender, vaultKeylet.key), + kDebtMaximum((*f.asset)(kStuckPrincipal * 10).value())); + env.close(); + } + + // Loan: 3,333 USD principal, impaired immediately. + auto const sleBroker = env.le(keylet::loanbroker(f.brokerID)); + if (!BEAST_EXPECT(sleBroker)) + return f; + f.loanKeylet = keylet::loan(f.brokerID, sleBroker->at(sfLoanSequence)); + + { + using namespace loan; + env(set(f.borrower, f.brokerID, kStuckPrincipal), + Sig(sfCounterpartySignature, f.lender), + kPaymentTotal(kStuckPayTotal), + kPaymentInterval(kStuckPayInterval), + Fee(env.current()->fees().base * 2), + Ter(tesSUCCESS)); + env.close(); + env(manage(f.lender, f.loanKeylet->key, tfLoanImpair), Ter(tesSUCCESS)); + env.close(); + } + + auto const vaultSle = env.le(vaultKeylet); + if (!BEAST_EXPECT(vaultSle)) + return f; + BEAST_EXPECT(vaultSle->at(sfLossUnrealized) == (*f.asset)(kStuckPrincipal).value()); + + f.shareAsset = vaultSle->at(sfShareMPTID); + + auto const tokenBob = env.le(keylet::mptoken(f.shareAsset, f.bob.id())); + if (!BEAST_EXPECT(tokenBob)) + return f; + std::uint64_t const sharesBob = tokenBob->getFieldU64(sfMPTAmount); + + // Bob (non-sole) exits at the discounted rate. Always succeeds. + STAmount const bobShareAmt{MPTIssue{f.shareAsset}, Number(sharesBob)}; + env(v.withdraw({ + .depositor = f.bob, + .id = vaultKeylet.key, + .amount = bobShareAmt, + }), + Ter(tesSUCCESS)); + env.close(); + + auto const tokenLender = env.le(keylet::mptoken(f.shareAsset, f.lender.id())); + if (!BEAST_EXPECT(tokenLender)) + return f; + f.sharesLender = tokenLender->getFieldU64(sfMPTAmount); + + auto const sleIssuance = env.le(keylet::mptIssuance(f.shareAsset)); + if (!BEAST_EXPECT(sleIssuance)) + return f; + BEAST_EXPECT(sleIssuance->getFieldU64(sfOutstandingAmount) == f.sharesLender); + + auto const vaultAfterBob = env.le(vaultKeylet); + if (!BEAST_EXPECT(vaultAfterBob)) + return f; + // After Bob's exit: loss is unchanged (3,333 receivable), and the + // gap between assetsTotal and assetsAvailable equals exactly that + // receivable. + BEAST_EXPECT(vaultAfterBob->at(sfLossUnrealized) == (*f.asset)(kStuckPrincipal).value()); + BEAST_EXPECT( + vaultAfterBob->at(sfAssetsTotal) - vaultAfterBob->at(sfAssetsAvailable) == + vaultAfterBob->at(sfLossUnrealized)); + + return f; + } + + // Reproduces the worked example from the XLS-0065 design doc. The sole + // remaining shareholder asks (via fixed-asset input) for the vault's + // entire AssetsAvailable. Pre-fix this fails with the zero-sized-vault + // invariant violation. Post-fix the full-price exchange rate burns + // only a portion of the shares, the depositor receives all of + // AssetsAvailable, and the residual shares remain backed by the + // impaired-loan receivable. + void + testWithdrawSoleShareholderFixedAssetExit(FeatureBitset features) + { + using namespace test::jtx; + + bool const withFix = features[fixCleanup3_2_0]; + testcase( + std::string{"Vault withdraw: sole shareholder exits via " + "fixed-asset amount with impaired loan"} + + (withFix ? " (fixCleanup3_2_0)" : " (pre-fix)")); + + Env env(*this, features); + auto const f = setupStuckDepositor(env); + if (!f.vaultKeylet || !f.asset || f.sharesLender == 0) + { + BEAST_EXPECT(false); + return; + } + Keylet const& vaultKey = *f.vaultKeylet; + PrettyAsset const& asset = *f.asset; + + auto const vaultBefore = env.le(vaultKey); + if (!BEAST_EXPECT(vaultBefore)) + return; + Number const availableBefore = vaultBefore->at(sfAssetsAvailable); + Number const totalBefore = vaultBefore->at(sfAssetsTotal); + Number const lossBefore = vaultBefore->at(sfLossUnrealized); + + STAmount const lenderBalanceBefore = env.balance(f.lender, asset); + + // The requested amount differs between feature regimes because + // the two regimes are testing different behaviors: + // + // - Pre-fix: request the full AssetsAvailable (3,333.50). Under + // the discounted formula this would burn every outstanding + // share, hitting the zero-sized-vault invariant. The + // transaction is rejected with tecINVARIANT_FAILED — the + // stuck-depositor bug. + // + // - Post-fix: request a strictly smaller amount (1,000 USD). + // The full-price formula burns only ~30% of the outstanding + // shares; the vault retains the rest, backed by the impaired + // receivable. Requesting *exactly* AssetsAvailable post-fix + // would currently fail with tecINSUFFICIENT_FUNDS due to the + // round-to-nearest used by assetsToSharesWithdraw (the + // recomputed payout can overshoot the request by a few ULPs). + // The "force payout to AssetsAvailable" branch in doApply + // only triggers when every share is burned, which is covered + // by the loan-repayment test. + STAmount const requestAssets = + withFix ? asset(1000).value() : STAmount{asset.raw(), availableBefore}; + Vault const v{env}; + env(v.withdraw({ + .depositor = f.lender, + .id = vaultKey.key, + .amount = requestAssets, + }), + Ter(withFix ? TER{tesSUCCESS} : TER{tecINVARIANT_FAILED})); + env.close(); + + auto const vaultAfter = env.le(vaultKey); + if (!BEAST_EXPECT(vaultAfter)) + return; + auto const issuanceAfter = env.le(keylet::mptIssuance(f.shareAsset)); + if (!BEAST_EXPECT(issuanceAfter)) + return; + + std::uint64_t const sharesAfter = issuanceAfter->getFieldU64(sfOutstandingAmount); + Number const availableAfter = vaultAfter->at(sfAssetsAvailable); + Number const totalAfter = vaultAfter->at(sfAssetsTotal); + Number const lossAfter = vaultAfter->at(sfLossUnrealized); + + if (!withFix) + { + // Pre-fix: rejected — vault state unchanged. + BEAST_EXPECT(sharesAfter == f.sharesLender); + BEAST_EXPECT(availableAfter == availableBefore); + BEAST_EXPECT(totalAfter == totalBefore); + BEAST_EXPECT(lossAfter == lossBefore); + return; + } + + // Post-fix exact-value derivation (fixture: sharesLender=5e9, + // totalBefore=6666.5, request=1000): + // sharesRedeemed = round(sharesLender * request / totalBefore) + // = round(750,018,750.469) = 750,018,750 + // received = totalBefore * sharesRedeemed / sharesLender + // = 999.999999375 (slightly under 1,000 due to + // integer-share rounding) + constexpr std::uint64_t kExpectedSharesRedeemed = 750'018'750; + Number const expectedReceived = + totalBefore * Number(kExpectedSharesRedeemed) / Number(f.sharesLender); + + BEAST_EXPECT(sharesAfter == f.sharesLender - kExpectedSharesRedeemed); + + // LossUnrealized is unchanged: the loan-protocol side is untouched. + BEAST_EXPECT(lossAfter == lossBefore); + + // The entire (total - available) gap is the impaired receivable, + // i.e. equal to lossUnrealized. + BEAST_EXPECT(totalAfter - availableAfter == lossAfter); + + STAmount const lenderBalanceAfter = env.balance(f.lender, asset); + Number const received{lenderBalanceAfter - lenderBalanceBefore}; + BEAST_EXPECT(received == expectedReceived); + + // Conservation: assets removed from the vault equal what the + // depositor received. + BEAST_EXPECT(totalBefore - totalAfter == received); + BEAST_EXPECT(availableBefore - availableAfter == received); + } + + // Sole shareholder attempts to burn ALL outstanding shares via + // fixed-shares input while the vault still holds an impaired + // receivable. Pre-fix this fails with the zero-sized-vault invariant + // violation. Post-fix the full-price rate causes assetsWithdrawn to + // equal assetsTotal, which exceeds assetsAvailable, so the transaction + // is rejected with tecINSUFFICIENT_FUNDS. + void + testWithdrawSoleShareholderFullSharesRejected(FeatureBitset features) + { + using namespace test::jtx; + + bool const withFix = features[fixCleanup3_2_0]; + testcase( + std::string{"Vault withdraw: sole shareholder full-shares " + "burn is rejected while loss outstanding"} + + (withFix ? " (fixCleanup3_2_0)" : " (pre-fix)")); + + Env env(*this, features); + auto const f = setupStuckDepositor(env); + if (!f.vaultKeylet || f.sharesLender == 0) + { + BEAST_EXPECT(false); + return; + } + Keylet const& vaultKey = *f.vaultKeylet; + + auto const vaultBefore = env.le(vaultKey); + if (!BEAST_EXPECT(vaultBefore)) + return; + Number const availableBefore = vaultBefore->at(sfAssetsAvailable); + Number const totalBefore = vaultBefore->at(sfAssetsTotal); + Number const lossBefore = vaultBefore->at(sfLossUnrealized); + + // Fixed-shares input: ask for ALL outstanding shares. + STAmount const shareAmt{MPTIssue{f.shareAsset}, Number(f.sharesLender)}; + Vault const v{env}; + env(v.withdraw({ + .depositor = f.lender, + .id = vaultKey.key, + .amount = shareAmt, + }), + Ter(withFix ? TER{tecINSUFFICIENT_FUNDS} : TER{tecINVARIANT_FAILED})); + env.close(); + + // Either way the transaction was rejected; vault state unchanged. + auto const vaultAfter = env.le(vaultKey); + if (!BEAST_EXPECT(vaultAfter)) + return; + auto const issuanceAfter = env.le(keylet::mptIssuance(f.shareAsset)); + if (!BEAST_EXPECT(issuanceAfter)) + return; + BEAST_EXPECT(issuanceAfter->getFieldU64(sfOutstandingAmount) == f.sharesLender); + BEAST_EXPECT(vaultAfter->at(sfAssetsAvailable) == availableBefore); + BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == totalBefore); + BEAST_EXPECT(vaultAfter->at(sfLossUnrealized) == lossBefore); + } + + // Post-fix end-to-end resolution: after the sole-shareholder partial + // exit, the loan is repaid in full. With unrealized loss cleared and + // all assets back as cash, the depositor can burn all remaining + // shares and fully exit the vault. The final withdrawal hits the + // "force payout to assetsAvailable" branch in doApply. + void + testWithdrawSoleShareholderLoanRepaymentExit() + { + using namespace test::jtx; + using namespace loan; + + testcase( + "Vault withdraw: sole shareholder fully exits after impaired " + "loan is repaid (fixCleanup3_2_0)"); + + Env env(*this, all_ | fixCleanup3_2_0); + auto const f = setupStuckDepositor(env); + if (!f.vaultKeylet || !f.asset || !f.loanKeylet || f.sharesLender == 0) + { + BEAST_EXPECT(false); + return; + } + Keylet const& vaultKey = *f.vaultKeylet; + Keylet const& loanKey = *f.loanKeylet; + PrettyAsset const& asset = *f.asset; + + Vault const v{env}; + + // Sole-shareholder partial exit (see comment in + // testWithdrawSoleShareholderFixedAssetExit for why we request + // less than full AssetsAvailable). + { + STAmount const requestAssets = asset(1000).value(); + env(v.withdraw({ + .depositor = f.lender, + .id = vaultKey.key, + .amount = requestAssets, + }), + Ter(tesSUCCESS)); + env.close(); + } + + // Confirm the "dormant-but-alive" state from the design doc. The + // partial exit burned exactly 750,018,750 shares (see derivation + // in testWithdrawSoleShareholderFixedAssetExit). + auto const tokenAfterExit = env.le(keylet::mptoken(f.shareAsset, f.lender.id())); + if (!BEAST_EXPECT(tokenAfterExit)) + return; + std::uint64_t const retainedShares = tokenAfterExit->getFieldU64(sfMPTAmount); + BEAST_EXPECT(retainedShares == f.sharesLender - 750'018'750); + + // Borrower repays the loan in full (pays more than the outstanding + // total; the loan transactor caps the receivable). + env(pay(f.borrower, loanKey.key, asset(kStuckPrincipal * 2)), Ter(tesSUCCESS)); + env.close(); + + auto const vaultAfterRepay = env.le(vaultKey); + if (!BEAST_EXPECT(vaultAfterRepay)) + return; + // Repayment converts the 3,333 receivable back to cash; assetsTotal + // is unchanged but assetsAvailable jumps by exactly the same amount, + // and lossUnrealized clears to zero. + BEAST_EXPECT(vaultAfterRepay->at(sfLossUnrealized) == beast::kZero); + BEAST_EXPECT(vaultAfterRepay->at(sfAssetsAvailable) == vaultAfterRepay->at(sfAssetsTotal)); + + STAmount const lenderBalanceBeforeFinal = env.balance(f.lender, asset); + Number const availableBeforeFinal = vaultAfterRepay->at(sfAssetsAvailable); + + // Burn all remaining shares — the clean-state preconditions of + // the "final withdrawal" guard are now satisfied. + STAmount const allShares{MPTIssue{f.shareAsset}, Number(retainedShares)}; + env(v.withdraw({ + .depositor = f.lender, + .id = vaultKey.key, + .amount = allShares, + }), + Ter(tesSUCCESS)); + env.close(); + + auto const vaultFinal = env.le(vaultKey); + if (!BEAST_EXPECT(vaultFinal)) + return; + auto const issuanceFinal = env.le(keylet::mptIssuance(f.shareAsset)); + if (!BEAST_EXPECT(issuanceFinal)) + return; + + // Zero-sized vault invariant satisfied: 0 shares, 0 assets. + BEAST_EXPECT(issuanceFinal->getFieldU64(sfOutstandingAmount) == 0); + BEAST_EXPECT(vaultFinal->at(sfAssetsTotal) == beast::kZero); + BEAST_EXPECT(vaultFinal->at(sfAssetsAvailable) == beast::kZero); + BEAST_EXPECT(vaultFinal->at(sfLossUnrealized) == beast::kZero); + + // The final payout equals exactly the AssetsAvailable that + // existed before the call (the "force payout" branch). + STAmount const lenderBalanceAfter = env.balance(f.lender, asset); + Number const finalReceived{lenderBalanceAfter - lenderBalanceBeforeFinal}; + BEAST_EXPECT(finalReceived == availableBeforeFinal); + } + + // Clean-state regression: with no impaired loan, a sole shareholder + // burning all their shares fully empties the vault under both the + // pre-fix and post-fix code paths. Confirms the new logic doesn't + // break the existing happy-path close-out. + void + testWithdrawSoleShareholderCleanVaultUnaffected(FeatureBitset features) + { + using namespace test::jtx; + + bool const withFix = features[fixCleanup3_2_0]; + testcase( + std::string{"Vault withdraw: sole shareholder clean-state " + "close-out unchanged"} + + (withFix ? " (fixCleanup3_2_0)" : " (pre-fix)")); + + Env env(*this, features); + + Account const issuer{"issuer"}; + Account const lender{"lender"}; + + env.fund(XRP(kStuckFunding), issuer, lender); + env.close(); + + PrettyAsset const asset = issuer[iouCurrency_]; + env(trust(lender, asset(10'000'000))); + env.close(); + env(pay(issuer, lender, asset(kStuckDepositorIOU))); + env.close(); + + // Sole shareholder of a clean vault — no loan broker needed. + Vault const v{env}; + auto [createTx, vaultKeylet] = v.create({.owner = lender, .asset = asset}); + env(createTx); + env.close(); + + env(v.deposit({ + .depositor = lender, + .id = vaultKeylet.key, + .amount = asset(kStuckDeposit), + }), + Ter(tesSUCCESS)); + env.close(); + + auto const vaultBefore = env.le(vaultKeylet); + if (!BEAST_EXPECT(vaultBefore)) + return; + auto const shareAsset = vaultBefore->at(sfShareMPTID); + auto const tokenLender = env.le(keylet::mptoken(shareAsset, lender.id())); + if (!BEAST_EXPECT(tokenLender)) + return; + std::uint64_t const sharesLender = tokenLender->getFieldU64(sfMPTAmount); + + // Sole shareholder, no loans, no loss. Burn everything. + STAmount const allShares{MPTIssue{shareAsset}, Number(sharesLender)}; + env(v.withdraw({ + .depositor = lender, + .id = vaultKeylet.key, + .amount = allShares, + }), + Ter(tesSUCCESS)); + env.close(); + + auto const vaultFinal = env.le(vaultKeylet); + if (!BEAST_EXPECT(vaultFinal)) + return; + auto const issuanceFinal = env.le(keylet::mptIssuance(shareAsset)); + if (!BEAST_EXPECT(issuanceFinal)) + return; + BEAST_EXPECT(issuanceFinal->getFieldU64(sfOutstandingAmount) == 0); + BEAST_EXPECT(vaultFinal->at(sfAssetsTotal) == beast::kZero); + BEAST_EXPECT(vaultFinal->at(sfAssetsAvailable) == beast::kZero); + BEAST_EXPECT(vaultFinal->at(sfLossUnrealized) == beast::kZero); + + // (Pre-fix path takes the regular code path; post-fix path enters + // the new final-withdrawal guard, which forces payout to exactly + // assetsAvailable. Either way the result is identical for a clean + // vault.) + (void)withFix; + } + + // Sole shareholder in an impaired vault redeems a *partial* count of + // shares via fixed-shares input. Pre-fix the discounted formula is + // used; post-fix the full-price formula is used (waiveUnrealizedLoss + // = Yes). The relative payout therefore differs, and post-fix the + // depositor recovers proportionally more of the residual cash for + // the shares burned. In both cases the vault is left in a valid + // (non-empty) state. + void + testWithdrawSoleShareholderPartialFixedSharesUsesFullPrice() + { + using namespace test::jtx; + + testcase( + "Vault withdraw: sole-shareholder partial fixed-shares uses " + "full-price rate (fixCleanup3_2_0)"); + + Env env(*this, all_ | fixCleanup3_2_0); + auto const f = setupStuckDepositor(env); + if (!f.vaultKeylet || !f.asset || f.sharesLender == 0) + { + BEAST_EXPECT(false); + return; + } + Keylet const& vaultKey = *f.vaultKeylet; + PrettyAsset const& asset = *f.asset; + + auto const vaultBefore = env.le(vaultKey); + if (!BEAST_EXPECT(vaultBefore)) + return; + Number const totalBefore = vaultBefore->at(sfAssetsTotal); + Number const availableBefore = vaultBefore->at(sfAssetsAvailable); + Number const lossBefore = vaultBefore->at(sfLossUnrealized); + + // Burn exactly half of the outstanding shares. + std::uint64_t const halfShares = f.sharesLender / 2; + STAmount const halfAmt{MPTIssue{f.shareAsset}, Number(halfShares)}; + + STAmount const lenderBalanceBefore = env.balance(f.lender, asset); + + Vault const v{env}; + env(v.withdraw({ + .depositor = f.lender, + .id = vaultKey.key, + .amount = halfAmt, + }), + Ter(tesSUCCESS)); + env.close(); + + // Expected payout under the full-price formula: + // assets = totalBefore * halfShares / sharesLender + // which (with halfShares == sharesLender/2) is roughly + // totalBefore / 2. + STAmount const lenderBalanceAfter = env.balance(f.lender, asset); + Number const received{lenderBalanceAfter - lenderBalanceBefore}; + Number const expected = totalBefore * Number(halfShares) / Number(f.sharesLender); + BEAST_EXPECT(received == expected); + + // The full-price payout exceeds the discounted formula by exactly + // lossBefore * halfShares / sharesLender — that's the whole point + // of the waive. + Number const discounted = + (totalBefore - lossBefore) * Number(halfShares) / Number(f.sharesLender); + Number const expectedDelta = lossBefore * Number(halfShares) / Number(f.sharesLender); + BEAST_EXPECT(received - discounted == expectedDelta); + + auto const vaultAfter = env.le(vaultKey); + if (!BEAST_EXPECT(vaultAfter)) + return; + auto const issuanceAfter = env.le(keylet::mptIssuance(f.shareAsset)); + if (!BEAST_EXPECT(issuanceAfter)) + return; + + // Vault remains valid: half the shares remain, lossUnrealized + // is untouched, and the entire (total - available) gap is still + // the impaired receivable. + BEAST_EXPECT( + issuanceAfter->getFieldU64(sfOutstandingAmount) == f.sharesLender - halfShares); + BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == totalBefore - received); + BEAST_EXPECT(vaultAfter->at(sfLossUnrealized) == lossBefore); + BEAST_EXPECT( + vaultAfter->at(sfAssetsTotal) - vaultAfter->at(sfAssetsAvailable) == + vaultAfter->at(sfLossUnrealized)); + + // Conservation: vault delta matches the depositor's gain. + BEAST_EXPECT(totalBefore - vaultAfter->at(sfAssetsTotal) == received); + BEAST_EXPECT(availableBefore - vaultAfter->at(sfAssetsAvailable) == received); + } + // Bug: DeltaInfo::makeDelta uses max(scale(after), scale(before)) for the // sfAssetsTotal and sfAssetsAvailable deltas, and visitEntry applies the // same max() for the vault pseudo-account RippleState. When @@ -7449,6 +8047,16 @@ public: testAssetsMaximum(); testBug6LimitBypassWithShares(); testRemoveEmptyHoldingLockedAmount(); + + testWithdrawSoleShareholderFixedAssetExit(all_ - fixCleanup3_2_0); + testWithdrawSoleShareholderFixedAssetExit(all_); + testWithdrawSoleShareholderFullSharesRejected(all_ - fixCleanup3_2_0); + testWithdrawSoleShareholderFullSharesRejected(all_); + testWithdrawSoleShareholderCleanVaultUnaffected(all_ - fixCleanup3_2_0); + testWithdrawSoleShareholderCleanVaultUnaffected(all_); + testWithdrawSoleShareholderPartialFixedSharesUsesFullPrice(); + testWithdrawSoleShareholderLoanRepaymentExit(); + testReferenceHolding(); testHoldingDeletionBlocked(); } From 23d08128278a7dcd80cdbd718df8c069a8b25586 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 26 May 2026 19:28:23 +0100 Subject: [PATCH 029/135] style: Use shfmt instead of bashate (#7326) --- .github/scripts/rename/binary.sh | 2 +- .github/scripts/rename/cmake.sh | 12 ++-- .github/scripts/rename/config.sh | 2 +- .github/scripts/rename/copyright.sh | 24 ++++---- .github/scripts/rename/definitions.sh | 2 +- .github/scripts/rename/docs.sh | 2 +- .github/scripts/rename/namespace.sh | 2 +- .pre-commit-config.yaml | 20 +++---- bin/git/setup-upstreams.sh | 24 ++++---- bin/git/squash-branches.sh | 14 ++--- bin/git/update-version.sh | 20 +++---- cspell.config.yaml | 1 + docker/check-sanitizers.sh | 14 +++-- package/build_pkg.sh | 81 +++++++++++++++++---------- package/shared/update-xrpld | 4 +- 15 files changed, 127 insertions(+), 97 deletions(-) diff --git a/.github/scripts/rename/binary.sh b/.github/scripts/rename/binary.sh index cdce6db4ba..89d884538c 100755 --- a/.github/scripts/rename/binary.sh +++ b/.github/scripts/rename/binary.sh @@ -6,7 +6,7 @@ set -e # On MacOS, ensure that GNU sed is installed and available as `gsed`. SED_COMMAND=sed if [[ "${OSTYPE}" == 'darwin'* ]]; then - if ! command -v gsed &> /dev/null; then + if ! command -v gsed &>/dev/null; then echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." exit 1 fi diff --git a/.github/scripts/rename/cmake.sh b/.github/scripts/rename/cmake.sh index 9c91e8f277..28bf777fed 100755 --- a/.github/scripts/rename/cmake.sh +++ b/.github/scripts/rename/cmake.sh @@ -8,12 +8,12 @@ set -e SED_COMMAND=sed HEAD_COMMAND=head if [[ "${OSTYPE}" == 'darwin'* ]]; then - if ! command -v gsed &> /dev/null; then + if ! command -v gsed &>/dev/null; then echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." exit 1 fi SED_COMMAND=gsed - if ! command -v ghead &> /dev/null; then + if ! command -v ghead &>/dev/null; then echo "Error: ghead is not installed. Please install it using 'brew install coreutils'." exit 1 fi @@ -74,10 +74,10 @@ if grep -q '"xrpld"' cmake/XrplCore.cmake; then # The script has been rerun, so just restore the name of the binary. ${SED_COMMAND} -i 's/"xrpld"/"rippled"/' cmake/XrplCore.cmake elif ! grep -q '"rippled"' cmake/XrplCore.cmake; then - ${HEAD_COMMAND} -n -1 cmake/XrplCore.cmake > cmake.tmp - echo ' # For the time being, we will keep the name of the binary as it was.' >> cmake.tmp - echo ' set_target_properties(xrpld PROPERTIES OUTPUT_NAME "rippled")' >> cmake.tmp - tail -1 cmake/XrplCore.cmake >> cmake.tmp + ${HEAD_COMMAND} -n -1 cmake/XrplCore.cmake >cmake.tmp + echo ' # For the time being, we will keep the name of the binary as it was.' >>cmake.tmp + echo ' set_target_properties(xrpld PROPERTIES OUTPUT_NAME "rippled")' >>cmake.tmp + tail -1 cmake/XrplCore.cmake >>cmake.tmp mv cmake.tmp cmake/XrplCore.cmake fi diff --git a/.github/scripts/rename/config.sh b/.github/scripts/rename/config.sh index 81edcc73d6..ac9debb154 100755 --- a/.github/scripts/rename/config.sh +++ b/.github/scripts/rename/config.sh @@ -6,7 +6,7 @@ set -e # On MacOS, ensure that GNU sed is installed and available as `gsed`. SED_COMMAND=sed if [[ "${OSTYPE}" == 'darwin'* ]]; then - if ! command -v gsed &> /dev/null; then + if ! command -v gsed &>/dev/null; then echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." exit 1 fi diff --git a/.github/scripts/rename/copyright.sh b/.github/scripts/rename/copyright.sh index 9ebdad1e89..09bc5a8926 100755 --- a/.github/scripts/rename/copyright.sh +++ b/.github/scripts/rename/copyright.sh @@ -6,7 +6,7 @@ set -e # On MacOS, ensure that GNU sed is installed and available as `gsed`. SED_COMMAND=sed if [[ "${OSTYPE}" == 'darwin'* ]]; then - if ! command -v gsed &> /dev/null; then + if ! command -v gsed &>/dev/null; then echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." exit 1 fi @@ -62,37 +62,37 @@ done # restoring the verbiage that is already present in LICENSE.md. Ensure that if # the script is run multiple times, duplicate notices are not added. if ! grep -q 'Raw Material Software' include/xrpl/beast/core/CurrentThreadName.h; then - echo -e "// Portions of this file are from JUCE (http://www.juce.com).\n// Copyright (c) 2013 - Raw Material Software Ltd.\n// Please visit http://www.juce.com\n\n$(cat include/xrpl/beast/core/CurrentThreadName.h)" > include/xrpl/beast/core/CurrentThreadName.h + echo -e "// Portions of this file are from JUCE (http://www.juce.com).\n// Copyright (c) 2013 - Raw Material Software Ltd.\n// Please visit http://www.juce.com\n\n$(cat include/xrpl/beast/core/CurrentThreadName.h)" >include/xrpl/beast/core/CurrentThreadName.h fi if ! grep -q 'Dev Null' src/test/app/NetworkID_test.cpp; then - echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/NetworkID_test.cpp)" > src/test/app/NetworkID_test.cpp + echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/NetworkID_test.cpp)" >src/test/app/NetworkID_test.cpp fi if ! grep -q 'Dev Null' src/test/app/tx/apply_test.cpp; then - echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/tx/apply_test.cpp)" > src/test/app/tx/apply_test.cpp + echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/tx/apply_test.cpp)" >src/test/app/tx/apply_test.cpp fi if ! grep -q 'Dev Null' src/test/rpc/ManifestRPC_test.cpp; then - echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ManifestRPC_test.cpp)" > src/test/rpc/ManifestRPC_test.cpp + echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ManifestRPC_test.cpp)" >src/test/rpc/ManifestRPC_test.cpp fi if ! grep -q 'Dev Null' src/test/rpc/ValidatorInfo_test.cpp; then - echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ValidatorInfo_test.cpp)" > src/test/rpc/ValidatorInfo_test.cpp + echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ValidatorInfo_test.cpp)" >src/test/rpc/ValidatorInfo_test.cpp fi if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/server_info/Manifest.cpp; then - echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/server_info/Manifest.cpp)" > src/xrpld/rpc/handlers/server_info/Manifest.cpp + echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/server_info/Manifest.cpp)" >src/xrpld/rpc/handlers/server_info/Manifest.cpp fi if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp; then - echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp)" > src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp + echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp)" >src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp fi if ! grep -q 'Bougalis' include/xrpl/basics/SlabAllocator.h; then - echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis \n\n$(cat include/xrpl/basics/SlabAllocator.h)" > include/xrpl/basics/SlabAllocator.h # cspell: ignore Nikolaos Bougalis nikb + echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis \n\n$(cat include/xrpl/basics/SlabAllocator.h)" >include/xrpl/basics/SlabAllocator.h # cspell: ignore Nikolaos Bougalis nikb fi if ! grep -q 'Bougalis' include/xrpl/basics/spinlock.h; then - echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis \n\n$(cat include/xrpl/basics/spinlock.h)" > include/xrpl/basics/spinlock.h # cspell: ignore Nikolaos Bougalis nikb + echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis \n\n$(cat include/xrpl/basics/spinlock.h)" >include/xrpl/basics/spinlock.h # cspell: ignore Nikolaos Bougalis nikb fi if ! grep -q 'Bougalis' include/xrpl/basics/tagged_integer.h; then - echo -e "// Copyright (c) 2014, Nikolaos D. Bougalis \n\n$(cat include/xrpl/basics/tagged_integer.h)" > include/xrpl/basics/tagged_integer.h # cspell: ignore Nikolaos Bougalis nikb + echo -e "// Copyright (c) 2014, Nikolaos D. Bougalis \n\n$(cat include/xrpl/basics/tagged_integer.h)" >include/xrpl/basics/tagged_integer.h # cspell: ignore Nikolaos Bougalis nikb fi if ! grep -q 'Ritchford' include/xrpl/beast/utility/Zero.h; then - echo -e "// Copyright (c) 2014, Tom Ritchford \n\n$(cat include/xrpl/beast/utility/Zero.h)" > include/xrpl/beast/utility/Zero.h # cspell: ignore Ritchford + echo -e "// Copyright (c) 2014, Tom Ritchford \n\n$(cat include/xrpl/beast/utility/Zero.h)" >include/xrpl/beast/utility/Zero.h # cspell: ignore Ritchford fi # Restore newlines and tabs in string literals in the affected file. diff --git a/.github/scripts/rename/definitions.sh b/.github/scripts/rename/definitions.sh index 5e004afe39..daa5d01e80 100755 --- a/.github/scripts/rename/definitions.sh +++ b/.github/scripts/rename/definitions.sh @@ -6,7 +6,7 @@ set -e # On MacOS, ensure that GNU sed is installed and available as `gsed`. SED_COMMAND=sed if [[ "${OSTYPE}" == 'darwin'* ]]; then - if ! command -v gsed &> /dev/null; then + if ! command -v gsed &>/dev/null; then echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." exit 1 fi diff --git a/.github/scripts/rename/docs.sh b/.github/scripts/rename/docs.sh index 8b7a362405..9f080b06e5 100755 --- a/.github/scripts/rename/docs.sh +++ b/.github/scripts/rename/docs.sh @@ -6,7 +6,7 @@ set -e # On MacOS, ensure that GNU sed is installed and available as `gsed`. SED_COMMAND=sed if [[ "${OSTYPE}" == 'darwin'* ]]; then - if ! command -v gsed &> /dev/null; then + if ! command -v gsed &>/dev/null; then echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." exit 1 fi diff --git a/.github/scripts/rename/namespace.sh b/.github/scripts/rename/namespace.sh index aba193b0cf..bb186bc8bc 100755 --- a/.github/scripts/rename/namespace.sh +++ b/.github/scripts/rename/namespace.sh @@ -6,7 +6,7 @@ set -e # On MacOS, ensure that GNU sed is installed and available as `gsed`. SED_COMMAND=sed if [[ "${OSTYPE}" == 'darwin'* ]]; then - if ! command -v gsed &> /dev/null; then + if ! command -v gsed &>/dev/null; then echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." exit 1 fi diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1313ad567c..23441c9dde 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,37 +37,37 @@ repos: exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/ - repo: https://github.com/pre-commit/mirrors-clang-format - rev: cd481d7b0bfb5c7b3090c21846317f9a8262e891 # frozen: v22.1.0 + rev: dd18dad857d6133e90bbe478f4f2f22ec0030269 # frozen: v22.1.5 hooks: - id: clang-format args: [--style=file] "types_or": [c++, c, proto] exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/ - - repo: https://github.com/BlankSpruce/gersemi - rev: 0.26.0 + - repo: https://github.com/BlankSpruce/gersemi-pre-commit + rev: faadd6a9d852369ca94f4d15b2404c967ba8cb01 # frozen: 0.27.6 hooks: - id: gersemi - repo: https://github.com/rbubley/mirrors-prettier - rev: c2bc67fe8f8f549cc489e00ba8b45aa18ee713b1 # frozen: v3.8.1 + rev: 515f543f5718ebfd6ce22e16708bb32c68ff96e1 # frozen: v3.8.3 hooks: - id: prettier args: [--end-of-line=auto] - repo: https://github.com/psf/black-pre-commit-mirror - rev: ea488cebbfd88a5f50b8bd95d5c829d0bb76feb8 # frozen: 26.1.0 + rev: 4160603246a6b365d4a2af661c6d71b0a0f50478 # frozen: 26.5.1 hooks: - id: black - - repo: https://github.com/openstack/bashate - rev: 5798d24d571676fc407e81df574c1ef57b520f23 # frozen: 2.1.1 + - repo: https://github.com/scop/pre-commit-shfmt + rev: 05c1426671b9237fb5e1444dd63aa5731bec0dfb # frozen: v3.13.1-1 hooks: - - id: bashate - args: ["--ignore=E006"] + - id: shfmt + args: [--write, --indent=4, --case-indent=true] - repo: https://github.com/streetsidesoftware/cspell-cli - rev: a42085ade523f591dca134379a595e7859986445 # frozen: v9.7.0 + rev: 4643f154907327ee0a2c7038f0296e0dd77d9776 # frozen: v10.0.0 hooks: - id: cspell # Spell check changed files exclude: | diff --git a/bin/git/setup-upstreams.sh b/bin/git/setup-upstreams.sh index 57c3f935f9..97c84f5507 100755 --- a/bin/git/setup-upstreams.sh +++ b/bin/git/setup-upstreams.sh @@ -1,8 +1,8 @@ #!/bin/bash if [[ $# -ne 1 || "$1" == "--help" || "$1" == "-h" ]]; then - name=$( basename $0 ) - cat <<- USAGE + name=$(basename $0) + cat <<-USAGE Usage: $name Where is the Github username of the upstream repo. e.g. XRPLF @@ -14,7 +14,7 @@ fi shift user="$1" # Get the origin URL. Expect it be an SSH-style URL -origin=$( git remote get-url origin ) +origin=$(git remote get-url origin) if [[ "${origin}" == "" ]]; then echo Invalid origin remote >&2 exit 1 @@ -22,11 +22,11 @@ fi # echo "Origin: ${origin}" # Parse the origin ifs_orig="${IFS}" -IFS=':' read remote originpath <<< "${origin}" +IFS=':' read remote originpath <<<"${origin}" # echo "Remote: ${remote}, Originpath: ${originpath}" -IFS='@' read sshuser server <<< "${remote}" +IFS='@' read sshuser server <<<"${remote}" # echo "SSHUser: ${sshuser}, Server: ${server}" -IFS='/' read originuser repo <<< "${originpath}" +IFS='/' read originuser repo <<<"${originpath}" # echo "Originuser: ${originuser}, Repo: ${repo}" if [[ "${sshuser}" == "" || "${server}" == "" || "${originuser}" == "" || "${repo}" == "" ]]; then echo "Can't parse origin URL: ${origin}" >&2 @@ -35,9 +35,9 @@ fi upstream="https://${server}/${user}/${repo}" upstreampush="${remote}:${user}/${repo}" upstreamgroup="upstream upstream-push" -current=$( git remote get-url upstream 2>/dev/null ) -currentpush=$( git remote get-url upstream-push 2>/dev/null ) -currentgroup=$( git config remotes.upstreams ) +current=$(git remote get-url upstream 2>/dev/null) +currentpush=$(git remote get-url upstream-push 2>/dev/null) +currentgroup=$(git config remotes.upstreams) if [[ "${current}" == "${upstream}" ]]; then echo "Upstream already set up correctly. Skip" elif [[ -n "${current}" && "${current}" != "${upstream}" && "${current}" != "${upstreampush}" ]]; then @@ -45,9 +45,9 @@ elif [[ -n "${current}" && "${current}" != "${upstream}" && "${current}" != "${u else if [[ "${current}" == "${upstreampush}" ]]; then echo "Upstream set to dangerous push URL. Update." - _run git remote rename upstream upstream-push || \ - _run git remote remove upstream - currentpush=$( git remote get-url upstream-push 2>/dev/null ) + _run git remote rename upstream upstream-push || + _run git remote remove upstream + currentpush=$(git remote get-url upstream-push 2>/dev/null) fi _run git remote add upstream "${upstream}" fi diff --git a/bin/git/squash-branches.sh b/bin/git/squash-branches.sh index eb4aefe23c..ba63f9c148 100755 --- a/bin/git/squash-branches.sh +++ b/bin/git/squash-branches.sh @@ -1,8 +1,8 @@ #!/bin/bash if [[ $# -lt 3 || "$1" == "--help" || "$1" = "-h" ]]; then - name=$( basename $0 ) - cat <<- USAGE + name=$(basename $0) + cat <<-USAGE Usage: $name workbranch base/branch user/branch [user/branch [...]] * workbranch will be created locally from base/branch @@ -16,7 +16,7 @@ fi work="$1" shift -branches=( $( echo "${@}" | sed "s/:/\//" ) ) +branches=($(echo "${@}" | sed "s/:/\//")) base="${branches[0]}" unset branches[0] @@ -24,10 +24,10 @@ set -e users=() for b in "${branches[@]}"; do - users+=( $( echo $b | cut -d/ -f1 ) ) + users+=($(echo $b | cut -d/ -f1)) done -users=( $( printf '%s\n' "${users[@]}" | sort -u ) ) +users=($(printf '%s\n' "${users[@]}" | sort -u)) git fetch --multiple upstreams "${users[@]}" git checkout -B "$work" --no-track "$base" @@ -40,7 +40,7 @@ done # Make sure the commits look right git log --show-signature "$base..HEAD" -parts=( $( echo $base | sed "s/\// /" ) ) +parts=($(echo $base | sed "s/\// /")) repo="${parts[0]}" b="${parts[1]}" push=$repo @@ -50,7 +50,7 @@ fi if [[ "$repo" == "upstream" ]]; then repo="upstreams" fi -cat << PUSH +cat </dev/null ) || true +push=$(git rev-parse --abbrev-ref --symbolic-full-name '@{push}' \ + 2>/dev/null) || true if [[ "${push}" != "" ]]; then echo "Warning: ${push} may already exist." fi -build=$( find -name BuildInfo.cpp ) -sed 's/\(^.*versionString =\).*$/\1 "'${version}'"/' ${build} > version.cpp && \ -diff "${build}" version.cpp && exit 1 || \ -mv -vi version.cpp ${build} +build=$(find -name BuildInfo.cpp) +sed 's/\(^.*versionString =\).*$/\1 "'${version}'"/' ${build} >version.cpp && + diff "${build}" version.cpp && exit 1 || + mv -vi version.cpp ${build} git diff @@ -47,7 +47,7 @@ git commit -S -m "Set version to ${version}" git log --oneline --first-parent ${base}^.. -cat << PUSH +cat <&2; exit 1 ;; + *) + echo "Unsupported arch: $(uname -m)" >&2 + exit 1 + ;; esac declare -A sanitize=( @@ -35,8 +38,11 @@ for compiler in g++ clang++; do echo "=== Run ${name}-${compiler} ===" output=$("$bin" 2>&1) || true echo "$output" - echo "$output" | grep -q "${expect[$name]}" \ - || { echo "expected '${expect[$name]}' from $bin"; exit 1; } + echo "$output" | grep -q "${expect[$name]}" || + { + echo "expected '${expect[$name]}' from $bin" + exit 1 + } rm -f "$bin" done done diff --git a/package/build_pkg.sh b/package/build_pkg.sh index adac2dc169..f2c2c63c12 100755 --- a/package/build_pkg.sh +++ b/package/build_pkg.sh @@ -36,12 +36,35 @@ SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-}" while [[ $# -gt 0 ]]; do case "$1" in - --src-dir) need_arg "$@"; SRC_DIR="$2"; shift 2 ;; - --build-dir) need_arg "$@"; BUILD_DIR="$2"; shift 2 ;; - --pkg-version) need_arg "$@"; PKG_VERSION="$2"; shift 2 ;; - --pkg-release) need_arg "$@"; PKG_RELEASE="$2"; shift 2 ;; - --source-date-epoch) need_arg "$@"; SOURCE_DATE_EPOCH="$2"; shift 2 ;; - -h|--help) usage; exit 0 ;; + --src-dir) + need_arg "$@" + SRC_DIR="$2" + shift 2 + ;; + --build-dir) + need_arg "$@" + BUILD_DIR="$2" + shift 2 + ;; + --pkg-version) + need_arg "$@" + PKG_VERSION="$2" + shift 2 + ;; + --pkg-release) + need_arg "$@" + PKG_RELEASE="$2" + shift 2 + ;; + --source-date-epoch) + need_arg "$@" + SOURCE_DATE_EPOCH="$2" + shift 2 + ;; + -h | --help) + usage + exit 0 + ;; *) echo "Unknown argument: $1" >&2 usage >&2 @@ -109,20 +132,20 @@ stage_common() { local dest="$1" mkdir -p "${dest}" - cp "${BUILD_DIR}/xrpld" "${dest}/xrpld" - cp "${SRC_DIR}/cfg/xrpld-example.cfg" "${dest}/xrpld.cfg" - cp "${SRC_DIR}/cfg/validators-example.txt" "${dest}/validators.txt" - cp "${SRC_DIR}/LICENSE.md" "${dest}/LICENSE.md" - cp "${SRC_DIR}/README.md" "${dest}/README.md" + cp "${BUILD_DIR}/xrpld" "${dest}/xrpld" + cp "${SRC_DIR}/cfg/xrpld-example.cfg" "${dest}/xrpld.cfg" + cp "${SRC_DIR}/cfg/validators-example.txt" "${dest}/validators.txt" + cp "${SRC_DIR}/LICENSE.md" "${dest}/LICENSE.md" + cp "${SRC_DIR}/README.md" "${dest}/README.md" - cp "${SHARED}/xrpld.service" "${dest}/xrpld.service" - cp "${SHARED}/xrpld.sysusers" "${dest}/xrpld.sysusers" - cp "${SHARED}/xrpld.tmpfiles" "${dest}/xrpld.tmpfiles" - cp "${SHARED}/xrpld.logrotate" "${dest}/xrpld.logrotate" - cp "${SHARED}/update-xrpld" "${dest}/update-xrpld" - cp "${SHARED}/update-xrpld.service" "${dest}/update-xrpld.service" - cp "${SHARED}/update-xrpld.timer" "${dest}/update-xrpld.timer" - cp "${SHARED}/50-xrpld.preset" "${dest}/50-xrpld.preset" + cp "${SHARED}/xrpld.service" "${dest}/xrpld.service" + cp "${SHARED}/xrpld.sysusers" "${dest}/xrpld.sysusers" + cp "${SHARED}/xrpld.tmpfiles" "${dest}/xrpld.tmpfiles" + cp "${SHARED}/xrpld.logrotate" "${dest}/xrpld.logrotate" + cp "${SHARED}/update-xrpld" "${dest}/update-xrpld" + cp "${SHARED}/update-xrpld.service" "${dest}/update-xrpld.service" + cp "${SHARED}/update-xrpld.timer" "${dest}/update-xrpld.timer" + cp "${SHARED}/50-xrpld.preset" "${dest}/50-xrpld.preset" } build_rpm() { @@ -159,12 +182,12 @@ build_deb() { cp -r "${DEBIAN_DIR}" "${staging}/debian" # Debhelper auto-discovers these only from debian/. - cp "${staging}/xrpld.service" "${staging}/debian/xrpld.service" - cp "${staging}/xrpld.sysusers" "${staging}/debian/xrpld.sysusers" - cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles" - cp "${staging}/xrpld.logrotate" "${staging}/debian/xrpld.logrotate" + cp "${staging}/xrpld.service" "${staging}/debian/xrpld.service" + cp "${staging}/xrpld.sysusers" "${staging}/debian/xrpld.sysusers" + cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles" + cp "${staging}/xrpld.logrotate" "${staging}/debian/xrpld.logrotate" cp "${staging}/update-xrpld.service" "${staging}/debian/xrpld.update-xrpld.service" - cp "${staging}/update-xrpld.timer" "${staging}/debian/xrpld.update-xrpld.timer" + cp "${staging}/update-xrpld.timer" "${staging}/debian/xrpld.update-xrpld.timer" # Debian '~' marks a pre-release; 3.2.0~b1 sorts before 3.2.0. local deb_full_version="${VER_BASE}${VER_SUFFIX:+~${VER_SUFFIX}}-${PKG_RELEASE}" @@ -175,12 +198,12 @@ build_deb() { # b, rc -> unstable (pre-release) local deb_distribution case "${VER_SUFFIX}" in - "") deb_distribution="stable" ;; - b0) deb_distribution="develop" ;; - *) deb_distribution="unstable" ;; + "") deb_distribution="stable" ;; + b0) deb_distribution="develop" ;; + *) deb_distribution="unstable" ;; esac - cat > "${staging}/debian/changelog" <"${staging}/debian/changelog" < Date: Tue, 26 May 2026 19:35:38 +0100 Subject: [PATCH 030/135] chore: Pin Python packages for codegen using uv (#7329) --- cmake/scripts/codegen/requirements.in | 13 +++ cmake/scripts/codegen/requirements.txt | 118 ++++++++++++++++++++++--- 2 files changed, 118 insertions(+), 13 deletions(-) create mode 100644 cmake/scripts/codegen/requirements.in diff --git a/cmake/scripts/codegen/requirements.in b/cmake/scripts/codegen/requirements.in new file mode 100644 index 0000000000..d799fd60fd --- /dev/null +++ b/cmake/scripts/codegen/requirements.in @@ -0,0 +1,13 @@ +# Python dependencies for XRP Ledger code generation scripts +# +# These packages are required to run the code generation scripts that +# parse macro files and generate C++ wrapper classes. + +# C preprocessor for Python - used to preprocess macro files +pcpp>=1.30 + +# Parser combinator library - used to parse the macro DSL +pyparsing>=3.0.0 + +# Template engine - used to generate C++ code from templates +Mako>=1.2.2 diff --git a/cmake/scripts/codegen/requirements.txt b/cmake/scripts/codegen/requirements.txt index d799fd60fd..ff37548c7b 100644 --- a/cmake/scripts/codegen/requirements.txt +++ b/cmake/scripts/codegen/requirements.txt @@ -1,13 +1,105 @@ -# Python dependencies for XRP Ledger code generation scripts -# -# These packages are required to run the code generation scripts that -# parse macro files and generate C++ wrapper classes. - -# C preprocessor for Python - used to preprocess macro files -pcpp>=1.30 - -# Parser combinator library - used to parse the macro DSL -pyparsing>=3.0.0 - -# Template engine - used to generate C++ code from templates -Mako>=1.2.2 +# This file was autogenerated by uv via the following command: +# uv pip compile requirements.in --generate-hashes --output-file requirements.txt +mako==1.3.12 \ + --hash=sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9 \ + --hash=sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a + # via -r requirements.in +markupsafe==3.0.3 \ + --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \ + --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \ + --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \ + --hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \ + --hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \ + --hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \ + --hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \ + --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \ + --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \ + --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \ + --hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \ + --hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \ + --hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \ + --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \ + --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \ + --hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \ + --hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \ + --hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \ + --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \ + --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \ + --hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \ + --hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \ + --hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \ + --hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \ + --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \ + --hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \ + --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \ + --hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \ + --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \ + --hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \ + --hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \ + --hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \ + --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \ + --hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \ + --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \ + --hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \ + --hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \ + --hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \ + --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \ + --hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \ + --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \ + --hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \ + --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \ + --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \ + --hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \ + --hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \ + --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \ + --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \ + --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \ + --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \ + --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \ + --hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \ + --hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \ + --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \ + --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \ + --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \ + --hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \ + --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \ + --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \ + --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \ + --hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \ + --hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \ + --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \ + --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \ + --hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \ + --hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \ + --hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \ + --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \ + --hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \ + --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \ + --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \ + --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \ + --hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \ + --hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \ + --hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \ + --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \ + --hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \ + --hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \ + --hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \ + --hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \ + --hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \ + --hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \ + --hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \ + --hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \ + --hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \ + --hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \ + --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \ + --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \ + --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50 + # via mako +pcpp==1.30 \ + --hash=sha256:05fe08292b6da57f385001c891a87f40d6aa7f46787b03e8ba326d20a3297c6e \ + --hash=sha256:5af9fbce55f136d7931ae915fae03c34030a3b36c496e72d9636cedc8e2543a1 + # via -r requirements.in +pyparsing==3.3.2 \ + --hash=sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d \ + --hash=sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc + # via -r requirements.in From 85af406a0ff234f6a571da46faee7551d254b6f6 Mon Sep 17 00:00:00 2001 From: Andrzej Budzanowski Date: Tue, 26 May 2026 21:35:32 +0200 Subject: [PATCH 031/135] fix: Update `clang-tidy` to include `src/tests` directory header check (#7307) --- .clang-tidy | 2 +- src/tests/libxrpl/helpers/Account.cpp | 2 +- src/tests/libxrpl/helpers/Account.h | 10 +++++----- src/tests/libxrpl/helpers/IOU.h | 3 +-- src/tests/libxrpl/helpers/TestFamily.h | 12 +++++------- src/tests/libxrpl/helpers/TestServiceRegistry.h | 10 ++++------ src/tests/libxrpl/helpers/TxTest.cpp | 2 +- src/tests/libxrpl/helpers/TxTest.h | 2 +- src/tests/libxrpl/tx/AccountSet.cpp | 14 +++++++------- 9 files changed, 26 insertions(+), 31 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index ee6bde6eba..b23d7ccbff 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -199,6 +199,6 @@ CheckOptions: readability-identifier-naming.PublicMemberSuffix: "" readability-identifier-naming.GlobalFunctionIgnoredRegexp: "^(to_string|hash_append|tuple_hash)$" -HeaderFilterRegex: '^.*/(test|xrpl|xrpld)/.*\.(h|hpp|ipp)$' +HeaderFilterRegex: '^.*/(tests?|xrpl|xrpld)/.*\.(h|hpp|ipp)$' ExcludeHeaderFilterRegex: '^.*/protocol_autogen/.*\.(h|hpp)$' WarningsAsErrors: "*" diff --git a/src/tests/libxrpl/helpers/Account.cpp b/src/tests/libxrpl/helpers/Account.cpp index 736ae0a24b..4862309c82 100644 --- a/src/tests/libxrpl/helpers/Account.cpp +++ b/src/tests/libxrpl/helpers/Account.cpp @@ -7,7 +7,7 @@ namespace xrpl::test { -Account const Account::master{"masterpassphrase"}; +Account const Account::kMaster{"masterpassphrase"}; Account::Account(std::string_view name, KeyType type) : name_(name) diff --git a/src/tests/libxrpl/helpers/Account.h b/src/tests/libxrpl/helpers/Account.h index 4e2d6e547f..a92497f2c3 100644 --- a/src/tests/libxrpl/helpers/Account.h +++ b/src/tests/libxrpl/helpers/Account.h @@ -26,7 +26,7 @@ public: * This account is created in the genesis ledger with all 100 billion XRP. * It uses the well-known seed "masterpassphrase". */ - static Account const master; + static Account const kMaster; /** * @brief Create an account from a name. @@ -39,28 +39,28 @@ public: explicit Account(std::string_view name, KeyType type = KeyType::Secp256k1); /** @brief Return the human-readable name. */ - std::string const& + [[nodiscard]] std::string const& name() const noexcept { return name_; } /** @brief Return the AccountID. */ - AccountID const& + [[nodiscard]] AccountID const& id() const noexcept { return id_; } /** @brief Return the public key. */ - PublicKey const& + [[nodiscard]] PublicKey const& pk() const noexcept { return keyPair_.first; } /** @brief Return the secret key. */ - SecretKey const& + [[nodiscard]] SecretKey const& sk() const noexcept { return keyPair_.second; diff --git a/src/tests/libxrpl/helpers/IOU.h b/src/tests/libxrpl/helpers/IOU.h index 18bc69cf33..d80f962edf 100644 --- a/src/tests/libxrpl/helpers/IOU.h +++ b/src/tests/libxrpl/helpers/IOU.h @@ -49,8 +49,7 @@ public: * @param currency The Currency object. * @param issuer The account that issues this currency. */ - IOU(Currency currency, Account const& issuer) - : currency_(std::move(currency)), issuer_(issuer.id()) + IOU(Currency currency, Account const& issuer) : currency_(currency), issuer_(issuer.id()) { XRPL_ASSERT(!isXRP(currency_), "IOU: currency code must not resolve to XRP"); } diff --git a/src/tests/libxrpl/helpers/TestFamily.h b/src/tests/libxrpl/helpers/TestFamily.h index 2f69a26faf..dea7a6d4b4 100644 --- a/src/tests/libxrpl/helpers/TestFamily.h +++ b/src/tests/libxrpl/helpers/TestFamily.h @@ -7,8 +7,7 @@ #include -namespace xrpl { -namespace test { +namespace xrpl::test { /** Test implementation of Family for unit tests. @@ -49,7 +48,7 @@ public: return *db_; } - NodeStore::Database const& + [[nodiscard]] NodeStore::Database const& db() const override { return *db_; @@ -95,8 +94,8 @@ public: void reset() override { - fbCache_->reset(); - tnCache_->reset(); + (*fbCache_).reset(); + (*tnCache_).reset(); } /** Access the test clock for time manipulation in tests. */ @@ -107,5 +106,4 @@ public: } }; -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/tests/libxrpl/helpers/TestServiceRegistry.h b/src/tests/libxrpl/helpers/TestServiceRegistry.h index 7070927842..0536176344 100644 --- a/src/tests/libxrpl/helpers/TestServiceRegistry.h +++ b/src/tests/libxrpl/helpers/TestServiceRegistry.h @@ -16,8 +16,7 @@ #include #include -namespace xrpl { -namespace test { +namespace xrpl::test { /** Logs implementation that creates TestSink instances. */ class TestLogs : public Logs @@ -63,7 +62,7 @@ private: class TestServiceRegistry : public ServiceRegistry { TestLogs logs_{beast::Severity::Warning}; - boost::asio::io_context io_context_; + boost::asio::io_context ioContext_; TestFamily family_{logs_.journal("TestFamily")}; LoadFeeTrack feeTrack_{logs_.journal("LoadFeeTrack")}; TestNetworkIDService networkIDService_; @@ -344,7 +343,7 @@ public: boost::asio::io_context& getIOContext() override { - return io_context_; + return ioContext_; } Logs& @@ -374,5 +373,4 @@ public: } }; -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/tests/libxrpl/helpers/TxTest.cpp b/src/tests/libxrpl/helpers/TxTest.cpp index 32667ba13d..aeaa805b27 100644 --- a/src/tests/libxrpl/helpers/TxTest.cpp +++ b/src/tests/libxrpl/helpers/TxTest.cpp @@ -123,7 +123,7 @@ void TxTest::createAccount(Account const& account, XRPAmount xrp, uint32_t accountFlags) { auto const paymentTer = - submit(transactions::PaymentBuilder{Account::master, account, xrp}, Account::master).ter; + submit(transactions::PaymentBuilder{Account::kMaster, account, xrp}, Account::kMaster).ter; if (paymentTer != tesSUCCESS) { diff --git a/src/tests/libxrpl/helpers/TxTest.h b/src/tests/libxrpl/helpers/TxTest.h index 0b578c7e7f..1b3ce460a2 100644 --- a/src/tests/libxrpl/helpers/TxTest.h +++ b/src/tests/libxrpl/helpers/TxTest.h @@ -44,7 +44,7 @@ namespace xrpl::test { */ template constexpr XRPAmount -XRP(T xrp) +XRP(T xrp) // NOLINT(readability-identifier-naming) { return XRPAmount{static_cast(xrp) * kDropsPerXrp.drops()}; } diff --git a/src/tests/libxrpl/tx/AccountSet.cpp b/src/tests/libxrpl/tx/AccountSet.cpp index d00df152ae..726e6e9024 100644 --- a/src/tests/libxrpl/tx/AccountSet.cpp +++ b/src/tests/libxrpl/tx/AccountSet.cpp @@ -432,13 +432,13 @@ TEST(AccountSet, TransferRate) // Test data: {rate to set, expected TER, expected stored rate} std::vector const testData = { - {1.0, tesSUCCESS, 1.0}, - {1.1, tesSUCCESS, 1.1}, - {2.0, tesSUCCESS, 2.0}, - {2.1, temBAD_TRANSFER_RATE, 2.0}, // > 2.0 is invalid - {0.0, tesSUCCESS, 1.0}, // 0 clears the rate (default = 1.0) - {2.0, tesSUCCESS, 2.0}, - {0.9, temBAD_TRANSFER_RATE, 2.0}, // < 1.0 is invalid + {.set = 1.0, .code = tesSUCCESS, .get = 1.0}, + {.set = 1.1, .code = tesSUCCESS, .get = 1.1}, + {.set = 2.0, .code = tesSUCCESS, .get = 2.0}, + {.set = 2.1, .code = temBAD_TRANSFER_RATE, .get = 2.0}, // > 2.0 is invalid + {.set = 0.0, .code = tesSUCCESS, .get = 1.0}, // 0 clears; default rate is 1.0 + {.set = 2.0, .code = tesSUCCESS, .get = 2.0}, + {.set = 0.9, .code = temBAD_TRANSFER_RATE, .get = 2.0}, // < 1.0 is invalid }; TxTest env; From 9623e67b761f2ffd191d9abfe60c5acd82c7e1fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 15:35:52 -0400 Subject: [PATCH 032/135] ci: [DEPENDABOT] bump docker/login-action from 4.1.0 to 4.2.0 (#7318) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-nix-image.yml | 2 +- .github/workflows/reusable-build-docker-image.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-nix-image.yml b/.github/workflows/build-nix-image.yml index a8fb6eec86..e124c8afd7 100644 --- a/.github/workflows/build-nix-image.yml +++ b/.github/workflows/build-nix-image.yml @@ -92,7 +92,7 @@ jobs: type=raw,value=latest - name: Login to GitHub Container Registry - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ghcr.io username: ${{ github.repository_owner }} diff --git a/.github/workflows/reusable-build-docker-image.yml b/.github/workflows/reusable-build-docker-image.yml index e631e02368..f919a550ed 100644 --- a/.github/workflows/reusable-build-docker-image.yml +++ b/.github/workflows/reusable-build-docker-image.yml @@ -60,7 +60,7 @@ jobs: - name: Login to GitHub Container Registry if: inputs.push - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ghcr.io username: ${{ github.repository_owner }} From 7c597865657ccf6ca8b8b89cdbd5a5506fdcddb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 15:36:00 -0400 Subject: [PATCH 033/135] ci: [DEPENDABOT] bump docker/metadata-action from 6.0.0 to 6.1.0 (#7319) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-nix-image.yml | 2 +- .github/workflows/reusable-build-docker-image.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-nix-image.yml b/.github/workflows/build-nix-image.yml index e124c8afd7..3987b48099 100644 --- a/.github/workflows/build-nix-image.yml +++ b/.github/workflows/build-nix-image.yml @@ -84,7 +84,7 @@ jobs: - name: Docker metadata id: meta - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 with: images: ${{ env.IMAGE_NAME }} tags: | diff --git a/.github/workflows/reusable-build-docker-image.yml b/.github/workflows/reusable-build-docker-image.yml index f919a550ed..9bb7b06186 100644 --- a/.github/workflows/reusable-build-docker-image.yml +++ b/.github/workflows/reusable-build-docker-image.yml @@ -68,7 +68,7 @@ jobs: - name: Docker metadata id: meta - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 with: images: ${{ inputs.image_name }} tags: | From 4584b01bde3d80d00a8813d9a14f6101162ba8e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 15:36:13 -0400 Subject: [PATCH 034/135] ci: [DEPENDABOT] bump docker/build-push-action from 7.1.0 to 7.2.0 (#7320) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/reusable-build-docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-build-docker-image.yml b/.github/workflows/reusable-build-docker-image.yml index 9bb7b06186..5d60dc29fd 100644 --- a/.github/workflows/reusable-build-docker-image.yml +++ b/.github/workflows/reusable-build-docker-image.yml @@ -78,7 +78,7 @@ jobs: suffix=-${{ steps.vars.outputs.arch }},onlatest=true - name: Build and push - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . file: ${{ inputs.dockerfile }} From 108a4c8217b5d84dc10dcfce97ec8404fc622883 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 15:36:21 -0400 Subject: [PATCH 035/135] ci: [DEPENDABOT] bump codecov/codecov-action from 6.0.0 to 6.0.1 (#7321) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/reusable-build-test-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index cc927512ea..4f9b926e98 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -324,7 +324,7 @@ jobs: - name: Upload coverage report if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 + uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 with: disable_search: true disable_telem: true From 2a0feca46b4034f215cb5b4dbe068c836031fa97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 19:36:32 +0000 Subject: [PATCH 036/135] ci: [DEPENDABOT] bump docker/setup-buildx-action from 4.0.0 to 4.1.0 (#7322) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-nix-image.yml | 2 +- .github/workflows/reusable-build-docker-image.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-nix-image.yml b/.github/workflows/build-nix-image.yml index 3987b48099..fde808642c 100644 --- a/.github/workflows/build-nix-image.yml +++ b/.github/workflows/build-nix-image.yml @@ -80,7 +80,7 @@ jobs: steps: - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Docker metadata id: meta diff --git a/.github/workflows/reusable-build-docker-image.yml b/.github/workflows/reusable-build-docker-image.yml index 5d60dc29fd..5b555b713f 100644 --- a/.github/workflows/reusable-build-docker-image.yml +++ b/.github/workflows/reusable-build-docker-image.yml @@ -56,7 +56,7 @@ jobs: echo "arch=${PLATFORM##*/}" >> $GITHUB_OUTPUT - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Login to GitHub Container Registry if: inputs.push From 1162371def825beed3888f79b9f253dea165c13d Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 26 May 2026 21:03:04 +0100 Subject: [PATCH 037/135] ci: Only push docker images in XRPLF/rippled (#7330) --- .github/workflows/build-nix-image.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-nix-image.yml b/.github/workflows/build-nix-image.yml index fde808642c..edd35132fa 100644 --- a/.github/workflows/build-nix-image.yml +++ b/.github/workflows/build-nix-image.yml @@ -61,12 +61,12 @@ jobs: base_image: ${{ matrix.distro.base_image }} platform: ${{ matrix.target.platform }} runner: ${{ matrix.target.runner }} - push: ${{ github.event_name == 'push' }} + push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} merge: name: Merge ${{ matrix.distro }} manifest needs: build - if: github.event_name == 'push' + if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} runs-on: ubuntu-latest permissions: contents: read From 7da643d8648959f83d5d6a7d5d3a869171dbf1ca Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Wed, 27 May 2026 11:19:20 -0400 Subject: [PATCH 038/135] fix: Fix a rounding error at the `Number::maxRep` cusp (#7051) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com> --- include/xrpl/basics/Number.h | 137 +++++---- include/xrpl/protocol/Rules.h | 13 + include/xrpl/protocol/STAmount.h | 2 +- src/libxrpl/basics/Number.cpp | 351 ++++++++++++++++-------- src/libxrpl/protocol/IOUAmount.cpp | 2 +- src/libxrpl/protocol/Rules.cpp | 62 ++++- src/libxrpl/protocol/STNumber.cpp | 3 +- src/libxrpl/tx/applySteps.cpp | 18 +- src/test/app/AMMExtended_test.cpp | 109 +++----- src/test/app/AMM_test.cpp | 9 - src/test/app/Invariants_test.cpp | 206 +++++++------- src/test/basics/IOUAmount_test.cpp | 3 +- src/test/basics/Number_test.cpp | 86 +++++- src/test/protocol/STNumber_test.cpp | 3 +- src/test/rpc/GetAggregatePrice_test.cpp | 3 +- 15 files changed, 646 insertions(+), 361 deletions(-) diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index e67f1f534d..3aeb4b5bcd 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include namespace xrpl { @@ -44,11 +46,11 @@ isPowerOfTen(T value) * * min is a power of 10, and * * max = min * 10 - 1. * - * The mantissa_scale enum indicates whether the range is "small" or "large". - * This intentionally restricts the number of MantissaRanges that can be - * instantiated to two: one for each scale. + * The MantissaScale enum indicates properties of the range: size, and some behavioral + * options. This intentionally restricts the number of unique MantissaRanges that can + * be instantiated: one for each scale. * - * The "small" scale is based on the behavior of STAmount for IOUs. It has a min + * The "Small" scale is based on the behavior of STAmount for IOUs. It has a min * value of 10^15, and a max value of 10^16-1. This was sufficient for * uses before Lending Protocol was implemented, mostly related to AMM. * @@ -59,29 +61,54 @@ isPowerOfTen(T value) * STNumber field type, and for internal calculations. That necessitated the * "large" scale. * - * The "large" scale is intended to represent all values that can be represented + * The "Large" scales are intended to represent all values that can be represented * by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max - * value of 10^19-1. + * value of 10^19-1. "LargeLegacy" is like "Large", but preserves + * a rounding error when a computation results in a mantissa of + * Number::kMaxRep that needs to be rounded up, but rounds down + * instead. It will maintain consistent behavior until the fixCleanup3_2_0 + * amendment is enabled. * * Note that if the mentioned amendments are eventually retired, this class - * should be left in place, but the "small" scale option should be removed. This + * should be left in place, but the "Small" scale option should be removed. This * will allow for future expansion beyond 64-bits if it is ever needed. */ -struct MantissaRange +struct MantissaRange final { using rep = std::uint64_t; - enum class MantissaScale { Small, Large }; + enum class MantissaScale { + Small, + // LargeLegacy can be removed when fixCleanup3_2_0 is retired + LargeLegacy, + Large, + }; + + // This entire enum can be removed when fixCleanup3_2_0 is retired + enum class CuspRoundingFix : bool { + Disabled = false, + Enabled = true, + }; explicit constexpr MantissaRange(MantissaScale scale) - : min(getMin(scale)), log(logTen(min).value_or(-1)), scale(scale) + : min(getMin(scale)) + , cuspRoundingFixEnabled(isCuspFixEnabled(scale)) + , log(logTen(min).value_or(-1)) + , scale(scale) { } rep min; rep max{(min * 10) - 1}; + CuspRoundingFix cuspRoundingFixEnabled; int log; MantissaScale scale; + static MantissaRange const& + getMantissaRange(MantissaScale scale); + + static std::set const& + getAllScales(); + private: static constexpr rep getMin(MantissaScale scale) @@ -90,15 +117,35 @@ private: { case MantissaScale::Small: return 1'000'000'000'000'000ULL; + case MantissaScale::LargeLegacy: case MantissaScale::Large: return 1'000'000'000'000'000'000ULL; default: - // Since this can never be called outside a non-constexpr - // context, this throw assures that the build fails if an + // If called in a constexpr context, this throw assures that the build fails if an // invalid scale is used. - throw std::runtime_error("Unknown mantissa scale"); + throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE } } + + static constexpr CuspRoundingFix + isCuspFixEnabled(MantissaScale scale) + { + switch (scale) + { + case MantissaScale::Small: + case MantissaScale::LargeLegacy: + return CuspRoundingFix::Disabled; + case MantissaScale::Large: + return CuspRoundingFix::Enabled; + default: + // If called in a constexpr context, this throw assures that the build fails if an + // invalid scale is used. + throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE + } + } + + static std::unordered_map const& + getRanges(); }; // Like std::integral, but only 64-bit integral types. @@ -203,7 +250,7 @@ concept Integral64 = std::is_same_v || std::is_same_v + template < + auto MinMantissa, + auto MaxMantissa, + Integral64 T = std::decay_t, + Integral64 TMax = std::decay_t> [[nodiscard]] std::pair - normalizeToRange(T minMantissa, T maxMantissa) const; + normalizeToRange() const; private: static thread_local RoundingMode mode; // The available ranges for mantissa - static constexpr MantissaRange kSmallRange{MantissaRange::MantissaScale::Small}; - static_assert(isPowerOfTen(kSmallRange.min)); - static_assert(kSmallRange.min == 1'000'000'000'000'000LL); - static_assert(kSmallRange.max == 9'999'999'999'999'999LL); - static_assert(kSmallRange.log == 15); - static_assert(kSmallRange.min < kMaxRep); - static_assert(kSmallRange.max < kMaxRep); - static constexpr MantissaRange kLargeRange{MantissaRange::MantissaScale::Large}; - static_assert(isPowerOfTen(kLargeRange.min)); - static_assert(kLargeRange.min == 1'000'000'000'000'000'000ULL); - static_assert(kLargeRange.max == internalrep(9'999'999'999'999'999'999ULL)); - static_assert(kLargeRange.log == 18); - static_assert(kLargeRange.min < kMaxRep); - static_assert(kLargeRange.max > kMaxRep); - // The range for the mantissa when normalized. // Use reference_wrapper to avoid making copies, and prevent accidentally // changing the values inside the range. static thread_local std::reference_wrapper kRange; void - normalize(); + normalize(MantissaRange const& range); /** Normalize Number components to an arbitrary range. * @@ -481,7 +508,8 @@ private: T& mantissa, int& exponent, internalrep const& minMantissa, - internalrep const& maxMantissa); + internalrep const& maxMantissa, + MantissaRange::CuspRoundingFix cuspRoundingFixEnabled); template friend void @@ -490,7 +518,8 @@ private: T& mantissa, int& exponent, MantissaRange::rep const& minMantissa, - MantissaRange::rep const& maxMantissa); + MantissaRange::rep const& maxMantissa, + MantissaRange::CuspRoundingFix cuspRoundingFixEnabled); [[nodiscard]] bool isnormal() const noexcept; @@ -526,7 +555,7 @@ static constexpr Number kNumZero{}; inline Number::Number(bool negative, internalrep mantissa, int exponent, Normalized) : Number(negative, mantissa, exponent, Unchecked{}) { - normalize(); + normalize(kRange); } inline Number::Number(internalrep mantissa, int exponent, Normalized) @@ -696,10 +725,19 @@ Number::isnormal() const noexcept kMinExponent <= exponent_ && exponent_ <= kMaxExponent); } -template +template std::pair -Number::normalizeToRange(T minMantissa, T maxMantissa) const +Number::normalizeToRange() const { + static_assert(std::is_same_v || std::is_same_v); + static_assert(std::is_same_v); + auto constexpr kMIN = static_cast(MinMantissa); + auto constexpr kMAX = static_cast(MaxMantissa); + static_assert(kMIN > 0); + static_assert(kMIN % 10 == 0); + static_assert(kMAX % 10 == 9); + static_assert((kMAX + 1) / 10 == kMIN); + bool negative = negative_; internalrep mantissa = mantissa_; int exponent = exponent_; @@ -711,7 +749,10 @@ Number::normalizeToRange(T minMantissa, T maxMantissa) const "xrpl::Number::normalizeToRange", "Number is non-negative for unsigned range."); } - Number::normalize(negative, mantissa, exponent, minMantissa, maxMantissa); + // Don't need to worry about the cuspRounding fix because rounding up will never take the + // mantissa over maxMantissa with a ones digit value other than 0. 0 can safely be truncated. + Number::normalize( + negative, mantissa, exponent, kMIN, kMAX, MantissaRange::CuspRoundingFix::Disabled); auto const sign = negative ? -1 : 1; return std::make_pair(static_cast(sign * mantissa), exponent); @@ -763,6 +804,8 @@ to_string(MantissaRange::MantissaScale const& scale) { case MantissaRange::MantissaScale::Small: return "small"; + case MantissaRange::MantissaScale::LargeLegacy: + return "largeLegacy"; case MantissaRange::MantissaScale::Large: return "large"; default: diff --git a/include/xrpl/protocol/Rules.h b/include/xrpl/protocol/Rules.h index fbbd3d8805..9c17ff2391 100644 --- a/include/xrpl/protocol/Rules.h +++ b/include/xrpl/protocol/Rules.h @@ -122,4 +122,17 @@ private: std::optional saved_; }; +class NumberSO; +class NumberMantissaScaleGuard; + +bool +useRulesGuards(Rules const& rules); + +void +createGuards( + Rules const& rules, + std::optional& stNumberSO, + std::optional& rulesGuard, + std::optional& mantissaScaleGuard); + } // namespace xrpl diff --git a/include/xrpl/protocol/STAmount.h b/include/xrpl/protocol/STAmount.h index a4fffad40c..1a5b442d8b 100644 --- a/include/xrpl/protocol/STAmount.h +++ b/include/xrpl/protocol/STAmount.h @@ -559,7 +559,7 @@ STAmount::fromNumber(A const& a, Number const& number) return STAmount{asset, intValue, 0, negative}; } - auto const [mantissa, exponent] = working.normalizeToRange(kMinValue, kMaxValue); + auto const [mantissa, exponent] = working.normalizeToRange(); return STAmount{asset, mantissa, exponent, negative}; } diff --git a/src/libxrpl/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index 06bd78d8b0..11f5934b04 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -10,9 +10,11 @@ #include #include #include +#include #include #include #include +#include #include #ifdef _MSC_VER @@ -28,7 +30,76 @@ using int128_t = __int128_t; namespace xrpl { thread_local Number::RoundingMode Number::mode = Number::RoundingMode::ToNearest; -thread_local std::reference_wrapper Number::kRange = kLargeRange; +thread_local std::reference_wrapper Number::kRange = + MantissaRange::getMantissaRange(MantissaRange::MantissaScale::Large); + +std::set const& +MantissaRange::getAllScales() +{ + static std::set const kScales = { + MantissaRange::MantissaScale::Small, + MantissaRange::MantissaScale::LargeLegacy, + MantissaRange::MantissaScale::Large, + }; + return kScales; +} + +std::unordered_map const& +MantissaRange::getRanges() +{ + static auto const kMap = []() { + std::unordered_map map; + for (auto const scale : getAllScales()) + { + map.emplace(scale, scale); + } + + // Use these constexpr declarations to do static_asserts to verify the MantissaRanges are + // created correctly, but nothing else. + { + [[maybe_unused]] + constexpr static MantissaRange kRange{MantissaRange::MantissaScale::Small}; + static_assert(isPowerOfTen(kRange.min)); + static_assert(kRange.min == 1'000'000'000'000'000LL); + static_assert(kRange.max == 9'999'999'999'999'999LL); + static_assert(kRange.log == 15); + static_assert(kRange.min < Number::kMaxRep); + static_assert(kRange.max < Number::kMaxRep); + static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Disabled); + } + { + [[maybe_unused]] + constexpr static MantissaRange kRange{MantissaRange::MantissaScale::LargeLegacy}; + static_assert(isPowerOfTen(kRange.min)); + static_assert(kRange.min == 1'000'000'000'000'000'000ULL); + static_assert(kRange.max == rep(9'999'999'999'999'999'999ULL)); + static_assert(kRange.log == 18); + static_assert(kRange.min < Number::kMaxRep); + static_assert(kRange.max > Number::kMaxRep); + static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Disabled); + } + { + [[maybe_unused]] + constexpr static MantissaRange kRange{MantissaRange::MantissaScale::Large}; + static_assert(isPowerOfTen(kRange.min)); + static_assert(kRange.min == 1'000'000'000'000'000'000ULL); + static_assert(kRange.max == rep(9'999'999'999'999'999'999ULL)); + static_assert(kRange.log == 18); + static_assert(kRange.min < Number::kMaxRep); + static_assert(kRange.max > Number::kMaxRep); + static_assert(kRange.cuspRoundingFixEnabled == CuspRoundingFix::Enabled); + } + return map; + }(); + + return kMap; +} + +MantissaRange const& +MantissaRange::getMantissaRange(MantissaScale scale) +{ + return getRanges().at(scale); +} Number::RoundingMode Number::getround() @@ -51,10 +122,37 @@ Number::getMantissaScale() void Number::setMantissaScale(MantissaRange::MantissaScale scale) { - if (scale != MantissaRange::MantissaScale::Small && - scale != MantissaRange::MantissaScale::Large) + if (!MantissaRange::getAllScales().contains(scale)) logicError("Unknown mantissa scale"); - kRange = scale == MantissaRange::MantissaScale::Small ? kSmallRange : kLargeRange; + kRange = MantissaRange::getMantissaRange(scale); +} + +// Optimization equivalent to: +// auto r = static_cast(u % 10); +// u /= 10; +// return r; +// Derived from Hacker's Delight Second Edition Chapter 10 +// by Henry S. Warren, Jr. +static inline unsigned +divu10(uint128_t& u) +{ + // q = u * 0.75 + auto q = (u >> 1) + (u >> 2); + // iterate towards q = u * 0.8 + q += q >> 4; + q += q >> 8; + q += q >> 16; + q += q >> 32; + q += q >> 64; + // q /= 8 approximately == u / 10 + q >>= 3; + // r = u - q * 10 approximately == u % 10 + auto r = static_cast(u - ((q << 3) + (q << 1))); + // correction c is 1 if r >= 10 else 0 + auto c = (r + 6) >> 4; + u = q + c; + r -= c * 10; + return r; } // Guard @@ -92,6 +190,18 @@ public: unsigned pop() noexcept; + /** Drop a digit from the mantissa, and increment the exponent, storing the dropped digit in + * this Guard. + * + * Substitute for: + push(mantissa % 10); + mantissa /= 10; + ++exponent; + */ + template + void + doDropDigit(T& mantissa, int& exponent) noexcept; + // Indicate round direction: 1 is up, -1 is down, 0 is even // This enables the client to round towards nearest, and on // tie, round towards even. @@ -107,6 +217,7 @@ public: int& exponent, internalrep const& minMantissa, internalrep const& maxMantissa, + MantissaRange::CuspRoundingFix cuspRoundingFixEnabled, std::string location); // Modify the result to the correctly rounded value @@ -168,6 +279,27 @@ Number::Guard::pop() noexcept return d; } +template +void +Number::Guard::doDropDigit(T& mantissa, int& exponent) noexcept +{ + push(mantissa % 10); + mantissa /= 10; + ++exponent; +} + +// Use the divu10 optimization for uint128s +template <> +void +Number::Guard::doDropDigit(uint128_t& mantissa, int& exponent) noexcept +{ + // The following is optimization for: + // push(static_cast(mantissa % 10)); + // mantissa /= 10; + push(divu10(mantissa)); + ++exponent; +} + // Returns: // -1 if Guard is less than half // 0 if Guard is exactly half @@ -242,18 +374,60 @@ Number::Guard::doRoundUp( int& exponent, internalrep const& minMantissa, internalrep const& maxMantissa, + MantissaRange::CuspRoundingFix cuspRoundingFixEnabled, std::string location) { auto r = round(); if (r == 1 || (r == 0 && (mantissa & 1) == 1)) { - ++mantissa; - // Ensure mantissa after incrementing fits within both the - // min/maxMantissa range and is a valid "rep". - if (mantissa > maxMantissa || mantissa > kMaxRep) + auto const safeToIncrement = [&maxMantissa](auto const& mantissa) { + return mantissa < maxMantissa && mantissa < kMaxRep; + }; + if (cuspRoundingFixEnabled == MantissaRange::CuspRoundingFix::Enabled) { - mantissa /= 10; - ++exponent; + // Ensure mantissa after incrementing fits within both the + // min/maxMantissa range and is a valid "rep". + if (safeToIncrement(mantissa)) + { + // Nothing unusual here, just increment the mantissa + ++mantissa; + } + else + { + // Incrementing the mantissa will require dividing, which will require rounding. So + // _don't_ increment the mantissa. Instead, divide and round recursively. It should + // be impossible to recurse more than once, because once the mantissa is divided by + // 10, it will be _well_ under maxMantissa and kMaxRep, so adding 1 will have no + // change of bringing it back over. + doDropDigit(mantissa, exponent); + XRPL_ASSERT_PARTS( + safeToIncrement(mantissa), + "xrpl::Number::Guard::doRoundUp", + "can't recurse more than once"); + doRoundUp( + negative, + mantissa, + exponent, + minMantissa, + maxMantissa, + cuspRoundingFixEnabled, + location); + return; + } + } + else + { + // Need to preserve the incorrect behavior until the fix amendment can be retired, + // because otherwise would risk an unplanned ledger fork. + ++mantissa; + // Ensure mantissa after incrementing fits within both the + // min/maxMantissa range and is a valid "rep". + if (mantissa > maxMantissa || mantissa > kMaxRep) + { + // Don't use doDropDigit here + mantissa /= 10; + ++exponent; + } } } bringIntoRange(negative, mantissa, exponent, minMantissa); @@ -293,9 +467,9 @@ Number::Guard::doRound(rep& drops, std::string location) const { static_assert(sizeof(internalrep) == sizeof(rep)); // This should be impossible, because it's impossible to represent - // "maxRep + 0.6" in Number, regardless of the scale. There aren't - // enough digits available. You'd either get a mantissa of "maxRep" - // or "(maxRep + 1) / 10", neither of which will round up when + // "kMaxRep + 0.6" in Number, regardless of the scale. There aren't + // enough digits available. You'd either get a mantissa of "kMaxRep" + // or "(kMaxRep + 1) / 10", neither of which will round up when // converting to rep, though the latter might overflow _before_ // rounding. Throw(std::string(location)); // LCOV_EXCL_LINE @@ -331,29 +505,11 @@ Number::externalToInternal(rep mantissa) return static_cast(-temp); } -constexpr Number -Number::oneSmall() -{ - return Number{false, Number::kSmallRange.min, -Number::kSmallRange.log, Number::Unchecked{}}; -}; - -constexpr Number kOneSml = Number::oneSmall(); - -constexpr Number -Number::oneLarge() -{ - return Number{false, Number::kLargeRange.min, -Number::kLargeRange.log, Number::Unchecked{}}; -}; - -constexpr Number kOneLrg = Number::oneLarge(); - Number Number::one() { - if (&kRange.get() == &kSmallRange) - return kOneSml; - XRPL_ASSERT(&kRange.get() == &kLargeRange, "Number::one() : valid range"); - return kOneLrg; + auto const& range = kRange.get(); + return Number{false, range.min, -range.log, Number::Unchecked{}}; } // Use the member names in this static function for now so the diff is cleaner @@ -365,7 +521,8 @@ doNormalize( T& mantissa, int& exponent, MantissaRange::rep const& minMantissa, - MantissaRange::rep const& maxMantissa) + MantissaRange::rep const& maxMantissa, + MantissaRange::CuspRoundingFix cuspRoundingFixEnabled) { static constexpr auto kMinExponent = Number::kMinExponent; static constexpr auto kMaxExponent = Number::kMaxExponent; @@ -394,9 +551,7 @@ doNormalize( { if (exponent >= kMaxExponent) throw std::overflow_error("Number::normalize 1"); - g.push(m % 10); - m /= 10; - ++exponent; + g.doDropDigit(m, exponent); } if ((exponent < kMinExponent) || (m < minMantissa)) { @@ -407,7 +562,7 @@ doNormalize( } // When using the largeRange, "m" needs fit within an int64, even if - // the final mantissa_ is going to end up larger to fit within the + // the final mantissa is going to end up larger to fit within the // MantissaRange. Cut it down here so that the rounding will be done while // it's smaller. // @@ -415,26 +570,31 @@ doNormalize( // so "m" will be modified to 990,000,000,000,012,345. Then that value // will be rounded to 990,000,000,000,012,345 or // 990,000,000,000,012,346, depending on the rounding mode. Finally, - // mantissa_ will be "m*10" so it fits within the range, and end up as + // mantissa will be "m*10" so it fits within the range, and end up as // 9,900,000,000,000,123,450 or 9,900,000,000,000,123,460. - // mantissa() will return mantissa_ / 10, and exponent() will return - // exponent_ + 1. + // mantissa() will return mantissa / 10, and exponent() will return + // exponent + 1. if (m > kMaxRep) { if (exponent >= kMaxExponent) throw std::overflow_error("Number::normalize 1.5"); - g.push(m % 10); - m /= 10; - ++exponent; + g.doDropDigit(m, exponent); } // Before modification, m should be within the min/max range. After - // modification, it must be less than maxRep. In other words, the original - // value should have been no more than maxRep * 10. - // (maxRep * 10 > maxMantissa) + // modification, it must be less than kMaxRep. In other words, the original + // value should have been no more than kMaxRep * 10. + // (kMaxRep * 10 > maxMantissa) XRPL_ASSERT_PARTS(m <= kMaxRep, "xrpl::doNormalize", "intermediate mantissa fits in int64"); mantissa = m; - g.doRoundUp(negative, mantissa, exponent, minMantissa, maxMantissa, "Number::normalize 2"); + g.doRoundUp( + negative, + mantissa, + exponent, + minMantissa, + maxMantissa, + cuspRoundingFixEnabled, + "Number::normalize 2"); XRPL_ASSERT_PARTS( mantissa >= minMantissa && mantissa <= maxMantissa, "xrpl::doNormalize", @@ -448,9 +608,10 @@ Number::normalize( uint128_t& mantissa, int& exponent, internalrep const& minMantissa, - internalrep const& maxMantissa) + internalrep const& maxMantissa, + MantissaRange::CuspRoundingFix cuspRoundingFixEnabled) { - doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa); + doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled); } template <> @@ -460,9 +621,10 @@ Number::normalize( unsigned long long& mantissa, int& exponent, internalrep const& minMantissa, - internalrep const& maxMantissa) + internalrep const& maxMantissa, + MantissaRange::CuspRoundingFix cuspRoundingFixEnabled) { - doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa); + doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled); } template <> @@ -472,16 +634,16 @@ Number::normalize( unsigned long& mantissa, int& exponent, internalrep const& minMantissa, - internalrep const& maxMantissa) + internalrep const& maxMantissa, + MantissaRange::CuspRoundingFix cuspRoundingFixEnabled) { - doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa); + doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled); } void -Number::normalize() +Number::normalize(MantissaRange const& range) { - auto const& range = kRange.get(); - normalize(negative_, mantissa_, exponent_, range.min, range.max); + normalize(negative_, mantissa_, exponent_, range.min, range.max, range.cuspRoundingFixEnabled); } // Copy the number, but set a new exponent. Because the mantissa doesn't change, @@ -542,9 +704,7 @@ Number::operator+=(Number const& y) g.setNegative(); do { - g.push(xm % 10); - xm /= 10; - ++xe; + g.doDropDigit(xm, xe); } while (xe < ye); } else if (xe > ye) @@ -553,26 +713,30 @@ Number::operator+=(Number const& y) g.setNegative(); do { - g.push(ym % 10); - ym /= 10; - ++ye; + g.doDropDigit(ym, ye); } while (xe > ye); } auto const& range = kRange.get(); auto const& minMantissa = range.min; auto const& maxMantissa = range.max; + auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled; if (xn == yn) { xm += ym; if (xm > maxMantissa || xm > kMaxRep) { - g.push(xm % 10); - xm /= 10; - ++xe; + g.doDropDigit(xm, xe); } - g.doRoundUp(xn, xm, xe, minMantissa, maxMantissa, "Number::addition overflow"); + g.doRoundUp( + xn, + xm, + xe, + minMantissa, + maxMantissa, + cuspRoundingFixEnabled, + "Number::addition overflow"); } else { @@ -598,38 +762,10 @@ Number::operator+=(Number const& y) negative_ = xn; mantissa_ = static_cast(xm); exponent_ = xe; - normalize(); + normalize(range); return *this; } -// Optimization equivalent to: -// auto r = static_cast(u % 10); -// u /= 10; -// return r; -// Derived from Hacker's Delight Second Edition Chapter 10 -// by Henry S. Warren, Jr. -static inline unsigned -divu10(uint128_t& u) -{ - // q = u * 0.75 - auto q = (u >> 1) + (u >> 2); - // iterate towards q = u * 0.8 - q += q >> 4; - q += q >> 8; - q += q >> 16; - q += q >> 32; - q += q >> 64; - // q /= 8 approximately == u / 10 - q >>= 3; - // r = u - q * 10 approximately == u % 10 - auto r = static_cast(u - ((q << 3) + (q << 1))); - // correction c is 1 if r >= 10 else 0 - auto c = (r + 6) >> 4; - u = q + c; - r -= c * 10; - return r; -} - Number& Number::operator*=(Number const& y) { @@ -667,15 +803,13 @@ Number::operator*=(Number const& y) auto const& range = kRange.get(); auto const& minMantissa = range.min; auto const& maxMantissa = range.max; + auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled; while (zm > maxMantissa || zm > kMaxRep) { - // The following is optimization for: - // g.push(static_cast(zm % 10)); - // zm /= 10; - g.push(divu10(zm)); - ++ze; + g.doDropDigit(zm, ze); } + xm = static_cast(zm); xe = ze; g.doRoundUp( @@ -684,12 +818,13 @@ Number::operator*=(Number const& y) xe, minMantissa, maxMantissa, + cuspRoundingFixEnabled, "Number::multiplication overflow : exponent is " + std::to_string(xe)); negative_ = zn; mantissa_ = xm; exponent_ = xe; - normalize(); + normalize(range); return *this; } @@ -721,6 +856,7 @@ Number::operator/=(Number const& y) auto const& range = kRange.get(); auto const& minMantissa = range.min; auto const& maxMantissa = range.max; + auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled; // Shift by 10^17 gives greatest precision while not overflowing // uint128_t or the cast back to int64_t @@ -728,8 +864,6 @@ Number::operator/=(Number const& y) // log(2^128,10) ~ 38.5 // largeRange.log = 18, fits in 10^19 // f can be up to 10^(38-19) = 10^19 safely - static_assert(kSmallRange.log == 15); - static_assert(kLargeRange.log == 18); bool const small = Number::getMantissaScale() == MantissaRange::MantissaScale::Small; uint128_t const f = small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL; XRPL_ASSERT_PARTS(f >= minMantissa * 10, "Number::operator/=", "factor expected size"); @@ -779,7 +913,7 @@ Number::operator/=(Number const& y) ze -= 3; } } - normalize(zn, zm, ze, minMantissa, maxMantissa); + normalize(zn, zm, ze, minMantissa, maxMantissa, cuspRoundingFixEnabled); negative_ = zn; mantissa_ = static_cast(zm); exponent_ = ze; @@ -801,10 +935,9 @@ operator rep() const g.setNegative(); drops = -drops; } - for (; offset < 0; ++offset) + while (offset < 0) { - g.push(drops % 10); - drops /= 10; + g.doDropDigit(drops, offset); } for (; offset > 0; --offset) { @@ -831,7 +964,7 @@ Number::truncate() const noexcept } // We are guaranteed that normalize() will never throw an exception // because exponent is either negative or zero at this point. - ret.normalize(); + ret.normalize(kRange); return ret; } diff --git a/src/libxrpl/protocol/IOUAmount.cpp b/src/libxrpl/protocol/IOUAmount.cpp index d65ba41a01..d214995809 100644 --- a/src/libxrpl/protocol/IOUAmount.cpp +++ b/src/libxrpl/protocol/IOUAmount.cpp @@ -56,7 +56,7 @@ IOUAmount::fromNumber(Number const& number) // to normalize, which calls fromNumber IOUAmount result{}; std::tie(result.mantissa_, result.exponent_) = - number.normalizeToRange(kMinMantissa, kMaxMantissa); + number.normalizeToRange(); return result; } diff --git a/src/libxrpl/protocol/Rules.cpp b/src/libxrpl/protocol/Rules.cpp index 2c971749b6..08a95145eb 100644 --- a/src/libxrpl/protocol/Rules.cpp +++ b/src/libxrpl/protocol/Rules.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -38,15 +39,68 @@ setCurrentTransactionRules(std::optional r) // Make global changes associated with the rules before the value is moved. // Push the appropriate setting, instead of having the class pull every time // the value is needed. That could get expensive fast. - bool const enableLargeNumbers = + + // If any new conditions with new amendments are added, those amendments must also be added to + // useRulesGuards. + bool const enableVaultNumbers = !r || (r->enabled(featureSingleAssetVault) || r->enabled(featureLendingProtocol)); - Number::setMantissaScale( - enableLargeNumbers ? MantissaRange::MantissaScale::Large - : MantissaRange::MantissaScale::Small); + bool const enableCuspRoundingFix = !r || r->enabled(fixCleanup3_2_0); + XRPL_ASSERT( + !r || useRulesGuards(*r) == (enableCuspRoundingFix || enableVaultNumbers), + "setCurrentTransactionRules : rule decisions match"); + + // Declare the range this way to keep clang-tidy from complaining + auto const range = [enableCuspRoundingFix, enableVaultNumbers]() { + if (enableVaultNumbers) + { + if (enableCuspRoundingFix) + { + return MantissaRange::MantissaScale::Large; + } + return MantissaRange::MantissaScale::LargeLegacy; + } + return MantissaRange::MantissaScale::Small; + }(); + Number::setMantissaScale(range); *getCurrentTransactionRulesRef() = std::move(r); } +bool +useRulesGuards(Rules const& rules) +{ + // The list of amendments used here - to decide whether to create a RulesGuard - must be a + // superset of the list used to figure out which mantissa scale to use in + // setCurrentTransactionRules. Additional amendments can be added if desired. + // + // As soon as any one of these amendments is retired, this whole function can be removed, along + // with createGuards, and any other callers, and the first set of guards can be created directly + // at the call site, without using optional. + return rules.enabled(fixCleanup3_2_0) || rules.enabled(featureSingleAssetVault) || + rules.enabled(featureLendingProtocol); +} + +void +createGuards( + Rules const& rules, + std::optional& stNumberSO, + std::optional& rulesGuard, + std::optional& mantissaScaleGuard) +{ + if (useRulesGuards(rules)) + { + // raii classes for the current ledger rules. + // fixUniversalNumber predates the rulesGuard and should be replaced. + stNumberSO.emplace(rules.enabled(fixUniversalNumber)); + rulesGuard.emplace(rules); + } + else + { + // Without those features enabled, always use the old number rules. + mantissaScaleGuard.emplace(MantissaRange::MantissaScale::Small); + } +} + class Rules::Impl { private: diff --git a/src/libxrpl/protocol/STNumber.cpp b/src/libxrpl/protocol/STNumber.cpp index aa3e83515c..8ef7b9760f 100644 --- a/src/libxrpl/protocol/STNumber.cpp +++ b/src/libxrpl/protocol/STNumber.cpp @@ -96,7 +96,8 @@ STNumber::add(Serializer& s) const // Json. Regardless, the only time we should be serializing an // STNumber is when the scale is large. XRPL_ASSERT_PARTS( - Number::getMantissaScale() == MantissaRange::MantissaScale::Large, + Number::getMantissaScale() == MantissaRange::MantissaScale::LargeLegacy || + Number::getMantissaScale() == MantissaRange::MantissaScale::Large, "xrpl::STNumber::add", "STNumber only used with large mantissa scale"); #endif diff --git a/src/libxrpl/tx/applySteps.cpp b/src/libxrpl/tx/applySteps.cpp index e0b1af80e2..217fdd717f 100644 --- a/src/libxrpl/tx/applySteps.cpp +++ b/src/libxrpl/tx/applySteps.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -66,26 +65,15 @@ withTxnType(Rules const& rules, TxType txnType, F&& f) // so these need to be more global. // // To prevent unintentional side effects on existing checks, they will be - // set for every operation only once SingleAssetVault (or later - // LendingProtocol) are enabled. + // set for every operation only once at least one of the relevant amendments + // are enabled. // // See also Transactor::operator(). // std::optional stNumberSO; std::optional rulesGuard; std::optional mantissaScaleGuard; - if (rules.enabled(featureSingleAssetVault) || rules.enabled(featureLendingProtocol)) - { - // raii classes for the current ledger rules. - // fixUniversalNumber predates the rulesGuard and should be replaced. - stNumberSO.emplace(rules.enabled(fixUniversalNumber)); - rulesGuard.emplace(rules); - } - else - { - // Without those features enabled, always use the old number rules. - mantissaScaleGuard.emplace(MantissaRange::MantissaScale::Small); - } + createGuards(rules, stNumberSO, rulesGuard, mantissaScaleGuard); switch (txnType) { diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index 18f2d6df2f..a0a7d0fb15 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -65,10 +65,15 @@ namespace xrpl::test { /** * Tests of AMM that use offers too. */ -struct AMMExtended_test : public jtx::AMMTest +class AMMExtended_test : public jtx::AMMTest { // Use small Number mantissas for the life of this test. - NumberMantissaScaleGuard const sg{xrpl::MantissaRange::MantissaScale::Small}; + NumberMantissaScaleGuard const sg_{xrpl::MantissaRange::MantissaScale::Small}; + + // For now, just disable SAV entirely, which locks in the small Number + // mantissas + FeatureBitset const all_{ + testableAmendments() - featureSingleAssetVault - featureLendingProtocol}; private: void @@ -1349,37 +1354,33 @@ private: testOffers() { using namespace jtx; - // For now, just disable SAV entirely, which locks in the small Number - // mantissas - FeatureBitset const all{ - testableAmendments() - featureSingleAssetVault - featureLendingProtocol}; - testRmFundedOffer(all); - testRmFundedOffer(all - fixAMMv1_1 - fixAMMv1_3); - testEnforceNoRipple(all); - testFillModes(all); - testOfferCrossWithXRP(all); - testOfferCrossWithLimitOverride(all); - testCurrencyConversionEntire(all); - testCurrencyConversionInParts(all); - testCrossCurrencyStartXRP(all); - testCrossCurrencyEndXRP(all); - testCrossCurrencyBridged(all); - testOfferFeesConsumeFunds(all); - testOfferCreateThenCross(all); - testSellFlagExceedLimit(all); - testGatewayCrossCurrency(all); - testGatewayCrossCurrency(all - fixAMMv1_1 - fixAMMv1_3); - testBridgedCross(all); - testSellWithFillOrKill(all); - testTransferRateOffer(all); - testSelfIssueOffer(all); - testBadPathAssert(all); - testSellFlagBasic(all); - testDirectToDirectPath(all); - testDirectToDirectPath(all - fixAMMv1_1 - fixAMMv1_3); - testRequireAuth(all); - testMissingAuth(all); + testRmFundedOffer(all_); + testRmFundedOffer(all_ - fixAMMv1_1 - fixAMMv1_3); + testEnforceNoRipple(all_); + testFillModes(all_); + testOfferCrossWithXRP(all_); + testOfferCrossWithLimitOverride(all_); + testCurrencyConversionEntire(all_); + testCurrencyConversionInParts(all_); + testCrossCurrencyStartXRP(all_); + testCrossCurrencyEndXRP(all_); + testCrossCurrencyBridged(all_); + testOfferFeesConsumeFunds(all_); + testOfferCreateThenCross(all_); + testSellFlagExceedLimit(all_); + testGatewayCrossCurrency(all_); + testGatewayCrossCurrency(all_ - fixAMMv1_1 - fixAMMv1_3); + testBridgedCross(all_); + testSellWithFillOrKill(all_); + testTransferRateOffer(all_); + testSelfIssueOffer(all_); + testBadPathAssert(all_); + testSellFlagBasic(all_); + testDirectToDirectPath(all_); + testDirectToDirectPath(all_ - fixAMMv1_1 - fixAMMv1_3); + testRequireAuth(all_); + testMissingAuth(all_); } void @@ -3516,15 +3517,11 @@ private: testFlow() { using namespace jtx; - // For now, just disable SAV entirely, which locks in the small Number - // mantissas in the transaction engine - FeatureBitset const all{ - testableAmendments() - featureSingleAssetVault - featureLendingProtocol}; - testFalseDry(all); - testBookStep(all); - testTransferRateNoOwnerFee(all); - testTransferRateNoOwnerFee(all - fixAMMv1_1 - fixAMMv1_3); + testFalseDry(all_); + testBookStep(all_); + testTransferRateNoOwnerFee(all_); + testTransferRateNoOwnerFee(all_ - fixAMMv1_1 - fixAMMv1_3); testLimitQuality(); testXRPPathLoop(); } @@ -3533,34 +3530,22 @@ private: testCrossingLimits() { using namespace jtx; - // For now, just disable SAV entirely, which locks in the small Number - // mantissas in the transaction engine - FeatureBitset const all{ - testableAmendments() - featureSingleAssetVault - featureLendingProtocol}; - testStepLimit(all); - testStepLimit(all - fixAMMv1_1 - fixAMMv1_3); + testStepLimit(all_); + testStepLimit(all_ - fixAMMv1_1 - fixAMMv1_3); } void testDeliverMin() { using namespace jtx; - // For now, just disable SAV entirely, which locks in the small Number - // mantissas in the transaction engine - FeatureBitset const all{ - testableAmendments() - featureSingleAssetVault - featureLendingProtocol}; - testConvertAllOfAnAsset(all); - testConvertAllOfAnAsset(all - fixAMMv1_1 - fixAMMv1_3); + testConvertAllOfAnAsset(all_); + testConvertAllOfAnAsset(all_ - fixAMMv1_1 - fixAMMv1_3); } void testDepositAuth() { - // For now, just disable SAV entirely, which locks in the small Number - // mantissas in the transaction engine - FeatureBitset const all{ - jtx::testableAmendments() - featureSingleAssetVault - featureLendingProtocol}; - testPayment(all); + testPayment(all_); testPayIOU(); } @@ -3568,13 +3553,9 @@ private: testFreeze() { using namespace test::jtx; - // For now, just disable SAV entirely, which locks in the small Number - // mantissas in the transaction engine - FeatureBitset const sa{ - testableAmendments() - featureSingleAssetVault - featureLendingProtocol}; - testRippleState(sa); - testGlobalFreeze(sa); - testOffersWhenFrozen(sa); + testRippleState(all_); + testGlobalFreeze(all_); + testOffersWhenFrozen(all_); } void diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index 17f959ae3c..64972c24ab 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -2625,10 +2625,6 @@ private: using namespace jtx; using namespace std::chrono; - // For now, just disable SAV entirely, which locks in the small Number - // mantissas - features = features - featureSingleAssetVault - featureLendingProtocol; - // Auction slot initially is owned by AMM creator, who pays 0 price. // Bid 110 tokens. Pay bidMin. @@ -3337,11 +3333,6 @@ private: testcase("Basic Payment"); using namespace jtx; - // For now, just disable SAV entirely, which locks in the small Number - // mantissas - features = - features - featureSingleAssetVault - featureLendingProtocol - featureLendingProtocol; - // Payment 100USD for 100XRP. // Force one path with tfNoRippleDirect. testAMM( diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index c8a6e813de..432dccce61 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -4767,109 +4767,119 @@ class Invariants_test : public beast::unit_test::Suite std::vector values; }; - NumberMantissaScaleGuard const g{MantissaRange::MantissaScale::Large}; - - auto makeDelta = [&vaultAsset](Number const& n) -> ValidVault::DeltaInfo { - return {.delta = n, .scale = scale(n, vaultAsset.raw())}; - }; - - auto const testCases = std::vector{ - { - .name = "No values", - .expectedMinScale = 0, - .values = {}, - }, - { - .name = "Mixed integer and Number values", - .expectedMinScale = -15, - .values = {makeDelta(1), makeDelta(-1), makeDelta(Number{10, -1})}, - }, - { - .name = "Mixed scales", - .expectedMinScale = -17, - .values = - {makeDelta(Number{1, -2}), makeDelta(Number{5, -3}), makeDelta(Number{3, -2})}, - }, - { - .name = "Equal scales", - .expectedMinScale = -16, - .values = - {makeDelta(Number{1, -1}), makeDelta(Number{5, -1}), makeDelta(Number{1, -1})}, - }, - { - .name = "Mixed mantissa sizes", - .expectedMinScale = -12, - .values = - {makeDelta(Number{1}), - makeDelta(Number{1234, -3}), - makeDelta(Number{12345, -6}), - makeDelta(Number{123, 1})}, - }, - }; - - for (auto const& tc : testCases) + for (auto const mantissaScale : { + MantissaRange::MantissaScale::LargeLegacy, + MantissaRange::MantissaScale::Large, + }) { - testcase("vault computeCoarsestScale: " + tc.name); + NumberMantissaScaleGuard const g{mantissaScale}; - auto const actualScale = ValidVault::computeCoarsestScale(tc.values); + auto makeDelta = [&vaultAsset](Number const& n) -> ValidVault::DeltaInfo { + return {.delta = n, .scale = scale(n, vaultAsset.raw())}; + }; - BEAST_EXPECTS( - actualScale == tc.expectedMinScale, - "expected: " + std::to_string(tc.expectedMinScale) + - ", actual: " + std::to_string(actualScale)); - for (auto const& num : tc.values) - { - // None of these scales are far enough apart that rounding the - // values would lose information, so check that the rounded - // value matches the original. - auto const actualRounded = roundToAsset(vaultAsset, num.delta, actualScale); - BEAST_EXPECTS( - actualRounded == num.delta, - "number " + to_string(num.delta) + " rounded to scale " + - std::to_string(actualScale) + " is " + to_string(actualRounded)); - } - } - - auto const testCases2 = std::vector{ - { - .name = "False equivalence", - .expectedMinScale = -15, - .values = - { - makeDelta(Number{1234567890123456789, -18}), - makeDelta(Number{12345, -4}), - makeDelta(Number{1}), - }, - }, - }; - - // Unlike the first set of test cases, the values in these test could - // look equivalent if using the wrong scale. - for (auto const& tc : testCases2) - { - testcase("vault computeCoarsestScale: " + tc.name); - - auto const actualScale = ValidVault::computeCoarsestScale(tc.values); - - BEAST_EXPECTS( - actualScale == tc.expectedMinScale, - "expected: " + std::to_string(tc.expectedMinScale) + - ", actual: " + std::to_string(actualScale)); - std::optional first; - Number firstRounded; - for (auto const& num : tc.values) - { - if (!first) + auto const testCases = std::vector{ { - first = num.delta; - firstRounded = roundToAsset(vaultAsset, num.delta, actualScale); - continue; - } - auto const numRounded = roundToAsset(vaultAsset, num.delta, actualScale); + .name = "No values", + .expectedMinScale = 0, + .values = {}, + }, + { + .name = "Mixed integer and Number values", + .expectedMinScale = -15, + .values = {makeDelta(1), makeDelta(-1), makeDelta(Number{10, -1})}, + }, + { + .name = "Mixed scales", + .expectedMinScale = -17, + .values = + {makeDelta(Number{1, -2}), + makeDelta(Number{5, -3}), + makeDelta(Number{3, -2})}, + }, + { + .name = "Equal scales", + .expectedMinScale = -16, + .values = + {makeDelta(Number{1, -1}), + makeDelta(Number{5, -1}), + makeDelta(Number{1, -1})}, + }, + { + .name = "Mixed mantissa sizes", + .expectedMinScale = -12, + .values = + {makeDelta(Number{1}), + makeDelta(Number{1234, -3}), + makeDelta(Number{12345, -6}), + makeDelta(Number{123, 1})}, + }, + }; + + for (auto const& tc : testCases) + { + testcase("vault computeCoarsestScale: " + tc.name); + + auto const actualScale = ValidVault::computeCoarsestScale(tc.values); + BEAST_EXPECTS( - numRounded != firstRounded, - "at a scale of " + std::to_string(actualScale) + " " + to_string(num.delta) + - " == " + to_string(*first)); + actualScale == tc.expectedMinScale, + "expected: " + std::to_string(tc.expectedMinScale) + + ", actual: " + std::to_string(actualScale)); + for (auto const& num : tc.values) + { + // None of these scales are far enough apart that rounding the + // values would lose information, so check that the rounded + // value matches the original. + auto const actualRounded = roundToAsset(vaultAsset, num.delta, actualScale); + BEAST_EXPECTS( + actualRounded == num.delta, + "number " + to_string(num.delta) + " rounded to scale " + + std::to_string(actualScale) + " is " + to_string(actualRounded)); + } + } + + auto const testCases2 = std::vector{ + { + .name = "False equivalence", + .expectedMinScale = -15, + .values = + { + makeDelta(Number{1234567890123456789, -18}), + makeDelta(Number{12345, -4}), + makeDelta(Number{1}), + }, + }, + }; + + // Unlike the first set of test cases, the values in these test could + // look equivalent if using the wrong scale. + for (auto const& tc : testCases2) + { + testcase("vault computeCoarsestScale: " + tc.name); + + auto const actualScale = ValidVault::computeCoarsestScale(tc.values); + + BEAST_EXPECTS( + actualScale == tc.expectedMinScale, + "expected: " + std::to_string(tc.expectedMinScale) + + ", actual: " + std::to_string(actualScale)); + std::optional first; + Number firstRounded; + for (auto const& num : tc.values) + { + if (!first) + { + first = num.delta; + firstRounded = roundToAsset(vaultAsset, num.delta, actualScale); + continue; + } + auto const numRounded = roundToAsset(vaultAsset, num.delta, actualScale); + BEAST_EXPECTS( + numRounded != firstRounded, + "at a scale of " + std::to_string(actualScale) + " " + + to_string(num.delta) + " == " + to_string(*first)); + } } } } diff --git a/src/test/basics/IOUAmount_test.cpp b/src/test/basics/IOUAmount_test.cpp index ef053449a5..b652d27625 100644 --- a/src/test/basics/IOUAmount_test.cpp +++ b/src/test/basics/IOUAmount_test.cpp @@ -156,8 +156,7 @@ public: BEAST_EXPECTS(result == expected, ss.str()); }; - for (auto const mantissaSize : - {MantissaRange::MantissaScale::Small, MantissaRange::MantissaScale::Large}) + for (auto const mantissaSize : MantissaRange::getAllScales()) { NumberMantissaScaleGuard const mg(mantissaSize); diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index 2a4e176ae5..26060c70e9 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -6,7 +6,10 @@ #include #include +#include + #include +#include #include #include #include @@ -19,6 +22,24 @@ namespace xrpl { class Number_test : public beast::unit_test::Suite { + using BigInt = boost::multiprecision::cpp_int; + + static std::string + fmt(BigInt const& value) + { + auto s = to_string(value); + std::string out; + int count = 0; + for (auto it = s.rbegin(); it != s.rend(); ++it) + { + if (count != 0 && count % 3 == 0 && (isdigit(*it) != 0)) + out.insert(out.begin(), '_'); + out.insert(out.begin(), *it); + ++count; + } + return out; + } + public: void testZero() @@ -178,7 +199,6 @@ public: {Number{true, 9'999'999'999'999'999'999ULL, -37, Number::Normalized{}}, Number{1'000'000'000'000'000'000, -18}, Number{false, 9'999'999'999'999'999'990ULL, -19, Number::Normalized{}}}, - {Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep / 10, 1}}, {Number{Number::kMaxRep - 1}, Number{1, 0}, Number{Number::kMaxRep}}, // Test extremes { @@ -189,16 +209,22 @@ public: Number{2, 19}, }, { - // Does not round. Mantissas are going to be > maxRep, so if + // Does not round. Mantissas are going to be > kMaxRep, so if // added together as uint64_t's, the result will overflow. // With addition using uint128_t, there's no problem. After // normalizing, the resulting mantissa ends up less than - // maxRep. + // kMaxRep. Number{false, 9'999'999'999'999'999'990ULL, 0, Number::Normalized{}}, Number{false, 9'999'999'999'999'999'990ULL, 0, Number::Normalized{}}, Number{false, 1'999'999'999'999'999'998ULL, 1, Number::Normalized{}}, }, }); + auto const cLargeLegacy = std::to_array({ + {Number{Number::kMaxRep}, Number{6, -1}, Number{Number::kMaxRep / 10, 1}}, + }); + auto const cLargeCorrected = std::to_array({ + {Number{Number::kMaxRep}, Number{6, -1}, Number{(Number::kMaxRep / 10) + 1, 1}}, + }); auto test = [this](auto const& c) { for (auto const& [x, y, z] : c) { @@ -215,6 +241,14 @@ public: else { test(cLarge); + if (scale == MantissaRange::MantissaScale::LargeLegacy) + { + test(cLargeLegacy); + } + else + { + test(cLargeCorrected); + } } { bool caught = false; @@ -835,7 +869,7 @@ public: /* auto tests = [&](auto const& cSmall, auto const& cLarge) { test(cSmall); - if (scale != MantissaRange::mantissa_scale::small) + if (scale != MantissaRange::MantissaScale::Small) test(cLarge); }; */ @@ -1266,6 +1300,7 @@ public: "9223372036854775e3"); } break; + case MantissaRange::MantissaScale::LargeLegacy: case MantissaRange::MantissaScale::Large: // Test the edges // ((exponent < -(28)) || (exponent > -(8))))) @@ -1551,11 +1586,48 @@ public: } } + void + testUpwardRoundsDown() + { + testcase << "upward rounding produces a value below exact at kMaxRep cusp"; + + NumberMantissaScaleGuard const mg{MantissaRange::MantissaScale::Large}; + NumberRoundModeGuard const rg{Number::RoundingMode::Upward}; + + constexpr std::int64_t kAValue = 1'000'000'000'000'049'863LL; + constexpr std::int64_t kBValue = 9'223'372'036'854'315'903LL; + + Number const a = kAValue; + Number const b = kBValue; + Number const product = a * b; + + // Exact reference in BigInt. + BigInt const exactProduct = BigInt(kAValue) * BigInt(kBValue); + + // What Number actually stored. + BigInt storedValue = BigInt(product.mantissa()); + for (int i = 0; i < product.exponent(); ++i) + storedValue *= 10; + + BigInt const signedDifference = storedValue - exactProduct; + + log << "\n" + << " a = " << fmt(BigInt(kAValue)) << "\n" + << " b = " << fmt(BigInt(kBValue)) << "\n" + << " exact a*b = " << fmt(exactProduct) << "\n" + << " stored = " << fmt(storedValue) << "\n" + << " stored - exact = " << fmt(signedDifference) << "\n" + << " upward = " << (signedDifference >= 0 ? "held" : "VIOLATED") << "\n"; + + BEAST_EXPECT(signedDifference >= 0); + BEAST_EXPECT(product.mantissa() == (std::numeric_limits::max() / 10) + 1); + BEAST_EXPECT(product.exponent() == 19); + } + void run() override { - for (auto const scale : - {MantissaRange::MantissaScale::Small, MantissaRange::MantissaScale::Large}) + for (auto const scale : MantissaRange::getAllScales()) { NumberMantissaScaleGuard const sg(scale); testZero(); @@ -1580,6 +1652,8 @@ public: testRounding(); testInt64(); } + // This test sets its own number range + testUpwardRoundsDown(); } }; diff --git a/src/test/protocol/STNumber_test.cpp b/src/test/protocol/STNumber_test.cpp index 4d95bc1ef7..5c9c3fd83c 100644 --- a/src/test/protocol/STNumber_test.cpp +++ b/src/test/protocol/STNumber_test.cpp @@ -280,8 +280,7 @@ struct STNumber_test : public beast::unit_test::Suite { static_assert(!std::is_convertible_v); - for (auto const scale : - {MantissaRange::MantissaScale::Small, MantissaRange::MantissaScale::Large}) + for (auto const scale : MantissaRange::getAllScales()) { NumberMantissaScaleGuard const sg(scale); testcase << to_string(Number::getMantissaScale()); diff --git a/src/test/rpc/GetAggregatePrice_test.cpp b/src/test/rpc/GetAggregatePrice_test.cpp index 1de08da205..37ecc54172 100644 --- a/src/test/rpc/GetAggregatePrice_test.cpp +++ b/src/test/rpc/GetAggregatePrice_test.cpp @@ -177,8 +177,7 @@ public: auto const all = testableAmendments(); for (auto const& feats : {all - featureSingleAssetVault - featureLendingProtocol, all}) { - for (auto const mantissaSize : - {MantissaRange::MantissaScale::Small, MantissaRange::MantissaScale::Large}) + for (auto const mantissaSize : MantissaRange::getAllScales()) { // Regardless of the features enabled, RPC is controlled by // the global mantissa size. And since it's a thread-local, From 1438bf1c6764056216bfeab5027e0eccc12fd870 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 27 May 2026 18:20:57 +0100 Subject: [PATCH 039/135] release: Bump version to 3.2.0-rc1 (#7335) --- 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 5613a9366e..4e95683308 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -23,7 +23,7 @@ namespace { //------------------------------------------------------------------------------ // clang-format off // NOLINTNEXTLINE(readability-identifier-naming) -char const* const versionString = "3.2.0-b7" +char const* const versionString = "3.2.0-rc1" // clang-format on ; From 396d772a15a634d4a50e3f8e22e7378d1eed914d Mon Sep 17 00:00:00 2001 From: Bart Date: Wed, 27 May 2026 15:10:33 -0400 Subject: [PATCH 040/135] refactor: Enable support for `fixCleanup3_2_0` amendment (#7347) --- include/xrpl/protocol/detail/features.macro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index fd62b74d59..c99c1e5ce8 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -15,7 +15,7 @@ // Add new amendments to the top of this list. // Keep it sorted in reverse chronological order. -XRPL_FIX (Cleanup3_2_0, Supported::No, VoteBehavior::DefaultNo) +XRPL_FIX (Cleanup3_2_0, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo) XRPL_FIX (Cleanup3_1_3, Supported::Yes, VoteBehavior::DefaultYes) XRPL_FIX (BatchInnerSigs, Supported::No, VoteBehavior::DefaultNo) From 1acc42313c01e7e114333390b4485e3e176ba66f Mon Sep 17 00:00:00 2001 From: Bart Date: Wed, 27 May 2026 15:11:38 -0400 Subject: [PATCH 041/135] release: Bump version to 3.2.0-rc2 (#7348) --- 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 4e95683308..2e33d03088 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -23,7 +23,7 @@ namespace { //------------------------------------------------------------------------------ // clang-format off // NOLINTNEXTLINE(readability-identifier-naming) -char const* const versionString = "3.2.0-rc1" +char const* const versionString = "3.2.0-rc2" // clang-format on ; From f9551ac5ca08bb851b2a17d2f5259de13c105876 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 27 May 2026 20:24:18 +0100 Subject: [PATCH 042/135] style: Run shfmt on workflows, actions and markdown bash code (#7333) --- .github/actions/build-deps/action.yml | 18 +- .github/actions/generate-version/action.yml | 10 +- .github/scripts/format-inline-bash.py | 403 ++++++++++++++++++ .github/workflows/build-nix-image.yml | 4 +- .github/workflows/check-pr-description.yml | 10 +- .github/workflows/check-pr-title.yml | 2 +- .github/workflows/on-pr.yml | 8 +- .github/workflows/pre-commit.yml | 2 +- .../workflows/reusable-build-docker-image.yml | 2 +- .../workflows/reusable-build-test-config.yml | 76 ++-- .../workflows/reusable-check-levelization.yml | 10 +- .github/workflows/reusable-check-rename.yml | 10 +- .github/workflows/reusable-clang-tidy.yml | 48 +-- .github/workflows/reusable-package.yml | 2 +- .../workflows/reusable-strategy-matrix.yml | 2 +- .pre-commit-config.yaml | 13 + BUILD.md | 6 +- cspell.config.yaml | 1 + include/xrpl/protocol_autogen/README.md | 4 +- package/README.md | 18 +- 20 files changed, 533 insertions(+), 116 deletions(-) create mode 100755 .github/scripts/format-inline-bash.py diff --git a/.github/actions/build-deps/action.yml b/.github/actions/build-deps/action.yml index 9d52be1998..0891d56dfa 100644 --- a/.github/actions/build-deps/action.yml +++ b/.github/actions/build-deps/action.yml @@ -37,12 +37,12 @@ runs: run: | echo 'Installing dependencies.' conan install \ - --profile ci \ - --build="${BUILD_OPTION}" \ - --options:host='&:tests=True' \ - --options:host='&:xrpld=True' \ - --settings:all build_type="${BUILD_TYPE}" \ - --conf:all tools.build:jobs=${BUILD_NPROC} \ - --conf:all tools.build:verbosity="${LOG_VERBOSITY}" \ - --conf:all tools.compilation:verbosity="${LOG_VERBOSITY}" \ - . + --profile ci \ + --build="${BUILD_OPTION}" \ + --options:host='&:tests=True' \ + --options:host='&:xrpld=True' \ + --settings:all build_type="${BUILD_TYPE}" \ + --conf:all tools.build:jobs=${BUILD_NPROC} \ + --conf:all tools.build:verbosity="${LOG_VERBOSITY}" \ + --conf:all tools.compilation:verbosity="${LOG_VERBOSITY}" \ + . diff --git a/.github/actions/generate-version/action.yml b/.github/actions/generate-version/action.yml index 8edb7920c6..50b3166596 100644 --- a/.github/actions/generate-version/action.yml +++ b/.github/actions/generate-version/action.yml @@ -15,7 +15,7 @@ runs: shell: bash env: VERSION: ${{ github.ref_name }} - run: echo "VERSION=${VERSION}" >> "${GITHUB_ENV}" + run: echo "VERSION=${VERSION}" >>"${GITHUB_ENV}" # When a tag is not pushed, then the version (e.g. 1.2.3-b0) is extracted # from the BuildInfo.cpp file and the shortened commit hash appended to it. @@ -28,17 +28,17 @@ runs: echo 'Extracting version from BuildInfo.cpp.' VERSION="$(cat src/libxrpl/protocol/BuildInfo.cpp | grep "versionString =" | awk -F '"' '{print $2}')" if [[ -z "${VERSION}" ]]; then - echo 'Unable to extract version from BuildInfo.cpp.' - exit 1 + echo 'Unable to extract version from BuildInfo.cpp.' + exit 1 fi echo 'Appending shortened commit hash to version.' SHA='${{ github.sha }}' VERSION="${VERSION}+${SHA:0:7}" - echo "VERSION=${VERSION}" >> "${GITHUB_ENV}" + echo "VERSION=${VERSION}" >>"${GITHUB_ENV}" - name: Output version id: version shell: bash - run: echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" + run: echo "version=${VERSION}" >>"${GITHUB_OUTPUT}" diff --git a/.github/scripts/format-inline-bash.py b/.github/scripts/format-inline-bash.py new file mode 100755 index 0000000000..423c78109c --- /dev/null +++ b/.github/scripts/format-inline-bash.py @@ -0,0 +1,403 @@ +#!/usr/bin/env python3 + +""" +Format embedded shell snippets using the shfmt hook configured in +.pre-commit-config.yaml. + +Two shapes are recognised: + +* YAML workflow/action files: literal block-scalar runs (`run: |`) and + single-line runs (`run: some command`). A single-line run is upgraded to + a `run: |` block scalar if shfmt's output spans multiple lines. + +* Markdown files: ``` ```bash ``` fenced code blocks. + +Any block that shfmt cannot parse is skipped with a warning on stderr, so +the file is left untouched and surrounding blocks still get formatted. + +For each occurrence the body is dedented, written to a temp .sh file, +formatted via `pre-commit run shfmt --files ` (falling back to +`prek`), then re-indented and written back in place. + +When invoked without arguments, every .yml/.yaml under .github/ plus every +.md file in the repo is scanned. When invoked with file arguments (the +pre-commit case), only those files are processed. +""" + +from __future__ import annotations + +import re +import shutil +import subprocess +import sys +import tempfile +from dataclasses import dataclass +from pathlib import Path +from typing import Union + +REPO = Path(__file__).resolve().parents[2] + +_HOOK_RUNNER = next((cmd for cmd in ("pre-commit", "prek") if shutil.which(cmd)), None) +if _HOOK_RUNNER is None: + sys.exit("error: neither `pre-commit` nor `prek` found on PATH") + +RUN_BLOCK_RE = re.compile(r"^(?P[ \t]*(?:- )?)run:[ \t]*\|[+-]?[ \t]*$") +RUN_INLINE_RE = re.compile( + r"^(?P[ \t]*(?:- )?)run:[ \t]+" r"(?P(?!\|[+-]?[ \t]*$)\S.*?)[ \t]*$" +) +MD_BASH_OPEN_RE = re.compile(r"^(?P[ ]{0,3})`{3}bash[ \t]*$") +MD_FENCE_CLOSE_RE = re.compile(r"^[ ]{0,3}`{3,}[ \t]*$") + + +@dataclass(frozen=True) +class BlockRun: + """A `run: |` block scalar; `body_start:body_end` slices into `lines`.""" + + body_start: int + body_end: int + body_indent: int + + +@dataclass(frozen=True) +class InlineRun: + """A single-line `run: value` at `line_idx`.""" + + line_idx: int + prefix: str + value: str + + +@dataclass(frozen=True) +class MdBashBlock: + """A markdown ``` ```bash ``` fenced code block. + + `body_start:body_end` slices into the file's lines; `open_line_idx` + points at the opening fence line. + """ + + open_line_idx: int + body_start: int + body_end: int + body_indent: int + + +RunItem = Union[BlockRun, InlineRun] + + +def _scan_block_body( + lines: list[str], body_start: int, run_col: int +) -> tuple[int | None, int]: + """Locate the body of a `run: |` block scalar starting at `body_start`. + + Returns `(body_indent, scan_end)`. `scan_end` is the line index where the + outer scanner should resume. `body_indent` is `None` when no body is + present (the scalar is empty, or the next non-blank line has indent + `<= run_col`). + """ + body_indent: int | None = None + scan_end = len(lines) + for idx in range(body_start, len(lines)): + line = lines[idx] + if line.strip() == "": + continue + indent = len(line) - len(line.lstrip(" ")) + if body_indent is None: + if indent > run_col: + body_indent = indent + else: + scan_end = idx + break + elif indent < body_indent: + scan_end = idx + break + if body_indent is not None: + while scan_end > body_start and lines[scan_end - 1].strip() == "": + scan_end -= 1 + if scan_end <= body_start: + body_indent = None + return body_indent, scan_end + + +def find_run_blocks(lines: list[str]) -> list[RunItem]: + """Return run items in document order.""" + items: list[RunItem] = [] + line_idx = 0 + while line_idx < len(lines): + line = lines[line_idx] + if block_match := RUN_BLOCK_RE.match(line): + run_col = len(block_match.group("prefix")) + body_start = line_idx + 1 + body_indent, scan_end = _scan_block_body(lines, body_start, run_col) + if body_indent is not None: + items.append( + BlockRun( + body_start=body_start, + body_end=scan_end, + body_indent=body_indent, + ) + ) + line_idx = scan_end + continue + if inline_match := RUN_INLINE_RE.match(line): + items.append( + InlineRun( + line_idx=line_idx, + prefix=inline_match.group("prefix"), + value=inline_match.group("value"), + ) + ) + line_idx += 1 + return items + + +def find_md_bash_blocks(lines: list[str]) -> list[MdBashBlock]: + """Return ``` ```bash ``` fenced code blocks in document order.""" + blocks: list[MdBashBlock] = [] + line_idx = 0 + while line_idx < len(lines): + open_match = MD_BASH_OPEN_RE.match(lines[line_idx]) + if not open_match: + line_idx += 1 + continue + body_start = line_idx + 1 + close_idx = next( + ( + j + for j in range(body_start, len(lines)) + if MD_FENCE_CLOSE_RE.match(lines[j]) + ), + None, + ) + if close_idx is None: + line_idx = body_start + continue + body = lines[body_start:close_idx] + non_blank = [b for b in body if b.strip()] + body_indent = ( + min(len(b) - len(b.lstrip(" ")) for b in non_blank) + if non_blank + else len(open_match.group("indent")) + ) + blocks.append( + MdBashBlock( + open_line_idx=line_idx, + body_start=body_start, + body_end=close_idx, + body_indent=body_indent, + ) + ) + line_idx = close_idx + 1 + return blocks + + +def dedent(lines: list[str], n: int) -> list[str]: + pad = " " * n + return [ + ( + "" + if line.strip() == "" + else (line[n:] if line.startswith(pad) else line.lstrip(" ")) + ) + for line in lines + ] + + +def reindent(lines: list[str], n: int) -> list[str]: + pad = " " * n + return [pad + line if line else "" for line in lines] + + +_SHFMT_ERR_RE = re.compile(r"\.sh:\d+:\d+:\s") +_GHA_EXPR_RE = re.compile(r"\$\{\{.*?\}\}", re.DOTALL) +_GHA_PLACEHOLDER_RE = re.compile(r"__GHA_EXPR_(\d+)__") + + +def _encode_gha_exprs(text: str) -> tuple[str, list[str]]: + """Replace `${{ ... }}` expressions with bash-safe placeholder identifiers.""" + exprs: list[str] = [] + + def repl(match: re.Match[str]) -> str: + exprs.append(match.group(0)) + return f"__GHA_EXPR_{len(exprs) - 1}__" + + return _GHA_EXPR_RE.sub(repl, text), exprs + + +def _decode_gha_exprs(text: str, exprs: list[str]) -> str: + """Restore `${{ ... }}` expressions from placeholder identifiers.""" + return _GHA_PLACEHOLDER_RE.sub(lambda m: exprs[int(m.group(1))], text) + + +def shfmt_via_hook(tmp_path: Path) -> tuple[bool, str]: + # `${{ ... }}` is not valid shell, so swap it for a placeholder identifier + # that shfmt can parse, then restore it after formatting. + encoded, exprs = _encode_gha_exprs(tmp_path.read_text()) + if exprs: + tmp_path.write_text(encoded) + res = subprocess.run( + [_HOOK_RUNNER, "run", "shfmt", "--files", str(tmp_path)], + cwd=REPO, + capture_output=True, + text=True, + ) + output = res.stdout + res.stderr + # shfmt emits parse errors as "::: ". + parse_err = bool(_SHFMT_ERR_RE.search(output)) + # A non-zero exit that is neither a parse error nor pre-commit's "I had + # to modify files" signal means the hook itself failed to run (missing + # binary, install failure, bad config, ...). Surface that loudly rather + # than silently treating it as a no-op. + if ( + res.returncode != 0 + and not parse_err + and "files were modified by this hook" not in output + ): + sys.exit( + f"error: `{_HOOK_RUNNER} run shfmt` failed with exit {res.returncode}:\n{output}" + ) + if exprs and not parse_err: + tmp_path.write_text(_decode_gha_exprs(tmp_path.read_text(), exprs)) + return not parse_err, output + + +def _skip(path: Path, where: int, kind: str, output: str) -> None: + print( + f" shfmt could not parse {kind} at {path}:{where + 1} — skipped", + file=sys.stderr, + ) + print(f" {output.strip()}", file=sys.stderr) + + +def process_yaml_file(path: Path, tmp_path: Path) -> int: + text = path.read_text() + had_nl = text.endswith("\n") + lines = text.split("\n") + if had_nl: + lines = lines[:-1] + items = find_run_blocks(lines) + if not items: + return 0 + changed = 0 + # Process in reverse so earlier indices remain valid as we splice. + for item in reversed(items): + if isinstance(item, BlockRun): + body = lines[item.body_start : item.body_end] + tmp_path.write_text("\n".join(dedent(body, item.body_indent)) + "\n") + ok, output = shfmt_via_hook(tmp_path) + if not ok: + _skip(path, item.body_start, "block", output) + continue + formatted = tmp_path.read_text().rstrip("\n") + new_body = reindent(formatted.split("\n"), item.body_indent) + if new_body != body: + lines[item.body_start : item.body_end] = new_body + changed += 1 + else: + tmp_path.write_text(item.value + "\n") + ok, output = shfmt_via_hook(tmp_path) + if not ok: + _skip(path, item.line_idx, "inline run", output) + continue + formatted = tmp_path.read_text().rstrip("\n") + if formatted == item.value: + continue + formatted_lines = formatted.split("\n") + if len(formatted_lines) == 1: + lines[item.line_idx] = f"{item.prefix}run: {formatted}" + else: + body_indent = len(item.prefix) + 2 + lines[item.line_idx : item.line_idx + 1] = [ + f"{item.prefix}run: |", + *reindent(formatted_lines, body_indent), + ] + changed += 1 + new_text = "\n".join(lines) + ("\n" if had_nl else "") + if new_text != text: + path.write_text(new_text) + return changed + + +def process_md_file(path: Path, tmp_path: Path) -> int: + text = path.read_text() + had_nl = text.endswith("\n") + lines = text.split("\n") + if had_nl: + lines = lines[:-1] + blocks = find_md_bash_blocks(lines) + if not blocks: + return 0 + changed = 0 + for block in reversed(blocks): + body = lines[block.body_start : block.body_end] + tmp_path.write_text("\n".join(dedent(body, block.body_indent)) + "\n") + ok, output = shfmt_via_hook(tmp_path) + if not ok: + _skip(path, block.open_line_idx, "```bash block", output) + continue + formatted = tmp_path.read_text().rstrip("\n") + formatted_lines = formatted.split("\n") if formatted else [] + new_body = reindent(formatted_lines, block.body_indent) + if new_body != body: + lines[block.body_start : block.body_end] = new_body + changed += 1 + new_text = "\n".join(lines) + ("\n" if had_nl else "") + if new_text != text: + path.write_text(new_text) + return changed + + +def process_file(path: Path, tmp_path: Path) -> int: + if path.suffix in (".yml", ".yaml"): + return process_yaml_file(path, tmp_path) + if path.suffix == ".md": + return process_md_file(path, tmp_path) + return 0 + + +def gather_files(argv: list[str]) -> list[Path]: + """Return YAML workflow/action files and markdown files that we should + process — either the paths in `argv` or, when `argv` is empty, every + such file in the repo (skipping `external/`).""" + if argv: + candidates: list[Path] = [ + (REPO / a).resolve() if not Path(a).is_absolute() else Path(a) for a in argv + ] + else: + gh = REPO / ".github" + candidates = [ + *gh.rglob("*.yml"), + *gh.rglob("*.yaml"), + *( + p + for p in REPO.rglob("*.md") + if "external" not in p.relative_to(REPO).parts + ), + ] + return sorted( + p + for p in candidates + if p.exists() + and ( + (p.suffix in (".yml", ".yaml") and ".github" in p.parts) + or p.suffix == ".md" + ) + ) + + +def main(argv: list[str]) -> int: + files = gather_files(argv) + if not files: + return 0 + with tempfile.TemporaryDirectory(prefix="format-inline-bash-") as tmpdir: + tmp_path = Path(tmpdir) / "shfmt.sh" + total = 0 + for f in files: + n = process_file(f, tmp_path) + if n: + print(f"{f.relative_to(REPO)}: reformatted {n} block(s)") + total += n + return 1 if total else 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/.github/workflows/build-nix-image.yml b/.github/workflows/build-nix-image.yml index edd35132fa..bae4cfd437 100644 --- a/.github/workflows/build-nix-image.yml +++ b/.github/workflows/build-nix-image.yml @@ -100,8 +100,8 @@ jobs: - name: Create multi-arch manifests run: | - for tag in $(jq -cr '.tags[]' <<< "$DOCKER_METADATA_OUTPUT_JSON"); do - docker buildx imagetools create -t "$tag" "${tag}-amd64" "${tag}-arm64" + for tag in $(jq -cr '.tags[]' <<<"$DOCKER_METADATA_OUTPUT_JSON"); do + docker buildx imagetools create -t "$tag" "${tag}-amd64" "${tag}-arm64" done - name: Inspect image diff --git a/.github/workflows/check-pr-description.yml b/.github/workflows/check-pr-description.yml index f6eee50291..3f345a9dcb 100644 --- a/.github/workflows/check-pr-description.yml +++ b/.github/workflows/check-pr-description.yml @@ -20,11 +20,11 @@ jobs: env: PR_BODY: ${{ github.event.pull_request.body }} if: ${{ github.event_name == 'pull_request' }} - run: printenv PR_BODY > pr_body.md + run: printenv PR_BODY >pr_body.md - name: Check PR description differs from template if: ${{ github.event_name == 'pull_request' }} - run: > - python .github/scripts/check-pr-description.py - --template-file .github/pull_request_template.md - --pr-body-file pr_body.md + run: | + python .github/scripts/check-pr-description.py \ + --template-file .github/pull_request_template.md \ + --pr-body-file pr_body.md diff --git a/.github/workflows/check-pr-title.yml b/.github/workflows/check-pr-title.yml index 5631950df6..79962e0620 100644 --- a/.github/workflows/check-pr-title.yml +++ b/.github/workflows/check-pr-title.yml @@ -11,4 +11,4 @@ on: jobs: check_title: if: ${{ github.event.pull_request.draft != true }} - uses: XRPLF/actions/.github/workflows/check-pr-title.yml@291206777251b4d493641b5afbdf7c23009d2988 + uses: XRPLF/actions/.github/workflows/check-pr-title.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81 diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index ca715e0376..db3c8667e5 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -98,7 +98,7 @@ jobs: READY: ${{ contains(github.event.pull_request.labels.*.name, 'Ready to merge') }} MERGE: ${{ github.event_name == 'merge_group' }} run: | - echo "go=${{ (env.DRAFT != 'true' && env.READY == 'true') || env.FILES == 'true' || env.MERGE == 'true' }}" >> "${GITHUB_OUTPUT}" + echo "go=${{ (env.DRAFT != 'true' && env.READY == 'true') || env.FILES == 'true' || env.MERGE == 'true' }}" >>"${GITHUB_OUTPUT}" cat "${GITHUB_OUTPUT}" outputs: go: ${{ steps.go.outputs.go == 'true' }} @@ -168,9 +168,9 @@ jobs: PR_URL: ${{ github.event.pull_request.html_url }} run: | gh api --method POST -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" \ - /repos/xrplf/clio/dispatches -f "event_type=check_libxrpl" \ - -F "client_payload[ref]=${{ needs.upload-recipe.outputs.recipe_ref }}" \ - -F "client_payload[pr_url]=${PR_URL}" + /repos/xrplf/clio/dispatches -f "event_type=check_libxrpl" \ + -F "client_payload[ref]=${{ needs.upload-recipe.outputs.recipe_ref }}" \ + -F "client_payload[pr_url]=${PR_URL}" passed: if: failure() || cancelled() diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 2f15b82266..de6a4f40b4 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -14,7 +14,7 @@ on: jobs: # Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks. run-hooks: - uses: XRPLF/actions/.github/workflows/pre-commit.yml@5e942d61bf32f7557a7c159cfac4712a687b3e3a + uses: XRPLF/actions/.github/workflows/pre-commit.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81 with: runs_on: ubuntu-latest container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }' diff --git a/.github/workflows/reusable-build-docker-image.yml b/.github/workflows/reusable-build-docker-image.yml index 5b555b713f..c3795e56fa 100644 --- a/.github/workflows/reusable-build-docker-image.yml +++ b/.github/workflows/reusable-build-docker-image.yml @@ -53,7 +53,7 @@ jobs: env: PLATFORM: ${{ inputs.platform }} run: | - echo "arch=${PLATFORM##*/}" >> $GITHUB_OUTPUT + echo "arch=${PLATFORM##*/}" >>$GITHUB_OUTPUT - name: Set up Docker Buildx uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 4f9b926e98..31457bb892 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -113,7 +113,7 @@ jobs: - name: Set ccache log file if: ${{ inputs.ccache_enabled && runner.debug == '1' }} - run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >> "${GITHUB_ENV}" + run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >>"${GITHUB_ENV}" - name: Print build environment uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574 @@ -146,11 +146,11 @@ jobs: CMAKE_ARGS: ${{ inputs.cmake_args }} run: | cmake \ - -G '${{ runner.os == 'Windows' && 'Visual Studio 17 2022' || 'Ninja' }}' \ - -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ - -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ - ${CMAKE_ARGS} \ - .. + -G '${{ runner.os == 'Windows' && 'Visual Studio 17 2022' || 'Ninja' }}' \ + -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ + ${CMAKE_ARGS} \ + .. - name: Check protocol autogen files are up-to-date working-directory: ${{ env.BUILD_DIR }} @@ -172,10 +172,10 @@ jobs: cmake --build . --target code_gen DIFF=$(git -C .. status --porcelain -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen) if [ -n "${DIFF}" ]; then - echo "::error::Generated protocol files are out of date" - git -C .. diff -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen - echo "${MESSAGE}" - exit 1 + echo "::error::Generated protocol files are out of date" + git -C .. diff -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen + echo "${MESSAGE}" + exit 1 fi - name: Build the binary @@ -186,18 +186,18 @@ jobs: CMAKE_TARGET: ${{ inputs.cmake_target }} run: | cmake \ - --build . \ - --config "${BUILD_TYPE}" \ - --parallel "${BUILD_NPROC}" \ - --target "${CMAKE_TARGET}" + --build . \ + --config "${BUILD_TYPE}" \ + --parallel "${BUILD_NPROC}" \ + --target "${CMAKE_TARGET}" - name: Show ccache statistics if: ${{ inputs.ccache_enabled }} run: | ccache --show-stats -vv if [ '${{ runner.debug }}' = '1' ]; then - cat "${CCACHE_LOGFILE}" - curl ${CCACHE_REMOTE_STORAGE%|*}/status || true + cat "${CCACHE_LOGFILE}" + curl ${CCACHE_REMOTE_STORAGE%|*}/status || true fi - name: Upload the binary (Linux) @@ -214,7 +214,7 @@ jobs: working-directory: ${{ env.BUILD_DIR }} run: | set -o pipefail - ./xrpld --definitions | python3 -m json.tool > server_definitions.json + ./xrpld --definitions | python3 -m json.tool >server_definitions.json - name: Upload server definitions if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-bookworm-gcc-13-amd64-release' }} @@ -231,10 +231,10 @@ jobs: run: | ldd ./xrpld if [ "$(ldd ./xrpld | grep -E '(libstdc\+\+|libgcc)' | wc -l)" -eq 0 ]; then - echo 'The binary is statically linked.' + echo 'The binary is statically linked.' else - echo 'The binary is dynamically linked.' - exit 1 + echo 'The binary is dynamically linked.' + exit 1 fi - name: Verify presence of instrumentation (Linux) @@ -250,12 +250,12 @@ jobs: run: | ASAN_OPTS="include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-asan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp" if [[ "${CONFIG_NAME}" == *gcc* ]]; then - ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0" + ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0" fi - echo "ASAN_OPTIONS=${ASAN_OPTS}" >> ${GITHUB_ENV} - echo "TSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-tsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >> ${GITHUB_ENV} - echo "UBSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-ubsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/ubsan.supp" >> ${GITHUB_ENV} - echo "LSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-lsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/lsan.supp" >> ${GITHUB_ENV} + echo "ASAN_OPTIONS=${ASAN_OPTS}" >>${GITHUB_ENV} + echo "TSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-tsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >>${GITHUB_ENV} + echo "UBSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-ubsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/ubsan.supp" >>${GITHUB_ENV} + echo "LSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-lsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/lsan.supp" >>${GITHUB_ENV} - name: Run the separate tests if: ${{ !inputs.build_only }} @@ -266,9 +266,9 @@ jobs: PARALLELISM: ${{ runner.os == 'Windows' && '1' || steps.nproc.outputs.nproc }} run: | ctest \ - --output-on-failure \ - -C "${BUILD_TYPE}" \ - -j "${PARALLELISM}" + --output-on-failure \ + -C "${BUILD_TYPE}" \ + -j "${PARALLELISM}" - name: Run the embedded tests if: ${{ !inputs.build_only }} @@ -278,7 +278,7 @@ jobs: run: | set -o pipefail # Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness - [ "$COVERAGE_ENABLED" = "true" ] && BUILD_NPROC=$(( BUILD_NPROC - 2 )) + [ "$COVERAGE_ENABLED" = "true" ] && BUILD_NPROC=$((BUILD_NPROC - 2)) ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log - name: Show test failure summary @@ -287,19 +287,19 @@ jobs: WORKING_DIR: ${{ runner.os == 'Windows' && format('{0}\{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }} run: | if [ ! -d "${WORKING_DIR}" ]; then - echo "Working directory '${WORKING_DIR}' does not exist." - exit 0 + echo "Working directory '${WORKING_DIR}' does not exist." + exit 0 fi cd "${WORKING_DIR}" if [ ! -f unittest.log ]; then - echo "unittest.log not found; embedded tests may not have run." - exit 0 + echo "unittest.log not found; embedded tests may not have run." + exit 0 fi if ! grep -E "failed" unittest.log; then - echo "Log present but no failure lines found in unittest.log." + echo "Log present but no failure lines found in unittest.log." fi - name: Debug failure (Linux) if: ${{ failure() && runner.os == 'Linux' && !inputs.build_only }} @@ -317,10 +317,10 @@ jobs: BUILD_TYPE: ${{ inputs.build_type }} run: | cmake \ - --build . \ - --config "${BUILD_TYPE}" \ - --parallel "${BUILD_NPROC}" \ - --target coverage + --build . \ + --config "${BUILD_TYPE}" \ + --parallel "${BUILD_NPROC}" \ + --target coverage - name: Upload coverage report if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }} diff --git a/.github/workflows/reusable-check-levelization.yml b/.github/workflows/reusable-check-levelization.yml index 4efe3e1138..b5d57a177a 100644 --- a/.github/workflows/reusable-check-levelization.yml +++ b/.github/workflows/reusable-check-levelization.yml @@ -38,9 +38,9 @@ jobs: run: | DIFF=$(git status --porcelain) if [ -n "${DIFF}" ]; then - # Print the differences to give the contributor a hint about what to - # expect when running levelization on their own machine. - git diff - echo "${MESSAGE}" - exit 1 + # Print the differences to give the contributor a hint about what to + # expect when running levelization on their own machine. + git diff + echo "${MESSAGE}" + exit 1 fi diff --git a/.github/workflows/reusable-check-rename.yml b/.github/workflows/reusable-check-rename.yml index 56a1a3e637..7aa5b80594 100644 --- a/.github/workflows/reusable-check-rename.yml +++ b/.github/workflows/reusable-check-rename.yml @@ -48,9 +48,9 @@ jobs: run: | DIFF=$(git status --porcelain) if [ -n "${DIFF}" ]; then - # Print the differences to give the contributor a hint about what to - # expect when running the renaming scripts on their own machine. - git diff - echo "${MESSAGE}" - exit 1 + # Print the differences to give the contributor a hint about what to + # expect when running the renaming scripts on their own machine. + git diff + echo "${MESSAGE}" + exit 1 fi diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index e01a50cf6d..8be1db5fb2 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -70,13 +70,13 @@ jobs: working-directory: ${{ env.BUILD_DIR }} run: | cmake \ - -G 'Ninja' \ - -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ - -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ - -Dtests=ON \ - -Dwerr=ON \ - -Dxrpld=ON \ - .. + -G 'Ninja' \ + -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ + -Dtests=ON \ + -Dwerr=ON \ + -Dxrpld=ON \ + .. # clang-tidy needs headers generated from proto files - name: Build libxrpl.libpb @@ -133,7 +133,7 @@ jobs: - name: Write issue header if: ${{ steps.run_clang_tidy.outcome != 'success' }} run: | - cat > "${ISSUE_FILE}" <"${ISSUE_FILE}" < filtered-output.txt || true + # Extract lines containing 'error:', 'warning:', or 'note:' + grep -E '(error:|warning:|note:)' "${OUTPUT_FILE}" >filtered-output.txt || true - # If filtered output is empty, use original (might be a different error format) - if [ ! -s filtered-output.txt ]; then - cp "${OUTPUT_FILE}" filtered-output.txt - fi + # If filtered output is empty, use original (might be a different error format) + if [ ! -s filtered-output.txt ]; then + cp "${OUTPUT_FILE}" filtered-output.txt + fi - # Truncate if too large - head -c 60000 filtered-output.txt >> "${ISSUE_FILE}" - if [ "$(wc -c < filtered-output.txt)" -gt 60000 ]; then - echo "" >> "${ISSUE_FILE}" - echo "... (output truncated, see artifacts for full output)" >> "${ISSUE_FILE}" - fi + # Truncate if too large + head -c 60000 filtered-output.txt >>"${ISSUE_FILE}" + if [ "$(wc -c >"${ISSUE_FILE}" + echo "... (output truncated, see artifacts for full output)" >>"${ISSUE_FILE}" + fi - rm filtered-output.txt + rm filtered-output.txt else - echo "No output file found" >> "${ISSUE_FILE}" + echo "No output file found" >>"${ISSUE_FILE}" fi - name: Append issue footer if: ${{ steps.run_clang_tidy.outcome != 'success' }} run: | - cat >> "${ISSUE_FILE}" <>"${ISSUE_FILE}" <> "${GITHUB_OUTPUT}" + ./generate.py --packaging --config=linux.json >>"${GITHUB_OUTPUT}" generate-version: runs-on: ubuntu-latest diff --git a/.github/workflows/reusable-strategy-matrix.yml b/.github/workflows/reusable-strategy-matrix.yml index b1232a138f..62d65ad3fa 100644 --- a/.github/workflows/reusable-strategy-matrix.yml +++ b/.github/workflows/reusable-strategy-matrix.yml @@ -42,4 +42,4 @@ jobs: env: GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}.json', inputs.os) || '' }} GENERATE_OPTION: ${{ inputs.strategy_matrix == 'all' && '--all' || '' }} - run: ./generate.py ${GENERATE_OPTION} ${GENERATE_CONFIG} >> "${GITHUB_OUTPUT}" + run: ./generate.py ${GENERATE_OPTION} ${GENERATE_CONFIG} >>"${GITHUB_OUTPUT}" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 23441c9dde..c9dec89435 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -66,6 +66,19 @@ repos: - id: shfmt args: [--write, --indent=4, --case-indent=true] + - repo: local + hooks: + - id: format-inline-bash-workflows + name: "format `run:` blocks in workflows/actions" + entry: ./.github/scripts/format-inline-bash.py + language: python + files: ^\.github/(workflows|actions)/.*\.ya?ml$ + - id: format-inline-bash-markdown + name: "format ```bash blocks in markdown" + entry: ./.github/scripts/format-inline-bash.py + language: python + files: \.md$ + - repo: https://github.com/streetsidesoftware/cspell-cli rev: 4643f154907327ee0a2c7038f0296e0dd77d9776 # frozen: v10.0.0 hooks: diff --git a/BUILD.md b/BUILD.md index a1163dbfcc..1d3fc8f774 100644 --- a/BUILD.md +++ b/BUILD.md @@ -151,8 +151,8 @@ git init git remote add origin git@github.com:XRPLF/conan-center-index.git git sparse-checkout init for recipe in "${recipes[@]}"; do - echo "Checking out recipe '${recipe}'..." - git sparse-checkout add recipes/${recipe} + echo "Checking out recipe '${recipe}'..." + git sparse-checkout add recipes/${recipe} done git fetch origin master git checkout master @@ -180,7 +180,7 @@ the new recipe will be automatically pulled from the official Conan Center. If you see an error similar to the following after running `conan profile show`: -```bash +```text ERROR: Invalid setting '17' is not a valid 'settings.compiler.version' value. Possible values are ['5.0', '5.1', '6.0', '6.1', '7.0', '7.3', '8.0', '8.1', '9.0', '9.1', '10.0', '11.0', '12.0', '13', '13.0', '13.1', '14', '14.0', '15', diff --git a/cspell.config.yaml b/cspell.config.yaml index f26268c804..ed936941e4 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -93,6 +93,7 @@ words: - daria - dcmake - dearmor + - dedented - deleteme - demultiplexer - deserializaton diff --git a/include/xrpl/protocol_autogen/README.md b/include/xrpl/protocol_autogen/README.md index 860ed1a242..608ffed085 100644 --- a/include/xrpl/protocol_autogen/README.md +++ b/include/xrpl/protocol_autogen/README.md @@ -15,8 +15,8 @@ Generation requires a one-time setup step to create a virtual environment and install Python dependencies, followed by running the generation target: ```bash -cmake --build . --target setup_code_gen # create venv and install dependencies (once) -cmake --build . --target code_gen # generate code +cmake --build . --target setup_code_gen # create venv and install dependencies (once) +cmake --build . --target code_gen # generate code ``` By default, `CODEGEN_VENV_DIR` points to `.venv` in the project root. The diff --git a/package/README.md b/package/README.md index 2089e32e64..867ca273b4 100644 --- a/package/README.md +++ b/package/README.md @@ -74,10 +74,10 @@ VERSION=2.4.0-local PKG_RELEASE=1 docker run --rm \ - -v "$(pwd):/src" \ - -w /src \ - "$IMAGE" \ - ./package/build_pkg.sh --pkg-version "$VERSION" --pkg-release "$PKG_RELEASE" + -v "$(pwd):/src" \ + -w /src \ + "$IMAGE" \ + ./package/build_pkg.sh --pkg-version "$VERSION" --pkg-release "$PKG_RELEASE" # Output: # build/debbuild/*.deb (DEB + dbgsym .ddeb) @@ -92,12 +92,12 @@ needed, but the host toolchain replaces the pinned CI image: ```bash cmake \ - -Dxrpld=ON \ - -Dxrpld_version=2.4.0-local \ - -Dtests=OFF \ - .. + -Dxrpld=ON \ + -Dxrpld_version=2.4.0-local \ + -Dtests=OFF \ + .. -cmake --build . --target package # deb on Debian/Ubuntu, rpm on RHEL +cmake --build . --target package # deb on Debian/Ubuntu, rpm on RHEL ``` The `cmake/XrplPackaging.cmake` module defines the target only if at least one From 2f3558c610ee3d446b1c68a003442e63d4abbdf5 Mon Sep 17 00:00:00 2001 From: Bart Date: Thu, 28 May 2026 10:57:29 -0400 Subject: [PATCH 043/135] ci: Run PR title and description checks on staging and release branches (#7331) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- .github/workflows/check-pr-description.yml | 13 +++++++++++-- .github/workflows/check-pr-title.yml | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check-pr-description.yml b/.github/workflows/check-pr-description.yml index 3f345a9dcb..ff28220171 100644 --- a/.github/workflows/check-pr-description.yml +++ b/.github/workflows/check-pr-description.yml @@ -5,8 +5,17 @@ on: types: - checks_requested pull_request: - types: [opened, edited, reopened, synchronize, ready_for_review] - branches: [develop] + types: + - opened + - edited + - reopened + - synchronize + - ready_for_review + branches: + - develop + - "release-*" + - "release/*" + - "staging/*" jobs: check_description: diff --git a/.github/workflows/check-pr-title.yml b/.github/workflows/check-pr-title.yml index 79962e0620..4b5f679df1 100644 --- a/.github/workflows/check-pr-title.yml +++ b/.github/workflows/check-pr-title.yml @@ -5,8 +5,17 @@ on: types: - checks_requested pull_request: - types: [opened, edited, reopened, synchronize, ready_for_review] - branches: [develop] + types: + - opened + - edited + - reopened + - synchronize + - ready_for_review + branches: + - develop + - "release-*" + - "release/*" + - "staging/*" jobs: check_title: From 763dd503be465e92d3ec4bf17a13b4aa366080a8 Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Fri, 29 May 2026 20:16:25 -0400 Subject: [PATCH 044/135] fix: Add zero domainID check for permissionedDomain (#7362) --- .../ledger/helpers/PermissionedDEXHelpers.cpp | 12 +++++++ .../tx/transactors/dex/OfferCreate.cpp | 6 ++++ .../tx/transactors/payment/Payment.cpp | 6 ++++ src/test/app/PermissionedDEX_test.cpp | 34 +++++++++++++++++++ 4 files changed, 58 insertions(+) diff --git a/src/libxrpl/ledger/helpers/PermissionedDEXHelpers.cpp b/src/libxrpl/ledger/helpers/PermissionedDEXHelpers.cpp index 0151c5b2df..f8653f7111 100644 --- a/src/libxrpl/ledger/helpers/PermissionedDEXHelpers.cpp +++ b/src/libxrpl/ledger/helpers/PermissionedDEXHelpers.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include #include @@ -19,6 +21,16 @@ namespace xrpl::permissioned_dex { bool accountInDomain(ReadView const& view, AccountID const& account, Domain const& domainID) { + // Avoid constructing a zero-key PermissionedDomain keylet. + // keylet::permissionedDomain(uint256) uses the DomainID as the ledger key. + if (view.rules().enabled(fixCleanup3_2_0) && domainID == beast::kZero) + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::permissioned_dex::accountInDomain : domainID is zero"); + return false; + // LCOV_EXCL_STOP + } + auto const sleDomain = view.read(keylet::permissionedDomain(domainID)); if (!sleDomain) return false; diff --git a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp index 8bf69d25c0..f982837c5e 100644 --- a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp @@ -94,6 +94,12 @@ OfferCreate::preflight(PreflightContext const& ctx) if (tx.isFlag(tfHybrid) && !tx.isFieldPresent(sfDomainID)) return temINVALID_FLAG; + // A zero DomainID is invalid for a PermissionedDomain ledger entry because + // keylet::permissionedDomain(uint256) uses the DomainID as the ledger key. + if (auto const domainID = tx[~sfDomainID]; + ctx.rules.enabled(fixCleanup3_2_0) && domainID && *domainID == beast::kZero) + return temMALFORMED; + bool const bImmediateOrCancel(tx.isFlag(tfImmediateOrCancel)); bool const bFillOrKill(tx.isFlag(tfFillOrKill)); diff --git a/src/libxrpl/tx/transactors/payment/Payment.cpp b/src/libxrpl/tx/transactors/payment/Payment.cpp index 1848d34786..d7ad5d3b3c 100644 --- a/src/libxrpl/tx/transactors/payment/Payment.cpp +++ b/src/libxrpl/tx/transactors/payment/Payment.cpp @@ -125,6 +125,12 @@ Payment::preflight(PreflightContext const& ctx) if (!mpTokensV2 && isDstMPT && ctx.tx.isFieldPresent(sfPaths)) return temMALFORMED; + // A zero DomainID is invalid for a PermissionedDomain ledger entry because + // keylet::permissionedDomain(uint256) uses the DomainID as the ledger key. + if (auto const domainID = tx[~sfDomainID]; + ctx.rules.enabled(fixCleanup3_2_0) && domainID && *domainID == beast::kZero) + return temMALFORMED; + bool const partialPaymentAllowed = tx.isFlag(tfPartialPayment); bool const limitQuality = tx.isFlag(tfLimitQuality); bool const defaultPathsAllowed = !tx.isFlag(tfNoRippleDirect); diff --git a/src/test/app/PermissionedDEX_test.cpp b/src/test/app/PermissionedDEX_test.cpp index c6e94d7994..a88cbaa868 100644 --- a/src/test/app/PermissionedDEX_test.cpp +++ b/src/test/app/PermissionedDEX_test.cpp @@ -197,6 +197,20 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); } + // test preflight - malformed DomainID being zero + // Only test this with fixCleanup3_2_0 enabled. Without the fix, + // an assert-enabled build can crash when Ledger::read() receives + // a zero-key PermissionedDomain keylet. + if (features[fixCleanup3_2_0]) + { + Env env(*this, features); + auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + PermissionedDEX(env); + + env(offer(bob_, XRP(10), USD(10)), Domain(uint256{}), Ter(temMALFORMED)); + env.close(); + } + // preclaim - someone outside of the domain cannot create domain offer { Env env(*this, features); @@ -396,6 +410,24 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); } + // test preflight - malformed DomainID being zero + // Only test this with fixCleanup3_2_0 enabled. Without the fix, + // an assert-enabled build can crash when Ledger::read() receives + // a zero-key PermissionedDomain keylet. + if (features[fixCleanup3_2_0]) + { + Env env(*this, features); + auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + PermissionedDEX(env); + + env(pay(bob_, alice_, USD(10)), + Path(~USD), + Sendmax(XRP(10)), + Domain(uint256{}), + Ter(temMALFORMED)); + env.close(); + } + // preclaim - cannot send payment with non existent domain { Env env(*this, features); @@ -1772,7 +1804,9 @@ public: // Test domain offer (w/o hybrid) testOfferCreate(all); + testOfferCreate(all - fixCleanup3_2_0); testPayment(all); + testPayment(all - fixCleanup3_2_0); testBookStep(all); testRippling(all); testOfferTokenIssuerInDomain(all); From 1599c1a672587a6797e6cd125bd8d319737d4418 Mon Sep 17 00:00:00 2001 From: Bart Date: Sat, 30 May 2026 14:48:59 -0400 Subject: [PATCH 045/135] refactor: Revert "perf: Remove unnecessary caches (#5439)" (#7359) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- cfg/xrpld-example.cfg | 15 +++ include/xrpl/nodestore/Database.h | 4 + .../xrpl/nodestore/detail/DatabaseNodeImp.h | 32 +++++++ .../nodestore/detail/DatabaseRotatingImp.h | 3 + src/libxrpl/nodestore/DatabaseNodeImp.cpp | 95 ++++++++++++++----- src/libxrpl/nodestore/DatabaseRotatingImp.cpp | 6 ++ src/test/app/SHAMapStore_test.cpp | 20 +++- src/xrpld/app/main/Application.cpp | 4 + src/xrpld/app/misc/SHAMapStoreImp.cpp | 26 ++++- src/xrpld/app/misc/SHAMapStoreImp.h | 4 + 10 files changed, 181 insertions(+), 28 deletions(-) diff --git a/cfg/xrpld-example.cfg b/cfg/xrpld-example.cfg index effc62c274..9e334e6f4f 100644 --- a/cfg/xrpld-example.cfg +++ b/cfg/xrpld-example.cfg @@ -953,6 +953,21 @@ # # Optional keys for NuDB and RocksDB: # +# cache_size Size of cache for database records. Default is 16384. +# Setting this value to 0 will use the default value. +# +# cache_age Length of time in minutes to keep database records +# cached. Default is 5 minutes. Setting this value to +# 0 will use the default value. +# +# Note: if cache_size or cache_age is not specified, +# default values will be used for the unspecified +# parameter. +# +# Note: the cache will not be created if online_delete +# is specified, because the rotating NodeStore does +# not use this cache). +# # fast_load Boolean. If set, load the last persisted ledger # from disk upon process start before syncing to # the network. This is likely to improve performance diff --git a/include/xrpl/nodestore/Database.h b/include/xrpl/nodestore/Database.h index ca2dde560c..438a3cc7fc 100644 --- a/include/xrpl/nodestore/Database.h +++ b/include/xrpl/nodestore/Database.h @@ -131,6 +131,10 @@ public: std::uint32_t ledgerSeq, std::function const&)>&& callback); + /** Remove expired entries from the positive and negative caches. */ + virtual void + sweep() = 0; + /** Gather statistics pertaining to read and write activities. * * @param obj Json object reference into which to place counters. diff --git a/include/xrpl/nodestore/detail/DatabaseNodeImp.h b/include/xrpl/nodestore/detail/DatabaseNodeImp.h index dd94b27075..951c60c8c7 100644 --- a/include/xrpl/nodestore/detail/DatabaseNodeImp.h +++ b/include/xrpl/nodestore/detail/DatabaseNodeImp.h @@ -22,6 +22,32 @@ public: beast::Journal j) : Database(scheduler, readThreads, config, j), backend_(std::move(backend)) { + std::optional cacheSize, cacheAge; + + if (config.exists("cache_size")) + { + cacheSize = get(config, "cache_size"); + if (cacheSize.value() < 0) + Throw("Specified negative value for cache_size"); + } + + if (config.exists("cache_age")) + { + cacheAge = get(config, "cache_age"); + if (cacheAge.value() < 0) + Throw("Specified negative value for cache_age"); + } + + if (cacheSize.has_value() || cacheAge.has_value()) + { + cache_ = std::make_shared>( + "DatabaseNodeImp", + cacheSize.value_or(0), + std::chrono::minutes(cacheAge.value_or(0)), + stopwatch(), + j); + } + XRPL_ASSERT( backend_, "xrpl::NodeStore::DatabaseNodeImp::DatabaseNodeImp : non-null " @@ -73,7 +99,13 @@ public: std::uint32_t ledgerSeq, std::function const&)>&& callback) override; + void + sweep() override; + private: + // Cache for database objects. This cache is not always initialized. Check + // for null before using. + std::shared_ptr> cache_; // Persistent key/value storage std::shared_ptr backend_; diff --git a/include/xrpl/nodestore/detail/DatabaseRotatingImp.h b/include/xrpl/nodestore/detail/DatabaseRotatingImp.h index 39441ef4d8..1ba9435a5f 100644 --- a/include/xrpl/nodestore/detail/DatabaseRotatingImp.h +++ b/include/xrpl/nodestore/detail/DatabaseRotatingImp.h @@ -55,6 +55,9 @@ public: void sync() override; + void + sweep() override; + private: std::shared_ptr writableBackend_; std::shared_ptr archiveBackend_; diff --git a/src/libxrpl/nodestore/DatabaseNodeImp.cpp b/src/libxrpl/nodestore/DatabaseNodeImp.cpp index a51c34079b..9323d69131 100644 --- a/src/libxrpl/nodestore/DatabaseNodeImp.cpp +++ b/src/libxrpl/nodestore/DatabaseNodeImp.cpp @@ -24,6 +24,13 @@ DatabaseNodeImp::store(NodeObjectType type, Blob&& data, uint256 const& hash, st auto obj = NodeObject::createObject(type, std::move(data), hash); backend_->store(obj); + if (cache_) + { + // After the store, replace a negative cache entry if there is one + cache_->canonicalize(hash, obj, [](std::shared_ptr const& n) { + return n->getType() == NodeObjectType::Dummy; + }); + } } void @@ -32,9 +39,25 @@ DatabaseNodeImp::asyncFetch( std::uint32_t ledgerSeq, std::function const&)>&& callback) { + if (cache_) + { + std::shared_ptr const obj = cache_->fetch(hash); + if (obj) + { + callback(obj->getType() == NodeObjectType::Dummy ? nullptr : obj); + return; + } + } Database::asyncFetch(hash, ledgerSeq, std::move(callback)); } +void +DatabaseNodeImp::sweep() +{ + if (cache_) + cache_->sweep(); +} + std::shared_ptr DatabaseNodeImp::fetchNodeObject( uint256 const& hash, @@ -42,32 +65,58 @@ DatabaseNodeImp::fetchNodeObject( FetchReport& fetchReport, bool duplicate) { - std::shared_ptr nodeObject = nullptr; - Status status = Status::Ok; + std::shared_ptr nodeObject = cache_ ? cache_->fetch(hash) : nullptr; + if (!nodeObject) + { + JLOG(j_.trace()) << "fetchNodeObject " << hash << ": record not " + << (cache_ ? "cached" : "found"); - try - { - status = backend_->fetch(hash, &nodeObject); - } - catch (std::exception const& e) - { - JLOG(j_.fatal()) << "fetchNodeObject " << hash - << ": Exception fetching from backend: " << e.what(); - rethrow(); - } + Status status = Status::Ok; + try + { + status = backend_->fetch(hash, &nodeObject); + } + catch (std::exception const& e) + { + JLOG(j_.fatal()) << "fetchNodeObject " << hash + << ": Exception fetching from backend: " << e.what(); + rethrow(); + } - switch (status) + switch (status) + { + case Status::Ok: + if (cache_) + { + if (nodeObject) + { + cache_->canonicalizeReplaceClient(hash, nodeObject); + } + else + { + auto notFound = NodeObject::createObject(NodeObjectType::Dummy, {}, hash); + cache_->canonicalizeReplaceClient(hash, notFound); + if (notFound->getType() != NodeObjectType::Dummy) + nodeObject = notFound; + } + } + break; + case Status::NotFound: + break; + case Status::DataCorrupt: + JLOG(j_.fatal()) << "fetchNodeObject " << hash << ": nodestore data is corrupted"; + break; + default: + JLOG(j_.warn()) << "fetchNodeObject " << hash << ": backend returns unknown result " + << static_cast(status); + break; + } + } + else { - case Status::Ok: - case Status::NotFound: - break; - case Status::DataCorrupt: - JLOG(j_.fatal()) << "fetchNodeObject " << hash << ": nodestore data is corrupted"; - break; - default: - JLOG(j_.warn()) << "fetchNodeObject " << hash << ": backend returns unknown result " - << static_cast(status); - break; + JLOG(j_.trace()) << "fetchNodeObject " << hash << ": record found in cache"; + if (nodeObject->getType() == NodeObjectType::Dummy) + nodeObject.reset(); } if (nodeObject) diff --git a/src/libxrpl/nodestore/DatabaseRotatingImp.cpp b/src/libxrpl/nodestore/DatabaseRotatingImp.cpp index 24b0e2de2e..1de6a83b4c 100644 --- a/src/libxrpl/nodestore/DatabaseRotatingImp.cpp +++ b/src/libxrpl/nodestore/DatabaseRotatingImp.cpp @@ -113,6 +113,12 @@ DatabaseRotatingImp::store(NodeObjectType type, Blob&& data, uint256 const& hash storeStats(1, nObj->getData().size()); } +void +DatabaseRotatingImp::sweep() +{ + // Nothing to do. +} + std::shared_ptr DatabaseRotatingImp::fetchNodeObject( uint256 const& hash, diff --git a/src/test/app/SHAMapStore_test.cpp b/src/test/app/SHAMapStore_test.cpp index fc5ef02465..6e279eadb2 100644 --- a/src/test/app/SHAMapStore_test.cpp +++ b/src/test/app/SHAMapStore_test.cpp @@ -520,6 +520,25 @@ public: ///////////////////////////////////////////////////////////// // Create NodeStore with two backends to allow online deletion of data. // Normally, SHAMapStoreImp handles all these details. + auto nscfg = env.app().config().section(ConfigSection::nodeDatabase()); + + // Provide default values. + if (!nscfg.exists("cache_size")) + { + nscfg.set( + "cache_size", + std::to_string( + env.app().config().getValueFor(SizedItem::TreeCacheSize, std::nullopt))); + } + + if (!nscfg.exists("cache_age")) + { + nscfg.set( + "cache_age", + std::to_string( + env.app().config().getValueFor(SizedItem::TreeCacheAge, std::nullopt))); + } + NodeStoreScheduler scheduler(env.app().getJobQueue()); std::string const writableDb = "write"; @@ -528,7 +547,6 @@ public: auto archiveBackend = makeBackendRotating(env, scheduler, archiveDb); static constexpr int kReadThreads = 4; - auto nscfg = env.app().config().section(ConfigSection::nodeDatabase()); auto dbr = std::make_unique( scheduler, kReadThreads, diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index a18462f0f7..508dfc8590 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -998,6 +998,10 @@ public: JLOG(journal_.debug()) << "MasterTransaction sweep. Size before: " << oldMasterTxSize << "; size after: " << masterTxCache.size(); } + { + // Sweep NodeStore database cache(s), if enabled. + getNodeStore().sweep(); + } { std::size_t const oldLedgerMasterCacheSize = getLedgerMaster().getFetchPackCacheSize(); diff --git a/src/xrpld/app/misc/SHAMapStoreImp.cpp b/src/xrpld/app/misc/SHAMapStoreImp.cpp index fc3f71c0cc..3f4bd6280f 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.cpp +++ b/src/xrpld/app/misc/SHAMapStoreImp.cpp @@ -165,6 +165,22 @@ std::unique_ptr SHAMapStoreImp::makeNodeStore(int readThreads) { auto nscfg = app_.config().section(ConfigSection::nodeDatabase()); + + // Provide default values. + if (!nscfg.exists("cache_size")) + { + nscfg.set( + "cache_size", + std::to_string(app_.config().getValueFor(SizedItem::TreeCacheSize, std::nullopt))); + } + + if (!nscfg.exists("cache_age")) + { + nscfg.set( + "cache_age", + std::to_string(app_.config().getValueFor(SizedItem::TreeCacheAge, std::nullopt))); + } + std::unique_ptr db; if (deleteInterval_ != 0u) @@ -254,6 +270,8 @@ SHAMapStoreImp::run() LedgerIndex lastRotated = stateDb_.getState().lastRotated; netOPs_ = &app_.getOPs(); ledgerMaster_ = &app_.getLedgerMaster(); + fullBelowCache_ = &(*app_.getNodeFamily().getFullBelowCache()); + treeNodeCache_ = &(*app_.getNodeFamily().getTreeNodeCache()); if (advisoryDelete_) canDelete_ = stateDb_.getCanDelete(); @@ -542,16 +560,16 @@ SHAMapStoreImp::clearCaches(LedgerIndex validatedSeq) // Also clear the FullBelowCache so its generation counter is bumped. // This prevents stale "full below" markers from persisting across // backend rotation/online deletion and interfering with SHAMap sync. - app_.getNodeFamily().getFullBelowCache()->clear(); + fullBelowCache_->clear(); } void SHAMapStoreImp::freshenCaches() { - if (freshenCache(*app_.getNodeFamily().getTreeNodeCache())) + if (freshenCache(*treeNodeCache_)) + return; + if (freshenCache(app_.getMasterTransaction().getCache())) return; - - freshenCache(app_.getMasterTransaction().getCache()); } void diff --git a/src/xrpld/app/misc/SHAMapStoreImp.h b/src/xrpld/app/misc/SHAMapStoreImp.h index 4f0ce04e0d..1803c15e71 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.h +++ b/src/xrpld/app/misc/SHAMapStoreImp.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include @@ -93,6 +95,8 @@ private: // as of run() or before NetworkOPs* netOPs_ = nullptr; LedgerMaster* ledgerMaster_ = nullptr; + FullBelowCache* fullBelowCache_ = nullptr; + TreeNodeCache* treeNodeCache_ = nullptr; static constexpr auto kNodeStoreName = "NodeStore"; From 47365f42203c53b723b3d209d8bcd760bf0c4822 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Sat, 30 May 2026 20:23:29 -0400 Subject: [PATCH 046/135] fix: Improve upward rounding edge cases for Number::operator/= (#7328) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com> --- include/xrpl/basics/Number.h | 94 +++++++--- src/libxrpl/basics/Number.cpp | 208 ++++++++++++++++------ src/test/basics/Number_test.cpp | 301 +++++++++++++++++++++++++++++--- 3 files changed, 506 insertions(+), 97 deletions(-) diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index 3aeb4b5bcd..93bef82a8c 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -2,12 +2,14 @@ #include +#include #include #include #include #include #include #include +#include #include #include @@ -40,6 +42,47 @@ isPowerOfTen(T value) return logTen(value).has_value(); } +namespace detail { + +/** Builds a table of the powers of 10 + * + * This function is marked consteval, so it can only be run in + * a constexpr context. This assures that it is and can only be run at + * compile time. Doing it at runtime would be pretty wasteful and + * inefficient. + */ +constexpr std::size_t kInt64Digits = 20; +consteval std::array +buildPowersOfTen() +{ + std::array result{}; + + std::uint64_t power = 1; + std::size_t exponent = 0; + // end the loop early so it doesn't overflow; + for (; exponent < result.size() - 1; ++exponent, power *= 10) + { + result[exponent] = power; + if (power > std::numeric_limits::max() / 10) + throw std::logic_error("Power of 10 table is too big"); + } + result[exponent] = power; + if (power < std::numeric_limits::max() / 10) + throw std::logic_error("Power of 10 table is not big enough for the uint64_t type"); + + return result; +} + +} // namespace detail + +constexpr std::array kPowerOfTen = detail::buildPowersOfTen(); + +static_assert(kPowerOfTen[0] == 1); +static_assert(kPowerOfTen[1] == 10); +static_assert(kPowerOfTen[10] == 10'000'000'000); +static_assert( + isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kInt64Digits - 1); + /** MantissaRange defines a range for the mantissa of a normalized Number. * * The mantissa is in the range [min, max], where @@ -76,6 +119,7 @@ isPowerOfTen(T value) struct MantissaRange final { using rep = std::uint64_t; + enum class MantissaScale { Small, // LargeLegacy can be removed when fixCleanup3_2_0 is retired @@ -89,19 +133,15 @@ struct MantissaRange final Enabled = true, }; - explicit constexpr MantissaRange(MantissaScale scale) - : min(getMin(scale)) - , cuspRoundingFixEnabled(isCuspFixEnabled(scale)) - , log(logTen(min).value_or(-1)) - , scale(scale) + explicit constexpr MantissaRange(MantissaScale sc) : scale(sc) { } - rep min; - rep max{(min * 10) - 1}; - CuspRoundingFix cuspRoundingFixEnabled; - int log; - MantissaScale scale; + MantissaScale const scale; + int const log{getExponent(scale)}; + rep const min{getMin(scale, log)}; + rep const max{(min * 10) - 1}; + CuspRoundingFix const cuspRoundingFixEnabled{isCuspFixEnabled(scale)}; static MantissaRange const& getMantissaRange(MantissaScale scale); @@ -110,23 +150,35 @@ struct MantissaRange final getAllScales(); private: - static constexpr rep - getMin(MantissaScale scale) + static constexpr int + getExponent(MantissaScale scale) { switch (scale) { case MantissaScale::Small: - return 1'000'000'000'000'000ULL; + return 15; case MantissaScale::LargeLegacy: case MantissaScale::Large: - return 1'000'000'000'000'000'000ULL; + return 18; + // LCOV_EXCL_START default: // If called in a constexpr context, this throw assures that the build fails if an // invalid scale is used. - throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE + throw std::runtime_error("Unknown mantissa scale"); + // LCOV_EXCL_STOP } } + // Keep this function for future use with different ways to compute + // the ranges. + static constexpr rep + getMin(MantissaScale scale, int exponent) + { + if (exponent < 0 || exponent >= kPowerOfTen.size()) + throw std::runtime_error("Invalid exponent"); // LCOV_EXCL_LINE + return kPowerOfTen[exponent]; + } + static constexpr CuspRoundingFix isCuspFixEnabled(MantissaScale scale) { @@ -477,8 +529,7 @@ public: template < auto MinMantissa, auto MaxMantissa, - Integral64 T = std::decay_t, - Integral64 TMax = std::decay_t> + Integral64 T = std::decay_t> [[nodiscard]] std::pair normalizeToRange() const; @@ -519,7 +570,8 @@ private: int& exponent, MantissaRange::rep const& minMantissa, MantissaRange::rep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled); + MantissaRange::CuspRoundingFix cuspRoundingFixEnabled, + bool dropped); [[nodiscard]] bool isnormal() const noexcept; @@ -725,16 +777,18 @@ Number::isnormal() const noexcept kMinExponent <= exponent_ && exponent_ <= kMaxExponent); } -template +template std::pair Number::normalizeToRange() const { static_assert(std::is_same_v || std::is_same_v); - static_assert(std::is_same_v); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); auto constexpr kMIN = static_cast(MinMantissa); auto constexpr kMAX = static_cast(MaxMantissa); static_assert(kMIN > 0); static_assert(kMIN % 10 == 0); + static_assert(isPowerOfTen(kMIN)); static_assert(kMAX % 10 == 9); static_assert((kMAX + 1) / 10 == kMIN); diff --git a/src/libxrpl/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index 11f5934b04..275d82d8c9 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -178,6 +178,10 @@ public: setPositive() noexcept; void setNegative() noexcept; + // Should only be called by doNormalize, and then only for division + // operations with remainders. + void + setDropped() noexcept; [[nodiscard]] bool isNegative() const noexcept; @@ -250,6 +254,12 @@ Number::Guard::setNegative() noexcept sbit_ = 1; } +inline void +Number::Guard::setDropped() noexcept +{ + xbit_ = 1; +} + inline bool Number::Guard::isNegative() const noexcept { @@ -398,7 +408,7 @@ Number::Guard::doRoundUp( // _don't_ increment the mantissa. Instead, divide and round recursively. It should // be impossible to recurse more than once, because once the mantissa is divided by // 10, it will be _well_ under maxMantissa and kMaxRep, so adding 1 will have no - // change of bringing it back over. + // chance of bringing it back over. doDropDigit(mantissa, exponent); XRPL_ASSERT_PARTS( safeToIncrement(mantissa), @@ -512,8 +522,6 @@ Number::one() return Number{false, range.min, -range.log, Number::Unchecked{}}; } -// Use the member names in this static function for now so the diff is cleaner -// TODO: Rename the function parameters to get rid of the "_" suffix template void doNormalize( @@ -522,7 +530,8 @@ doNormalize( int& exponent, MantissaRange::rep const& minMantissa, MantissaRange::rep const& maxMantissa, - MantissaRange::CuspRoundingFix cuspRoundingFixEnabled) + MantissaRange::CuspRoundingFix cuspRoundingFixEnabled, + bool dropped) { static constexpr auto kMinExponent = Number::kMinExponent; static constexpr auto kMaxExponent = Number::kMaxExponent; @@ -547,6 +556,8 @@ doNormalize( Guard g; if (negative) g.setNegative(); + if (dropped) + g.setDropped(); while (m > maxMantissa) { if (exponent >= kMaxExponent) @@ -611,7 +622,12 @@ Number::normalize( internalrep const& maxMantissa, MantissaRange::CuspRoundingFix cuspRoundingFixEnabled) { - doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled); + // Not used by every compiler version, and thus not necessarily + // counted by coverage build + // LCOV_EXCL_START + doNormalize( + negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled, false); + // LCOV_EXCL_STOP } template <> @@ -624,7 +640,12 @@ Number::normalize( internalrep const& maxMantissa, MantissaRange::CuspRoundingFix cuspRoundingFixEnabled) { - doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled); + // Not used by every compiler version, and thus not necessarily + // counted by coverage build + // LCOV_EXCL_START + doNormalize( + negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled, false); + // LCOV_EXCL_STOP } template <> @@ -637,7 +658,8 @@ Number::normalize( internalrep const& maxMantissa, MantissaRange::CuspRoundingFix cuspRoundingFixEnabled) { - doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled); + doNormalize( + negative, mantissa, exponent, minMantissa, maxMantissa, cuspRoundingFixEnabled, false); } void @@ -838,7 +860,9 @@ Number::operator/=(Number const& y) return *this; // n* = numerator // d* = denominator - // *p = negative (positive?) + // z* = result (quotient) + // *p = negative (p for positive, even though the value means not + // positive?) // *s = sign // *m = mantissa // *e = exponent @@ -850,71 +874,155 @@ Number::operator/=(Number const& y) bool const dp = y.negative_; int const ds = (dp ? -1 : 1); - auto dm = y.mantissa_; - auto de = y.exponent_; + // Create the denominator as 128-bit unsigned, since that's what we + // need to work with. + uint128_t const dm = static_cast(y.mantissa_); + auto const de = y.exponent_; auto const& range = kRange.get(); auto const& minMantissa = range.min; auto const& maxMantissa = range.max; auto const cuspRoundingFixEnabled = range.cuspRoundingFixEnabled; - // Shift by 10^17 gives greatest precision while not overflowing - // uint128_t or the cast back to int64_t - // TODO: Can/should this be made bigger for largeRange? - // log(2^128,10) ~ 38.5 - // largeRange.log = 18, fits in 10^19 - // f can be up to 10^(38-19) = 10^19 safely - bool const small = Number::getMantissaScale() == MantissaRange::MantissaScale::Small; - uint128_t const f = small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL; - XRPL_ASSERT_PARTS(f >= minMantissa * 10, "Number::operator/=", "factor expected size"); + // Division operates on two large integers (16-digit for small + // mantissas, 19-digit for large) using integer math. If the values + // were just divided directly, the result would be only ever be one + // digit or zero - not very useful. + // e.g. 9'876'543'210'987'654 / 1'234'567'890'123'456 = 8 + // 1'234'567'890'123'456 / 9'876'543'210'987'654 = 0 + // Introduce a power-of-ten multiplication factor for the numerator + // which will ensure the result has a meaningful number of digits. + // + // Consider numbers with a 2-digit mantissa: + // * Assume both numbers have an exponent of 0, using "ToNearest" rounding + // * 23 / 67 = 0 + // * Use a factor of 10^4 + // * 230'000 / 67 = 3432 with an exponent of -4 + // * The normalized result will be 34, exponent -2, or 0.34 + // + // The most extreme results are 10/99 and 99/10 + // * 100'000 / 99 = 1'010e-4 = 10e-2 or 0.10 + // * 990'000 / 10 = 99'000e-4 = 99e-1 or 9.9 + // + // Note that the computations give 2 or 3 digits after the + // decimal point to determine which way to round for most scenarios. + // + // For small mantissas (where the MantissaRange.log == 15), shifting by 10^17 gives sufficient + // precision while not overflowing uint128_t or the cast back to int64_t. (This is legacy + // behavior, which must not be changed.) + // + // For large mantissas (where the MantissaRange.log == 18), a shift by 10^20 would be optimal + // for most scenarios. However, larger mantissa values would overflow 2^128. + // + // * log(2^128,10) ~ 38.5 + // * largeRange.log = 18, fits in 10^19 + // * The expanded numerator must fit in 10^38 + // * f not be more than 10^(38-19) = 10^19 safely + // + // So, we do the division into stages: + // + // Stage 1: Use the same factor of 10^17, for the initial division. This + // will frequently not result in a whole number quotient. + // + // Stage 2: If there is a remainder from the first step, repeat the + // process with a "correction" factor of 10^5. Shift the + // result of Stage 1 over by 5 places, and add the second result to it. + // This is equivalent to if we had used an initial factor of 10^22, + // a couple digits more than we actually need. + // + // Stage 3: If there is still a remainder, and the CuspRoundingFix + // is enabled, pass a flag indicating such to doNormalize. The Guard + // in doNormalize will treat that flag as if non-zero digits had + // been dropped from the mantissa when shrinking it into range. + // This is only relevant when rounding away from zero (Upward for + // positive numbers, Downward for negative), or if the "regular" + // remainder is exactly 0.5 for "ToNearest". This will give the + // rounding the most accurate result possible, as if infinite + // precision was used in the initial calculation. - // unsigned denominator - auto const dmu = static_cast(dm); - // correctionFactor can be anything between 10 and f, depending on how much - // extra precision we want to only use for rounding with the - // largeRange. Three digits seems like plenty, and is more than - // the smallRange uses. - uint128_t const correctionFactor = 1'000; + // Stage 1: Do the initial division with a factor of 10^17. + auto constexpr factorExponent = 17; + + uint128_t constexpr f = kPowerOfTen[factorExponent]; auto const numerator = uint128_t(nm) * f; - auto zm = numerator / dmu; - auto ze = ne - de - (small ? 17 : 19); - bool zn = (ns * ds) < 0; - if (!small) + auto zm = numerator / dm; + auto ze = ne - de - factorExponent; + bool zp = (ns * ds) < 0; + // dropped is used in the same way as Guard::xbit_. In the case of + // division, it indicates if there's any remainder left over after + // we have been as precise as reasonable. If there is, it would be as + // if we were using infinite precision math, and a non-zero digit + // had been shifted off the end of the result when normalizing. + bool dropped = false; + + if (range.scale != MantissaRange::MantissaScale::Small) { - // Virtually multiply numerator by correctionFactor. Since that would - // overflow in the existing uint128_t, we'll do that part separately. + // Stage 2 + // + // If there is a remainder, treat it as a secondary numerator. + // Multiply by correctionFactor separately from stage 1. // The math for this would work for small mantissas, but we need to - // preserve existing behavior. + // preserve legacy behavior. // // Consider: - // ((numerator * correctionFactor) / dmu) / correctionFactor - // = ((numerator / dmu) * correctionFactor) / correctionFactor) + // ((numerator * correctionFactor) / dm) / correctionFactor + // = ((numerator / dm) * correctionFactor) / correctionFactor) // // But that assumes infinite precision. With integer math, this is // equivalent to // - // = ((numerator / dmu * correctionFactor) - // + ((numerator % dmu) * correctionFactor) / dmu) / correctionFactor + // = ((numerator / dm * correctionFactor) + // + ((numerator % dm) * correctionFactor) / dm) / correctionFactor + // = ((zm * correctionFactor) + // + (remainder * correctionFactor) / dm) / correctionFactor // - // We have already set `mantissa_ = numerator / dmu`. Now we - // compute `remainder = numerator % dmu`, and if it is - // nonzero, we do the rest of the arithmetic. If it's zero, we can skip - // it. - auto const remainder = (numerator % dmu); + // The trick is that multiplication by correctionFactor is done on the mantissa, but + // division by correctionFactor is done by modifying the exponent, so no precision is lost + // until we normalize. + // + // If remainder is zero, we can skip this stage entirely because + // the first stage gave an exact answer. + auto constexpr correctionExponent = 5; + uint128_t constexpr correctionFactor = kPowerOfTen[correctionExponent]; + static_assert(factorExponent + correctionExponent == 22); + + auto const remainder = (numerator % dm); if (remainder != 0) { - zm *= correctionFactor; - auto const correction = remainder * correctionFactor / dmu; - zm += correction; - // divide by 1000 by moving the exponent, so we don't lose the - // integer value we just computed - ze -= 3; + auto const partialNumerator = remainder * correctionFactor; + auto const correction = partialNumerator / dm; + + // If the correction is zero, we do not have to make any + // modifications to z*, because it will not have any + // effect on the final result. (We'd be adding a bunch of + // zeros to the end of zm that would just be removed in + // normalize.) However, if that is the case, then Stage 3 is + // even more important for accuracy. + if (correction != 0) + { + zm *= correctionFactor; + // divide by the correctionFactor by moving the exponent, so we don't lose the + // integer value we just computed + ze -= correctionExponent; + + zm += correction; + } + + // Stage 3: If there's still anything left, and the cusp + // rounding fix is enabled, flag if there is still + // a remainder from stage 2. + bool const useTrailingRemainder = + cuspRoundingFixEnabled == MantissaRange::CuspRoundingFix::Enabled; + if (useTrailingRemainder) + { + dropped = partialNumerator % dm != 0; + } } } - normalize(zn, zm, ze, minMantissa, maxMantissa, cuspRoundingFixEnabled); - negative_ = zn; + doNormalize(zp, zm, ze, minMantissa, maxMantissa, cuspRoundingFixEnabled, dropped); + negative_ = zp; mantissa_ = static_cast(zm); exponent_ = ze; XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::operator/=", "result is normalized"); diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index 26060c70e9..81019970ad 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -6,11 +6,14 @@ #include #include +// NOLINTNEXTLINE(misc-include-cleaner) +#include #include #include #include #include +#include #include #include #include @@ -40,6 +43,40 @@ class Number_test : public beast::unit_test::Suite return out; } + using dec = boost::multiprecision::cpp_dec_float_50; + + template + static T + pow10(int n) + { + if (n == 0) + return 1; + if (n == 1) + return 10; + + if (n > 1) + { + auto r = pow10(n / 2); + r *= r; + if (n % 2 != 0) + r *= 10; + return r; + } + + // n < 0 + T p = 1; + p /= pow10(-n); + return p; + } + + static std::string + fmt(dec const& v) + { + std::ostringstream os; + os << std::setprecision(40) << v; + return os.str(); + } + public: void testZero() @@ -1589,39 +1626,249 @@ public: void testUpwardRoundsDown() { - testcase << "upward rounding produces a value below exact at kMaxRep cusp"; + auto const scale = Number::getMantissaScale(); + { + testcase << "upward rounding produces a value below exact at kMaxRep cusp " + << to_string(scale); - NumberMantissaScaleGuard const mg{MantissaRange::MantissaScale::Large}; - NumberRoundModeGuard const rg{Number::RoundingMode::Upward}; + NumberRoundModeGuard const rg{Number::RoundingMode::Upward}; - constexpr std::int64_t kAValue = 1'000'000'000'000'049'863LL; - constexpr std::int64_t kBValue = 9'223'372'036'854'315'903LL; + constexpr std::int64_t kAValue = 1'000'000'000'000'049'863LL; + constexpr std::int64_t kBValue = 9'223'372'036'854'315'903LL; - Number const a = kAValue; - Number const b = kBValue; - Number const product = a * b; + Number const a = kAValue; + Number const b = kBValue; + Number const product = a * b; - // Exact reference in BigInt. - BigInt const exactProduct = BigInt(kAValue) * BigInt(kBValue); + // Exact reference in BigInt. + BigInt const exactProduct = BigInt(kAValue) * BigInt(kBValue); - // What Number actually stored. - BigInt storedValue = BigInt(product.mantissa()); - for (int i = 0; i < product.exponent(); ++i) - storedValue *= 10; + // What Number actually stored. + BigInt storedValue = BigInt(product.mantissa()); + for (int i = 0; i < product.exponent(); ++i) + storedValue *= 10; - BigInt const signedDifference = storedValue - exactProduct; + BigInt const signedDifference = storedValue - exactProduct; - log << "\n" - << " a = " << fmt(BigInt(kAValue)) << "\n" - << " b = " << fmt(BigInt(kBValue)) << "\n" - << " exact a*b = " << fmt(exactProduct) << "\n" - << " stored = " << fmt(storedValue) << "\n" - << " stored - exact = " << fmt(signedDifference) << "\n" - << " upward = " << (signedDifference >= 0 ? "held" : "VIOLATED") << "\n"; + log << "\n" + << " a = " << fmt(BigInt(kAValue)) << "\n" + << " b = " << fmt(BigInt(kBValue)) << "\n" + << " exact a*b = " << fmt(exactProduct) << "\n" + << " stored = " << fmt(storedValue) << "\n" + << " stored - exact = " << fmt(signedDifference) << "\n" + << " upward = " << (signedDifference >= 0 ? "held" : "VIOLATED") << "\n" + << " stored.mantissa = " << product.mantissa() << "\n" + << " stored.exponent = " << product.exponent() << "\n"; + log.flush(); - BEAST_EXPECT(signedDifference >= 0); - BEAST_EXPECT(product.mantissa() == (std::numeric_limits::max() / 10) + 1); - BEAST_EXPECT(product.exponent() == 19); + switch (scale) + { + case MantissaRange::MantissaScale::Large: + BEAST_EXPECT(signedDifference >= 0); + BEAST_EXPECT(signedDifference < pow10(product.exponent())); + BEAST_EXPECT( + product.mantissa() == (std::numeric_limits::max() / 10) + 1); + BEAST_EXPECT(product.exponent() == 19); + break; + + case MantissaRange::MantissaScale::LargeLegacy: + BEAST_EXPECT(signedDifference < 0); + BEAST_EXPECT( + product.mantissa() == + (std::numeric_limits::max() / 100) * 100); + BEAST_EXPECT(product.exponent() == 18); + break; + + case MantissaRange::MantissaScale::Small: + // The seemingly weird rounding here is because + // a & b are both normalized, and both round up when + // being converted to Number, so you're really + // getting 1_000_000_000_000_050 * 9_223_372_036_854_316 + BEAST_EXPECT(signedDifference >= 0); + BEAST_EXPECT( + product.mantissa() == + (std::numeric_limits::max() / 1000) + 3); + BEAST_EXPECT(product.exponent() == 21); + break; + } + } + + { + /* Companion regression for the kMaxRep cusp behavior, but for + * `operator/=` on the cusp-fix-ENABLED `Large` scale. + * + * Before the dropped-remainder fix, `operator/=` with Upward + * rounding could return a value STRICTLY LESS than the exact quotient, + * violating Upward's directional invariant. + * + * Mechanism (fix-enabled path): + * 1. `operator/=` computes `numerator = nm * 10^17` and + * `zm = numerator / dm` (integer division, truncates remainder). + * 2. If `remainder != 0`, the correction block runs: + * zm *= 100000 + * correction = (remainder * 100000) / dm // also truncates + * zm += correction + * ze -= 5 + * The truncation in `correction` discards a sub-1/100000 residual. + * 3. `normalize`'s shift loop reduces zm to fit, but the discarded + * residual is BELOW the Guard's visibility, so the Guard sees fraction = 0. + * 4. Under Upward + positive, `round()` returns -1 (no round-up), and + * the algorithm returns the truncated zm + */ + testcase << "operator/= Upward on Large returns value < truth " << to_string(scale); + + NumberRoundModeGuard const roundGuard{Number::RoundingMode::Upward}; + + constexpr std::int64_t aValue = 2LL; + constexpr std::int64_t bValue = 1'000'000'000'000'000'007LL; + // bValue = 10^18 + 7 (prime, in [minMantissa, kMaxRep]). + + Number const a{aValue, 0}; + Number const b{bValue, 0}; + Number const quotient = a / b; + + dec const exact = dec(aValue) / dec(bValue); + dec const stored = dec(quotient.mantissa()) * pow10(quotient.exponent()); + dec const diff = stored - exact; + + log << "\n" + << " a = " << aValue << "\n" + << " b = " << bValue << "\n" + << " exact a/b = " << fmt(exact) << "\n" + << " stored a/b = " << fmt(stored) << "\n" + << " stored - exact = " << fmt(diff) + << " (negative => Upward gave value BELOW truth)\n" + << " quotient.mantissa = " << quotient.mantissa() << "\n" + << " quotient.exponent = " << quotient.exponent() << "\n"; + log.flush(); + + // Upward invariant: stored >= exact. Bug: stored < exact. + switch (scale) + { + case MantissaRange::MantissaScale::Large: + BEAST_EXPECT(stored >= exact); + BEAST_EXPECT(diff < pow10(quotient.exponent())); + break; + + case MantissaRange::MantissaScale::LargeLegacy: + BEAST_EXPECT(stored < exact); + BEAST_EXPECT(diff >= -pow10(quotient.exponent())); + break; + + case MantissaRange::MantissaScale::Small: + // Small mantissa doesn't have the correction for + // dropped remainders + BEAST_EXPECT(stored < exact); + break; + } + } + { + /* Companion test case for Upward positive operator/=: Downward negative + */ + testcase << "operator/= Downward on Large returns value < truth " << to_string(scale); + + NumberRoundModeGuard const roundGuard{Number::RoundingMode::Downward}; + + constexpr std::int64_t aValue = -2LL; + constexpr std::int64_t bValue = 1'000'000'000'000'000'007LL; + // bValue = 10^18 + 7 (prime, in [minMantissa, kMaxRep]). + + Number const a{aValue, 0}; + Number const b{bValue, 0}; + Number const quotient = a / b; + + dec const exact = dec(aValue) / dec(bValue); + dec const stored = dec(quotient.mantissa()) * pow10(quotient.exponent()); + dec const diff = stored - exact; + + log << "\n" + << " a = " << aValue << "\n" + << " b = " << bValue << "\n" + << " exact a/b = " << fmt(exact) << "\n" + << " stored a/b = " << fmt(stored) << "\n" + << " stored - exact = " << fmt(diff) + << " (positive => Downward gave value ABOVE truth)\n" + << " quotient.mantissa = " << quotient.mantissa() << "\n" + << " quotient.exponent = " << quotient.exponent() << "\n"; + log.flush(); + + // invariant: stored <= exact. Bug: stored > exact. + switch (scale) + { + case MantissaRange::MantissaScale::Large: + BEAST_EXPECT(stored <= exact); + BEAST_EXPECT(diff > -pow10(quotient.exponent())); + break; + + case MantissaRange::MantissaScale::LargeLegacy: + BEAST_EXPECT(stored > exact); + BEAST_EXPECT(diff <= pow10(quotient.exponent())); + break; + + case MantissaRange::MantissaScale::Small: + // Small mantissa doesn't have the correction for + // dropped remainders + BEAST_EXPECT(stored < exact); + break; + } + } + { + /* Companion test case for Upward positive operator/=: ToNearest + * + * With ToNearest, if the dropped digits are exactly "5", then the mantissa will be + * rounded to even. The numbers below result in a value where the unrounded mantissa + * ends in an even digit, and "infinite precision" would drop + * "500000000000000000145...", but doNormalize only sees "5". Without the rounding fix, + * doNormalize rounds down to the even value. With the rounding fix, doNormalize knows + * there are more digits beyond "5", and so rounds _up_ to the odd value. + */ + testcase << "operator/= ToNearest on Large returns value < truth " << to_string(scale); + + NumberRoundModeGuard const roundGuard{Number::RoundingMode::ToNearest}; + + constexpr std::int64_t aValue = 1'269'917'268'816'087'809LL; + constexpr std::int64_t bValue = 3'458'525'013'821'685'511LL; + // bValue = 10^18 + 7 (prime, in [minMantissa, kMaxRep]). + + Number const a{aValue, 0}; + Number const b{bValue, 0}; + Number const quotient = a / b; + + dec const exact = dec(aValue) / dec(bValue); + dec const stored = dec(quotient.mantissa()) * pow10(quotient.exponent()); + dec const diff = stored - exact; + + log << "\n" + << " a = " << aValue << "\n" + << " b = " << bValue << "\n" + << " exact a/b = " << fmt(exact) << "\n" + << " stored a/b = " << fmt(stored) << "\n" + << " stored - exact = " << fmt(diff) + << " (negative => ToNearest gave value BELOW truth)\n" + << " quotient.mantissa = " << quotient.mantissa() << "\n" + << " quotient.exponent = " << quotient.exponent() << "\n"; + log.flush(); + + // invariant: stored >= exact. Bug: stored < exact. + switch (scale) + { + case MantissaRange::MantissaScale::Large: + BEAST_EXPECT(stored >= exact); + BEAST_EXPECT(diff < pow10(quotient.exponent())); + break; + + case MantissaRange::MantissaScale::LargeLegacy: + BEAST_EXPECT(stored < exact); + BEAST_EXPECT(diff >= -pow10(quotient.exponent())); + break; + + case MantissaRange::MantissaScale::Small: + // Small mantissa doesn't have the correction for + // dropped remainders + BEAST_EXPECT(stored < exact); + break; + } + } } void @@ -1651,9 +1898,9 @@ public: testTruncate(); testRounding(); testInt64(); + + testUpwardRoundsDown(); } - // This test sets its own number range - testUpwardRoundsDown(); } }; From 99431d78335919fce5910d01966ed8f55b98572a Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Mon, 1 Jun 2026 00:54:23 +0200 Subject: [PATCH 047/135] fix: Pin overpayment principal reduction to exact on-grid value (#7360) --- include/xrpl/ledger/helpers/LendingHelpers.h | 1 + src/libxrpl/ledger/helpers/LendingHelpers.cpp | 138 ++++--- src/test/app/LendingHelpers_test.cpp | 182 +++++---- src/test/app/Loan_test.cpp | 362 ++++++++++++++++++ 4 files changed, 552 insertions(+), 131 deletions(-) diff --git a/include/xrpl/ledger/helpers/LendingHelpers.h b/include/xrpl/ledger/helpers/LendingHelpers.h index b7a1ed6bf2..32f94ee277 100644 --- a/include/xrpl/ledger/helpers/LendingHelpers.h +++ b/include/xrpl/ledger/helpers/LendingHelpers.h @@ -461,6 +461,7 @@ loanAccruedInterest( ExtendedPaymentComponents computeOverpaymentComponents( + Rules const& rules, Asset const& asset, int32_t const loanScale, Number const& overpayment, diff --git a/src/libxrpl/ledger/helpers/LendingHelpers.cpp b/src/libxrpl/ledger/helpers/LendingHelpers.cpp index 9cda5905c9..15ebf33e46 100644 --- a/src/libxrpl/ledger/helpers/LendingHelpers.cpp +++ b/src/libxrpl/ledger/helpers/LendingHelpers.cpp @@ -559,15 +559,34 @@ tryOverpayment( << ", new total value: " << newLoanProperties.loanState.valueOutstanding << ", first payment principal: " << newLoanProperties.firstPaymentPrincipal; - // Calculate what the new loan state should be with the new periodic payment - // including rounding errors - auto const newTheoreticalState = computeTheoreticalLoanState( - rules, - newLoanProperties.periodicPayment, - periodicRate, - paymentRemaining, - managementFeeRate) + - errors; + // Calculate what the new loan state should be with the new periodic payment, + // including the preserved rounding errors. + + auto const newTheoreticalState = [&]() { + auto const state = computeTheoreticalLoanState( + rules, + newLoanProperties.periodicPayment, + periodicRate, + paymentRemaining, + managementFeeRate) + + errors; + + if (!rules.enabled(fixCleanup3_2_0)) + return state; + + // The new principal is known exactly: it is reduced by the overpayment's + // principal portion. computeTheoreticalLoanState instead derives the + // principal -- and, from it, the management fee and interest -- via a + // lossy (P * factor) / factor round-trip. Pin the principal to the exact + // value and re-derive the management fee from the exact interest gross + // (value - principal), so the intermediate state is fully consistent with + // the exact principal rather than the one-scale-unit-high round-trip. + Number const principal = + roundedOldState.principalOutstanding - overpaymentComponents.trackedPrincipalDelta; + Number const managementFee = + tenthBipsOfValue(state.valueOutstanding - principal, managementFeeRate); + return constructLoanState(state.valueOutstanding, principal, managementFee); + }(); JLOG(j.debug()) << "new theoretical value: " << newTheoreticalState.valueOutstanding << ", principal: " << newTheoreticalState.principalOutstanding @@ -762,13 +781,6 @@ doOverpayment( // The proxies still hold the original (pre-overpayment) values, which // allows us to compute deltas and verify they match what we expect // from the overpaymentComponents and loanPaymentParts. - - XRPL_ASSERT_PARTS( - overpaymentComponents.trackedPrincipalDelta == - principalOutstandingProxy - newRoundedLoanState.principalOutstanding, - "xrpl::detail::doOverpayment", - "principal change agrees"); - JLOG(j.debug()) << "valueChange: " << loanPaymentParts.valueChange << ", totalValue before: " << *totalValueOutstandingProxy << ", totalValue after: " << newRoundedLoanState.valueOutstanding @@ -780,35 +792,50 @@ doOverpayment( << overpaymentComponents.trackedPrincipalDelta - (totalValueOutstandingProxy - newRoundedLoanState.valueOutstanding); - // The valueChange returned by tryOverpayment satisfies - // valueChange = (newInterestDue - oldInterestDue) + untrackedInterest. - // Using the loan-state identity v = p + i + m and the adjacent - // `principal change agrees` assertion (dp = oldP - newP), this - // rearranges into three independently-computable terms: - // - // 1. TVO change beyond what principal repayment alone explains: - // newTVO - (oldTVO - dp) - // 2. Management fee released by re-amortization (positive when - // mfee decreased; zero when managementFeeRate == 0): - // oldMfee - newMfee - // 3. The overpayment's penalty interest part (= untrackedInterest - // for the overpayment path; see computeOverpaymentComponents): - // trackedInterestPart() - [[maybe_unused]] Number const tvoChange = newRoundedLoanState.valueOutstanding - - (totalValueOutstandingProxy - overpaymentComponents.trackedPrincipalDelta); - [[maybe_unused]] Number const managementFeeReleased = - managementFeeOutstandingProxy - newRoundedLoanState.managementFeeDue; - [[maybe_unused]] Number const interestPart = overpaymentComponents.trackedInterestPart(); + // The three assertions below are invariants that only hold once + // fixCleanup3_2_0 pins the new principal to the exact reduction + // (oldPrincipal - trackedPrincipalDelta). Before the amendment, the lossy + // (P * factor) / factor round-trip can leave the new principal one + // scale-unit high, so these equalities do not hold on the pre-amendment + // code path and must be gated to match the fix they verify. + if (rules.enabled(fixCleanup3_2_0)) + { + // The valueChange returned by tryOverpayment satisfies + // valueChange = (newInterestDue - oldInterestDue) + untrackedInterest. + // Using the loan-state identity v = p + i + m and the adjacent + // `principal change agrees` assertion (dp = oldP - newP), this + // rearranges into three independently-computable terms: + // + // 1. TVO change beyond what principal repayment alone explains: + // newTVO - (oldTVO - dp) + // 2. Management fee released by re-amortization (positive when + // mfee decreased; zero when managementFeeRate == 0): + // oldMfee - newMfee + // 3. The overpayment's penalty interest part (= untrackedInterest + // for the overpayment path; see computeOverpaymentComponents): + // trackedInterestPart() + [[maybe_unused]] Number const tvoChange = newRoundedLoanState.valueOutstanding - + (totalValueOutstandingProxy - overpaymentComponents.trackedPrincipalDelta); + [[maybe_unused]] Number const managementFeeReleased = + managementFeeOutstandingProxy - newRoundedLoanState.managementFeeDue; + [[maybe_unused]] Number const interestPart = overpaymentComponents.trackedInterestPart(); - XRPL_ASSERT_PARTS( - loanPaymentParts.valueChange == tvoChange + managementFeeReleased + interestPart, - "xrpl::detail::doOverpayment", - "interest paid agrees"); + XRPL_ASSERT_PARTS( + overpaymentComponents.trackedPrincipalDelta == + principalOutstandingProxy - newRoundedLoanState.principalOutstanding, + "xrpl::detail::doOverpayment", + "principal change agrees"); - XRPL_ASSERT_PARTS( - overpaymentComponents.trackedPrincipalDelta == loanPaymentParts.principalPaid, - "xrpl::detail::doOverpayment", - "principal payment matches"); + XRPL_ASSERT_PARTS( + loanPaymentParts.valueChange == tvoChange + managementFeeReleased + interestPart, + "xrpl::detail::doOverpayment", + "interest paid agrees"); + + XRPL_ASSERT_PARTS( + overpaymentComponents.trackedPrincipalDelta == loanPaymentParts.principalPaid, + "xrpl::detail::doOverpayment", + "principal payment matches"); + } // All validations passed, so update the proxy objects (which will // modify the actual Loan ledger object) @@ -1144,11 +1171,13 @@ computePaymentComponents( // Cap each component to never exceed what's actually outstanding deltas.principal = std::min(deltas.principal, currentLedgerState.principalOutstanding); - XRPL_ASSERT_PARTS( - deltas.interest <= currentLedgerState.interestDue, - "xrpl::detail::computePaymentComponents", - "interest due delta not greater than outstanding"); - + if (fixCleanup320Enabled) + { + XRPL_ASSERT_PARTS( + deltas.interest <= currentLedgerState.interestDue, + "xrpl::detail::computePaymentComponents", + "interest due delta not greater than outstanding"); + } // Cap interest to both the outstanding amount AND what's left of the // periodic payment after principal is paid deltas.interest = std::min( @@ -1289,6 +1318,7 @@ computePaymentComponents( */ ExtendedPaymentComponents computeOverpaymentComponents( + Rules const& rules, Asset const& asset, int32_t const loanScale, Number const& overpayment, @@ -1296,10 +1326,13 @@ computeOverpaymentComponents( TenthBips32 const overpaymentFeeRate, TenthBips16 const managementFeeRate) { - XRPL_ASSERT( - overpayment > 0 && isRounded(asset, overpayment, loanScale), - "xrpl::detail::computeOverpaymentComponents : valid overpayment " - "amount"); + if (rules.enabled(fixCleanup3_2_0)) + { + XRPL_ASSERT( + overpayment > 0 && isRounded(asset, overpayment, loanScale), + "xrpl::detail::computeOverpaymentComponents : valid overpayment " + "amount"); + } // First, deduct the fixed overpayment fee from the total amount. // This reduces the effective payment that will be applied to the loan. @@ -2055,6 +2088,7 @@ loanMakePayment( { detail::ExtendedPaymentComponents const overpaymentComponents = detail::computeOverpaymentComponents( + view.rules(), asset, loanScale, overpayment, diff --git a/src/test/app/LendingHelpers_test.cpp b/src/test/app/LendingHelpers_test.cpp index af46dd2e0f..ac8e0764fc 100644 --- a/src/test/app/LendingHelpers_test.cpp +++ b/src/test/app/LendingHelpers_test.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -513,7 +514,9 @@ class LendingHelpers_test : public beast::unit_test::Suite auto const expectedOverpaymentManagementFee = Number{10}; // 10% of 100 auto const expectedPrincipalPortion = Number{400}; // 1,000 - 100 - 500 + Env const env{*this}; auto const components = xrpl::detail::computeOverpaymentComponents( + env.current()->rules(), iou, loanScale, overpayment, @@ -854,7 +857,13 @@ class LendingHelpers_test : public beast::unit_test::Suite Number const overpaymentAmount{50}; auto const overpaymentComponents = computeOverpaymentComponents( - asset, loanScale, overpaymentAmount, TenthBips32(0), TenthBips32(0), managementFeeRate); + env.current()->rules(), + asset, + loanScale, + overpaymentAmount, + TenthBips32(0), + TenthBips32(0), + managementFeeRate); auto const loanProperties = computeLoanProperties( env.current()->rules(), @@ -942,6 +951,7 @@ class LendingHelpers_test : public beast::unit_test::Suite auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval); auto const overpaymentComponents = computeOverpaymentComponents( + env.current()->rules(), asset, loanScale, Number{50, 0}, @@ -1037,6 +1047,7 @@ class LendingHelpers_test : public beast::unit_test::Suite auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval); auto const overpaymentComponents = computeOverpaymentComponents( + env.current()->rules(), asset, loanScale, Number{50, 0}, @@ -1138,6 +1149,7 @@ class LendingHelpers_test : public beast::unit_test::Suite auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval); auto const overpaymentComponents = computeOverpaymentComponents( + env.current()->rules(), asset, loanScale, Number{50, 0}, @@ -1247,6 +1259,7 @@ class LendingHelpers_test : public beast::unit_test::Suite auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval); auto const overpaymentComponents = computeOverpaymentComponents( + env.current()->rules(), asset, loanScale, Number{50, 0}, @@ -1344,7 +1357,6 @@ class LendingHelpers_test : public beast::unit_test::Suite using namespace jtx; using namespace xrpl::detail; - Env const env{*this}; Account const issuer{"issuer"}; PrettyAsset const asset = issuer["USD"]; std::int32_t const loanScale = -5; @@ -1355,7 +1367,9 @@ class LendingHelpers_test : public beast::unit_test::Suite std::uint32_t const paymentsRemaining = 10; auto const periodicRate = loanPeriodicRate(loanInterestRate, paymentInterval); + Env const env{*this}; auto const overpaymentComponents = computeOverpaymentComponents( + env.current()->rules(), asset, loanScale, Number{50, 0}, @@ -1363,87 +1377,97 @@ class LendingHelpers_test : public beast::unit_test::Suite TenthBips32(10'000), // 10% overpayment fee managementFeeRate); - auto const loanProperties = computeLoanProperties( - env.current()->rules(), - asset, - loanPrincipal, - loanInterestRate, - paymentInterval, - paymentsRemaining, - managementFeeRate, - loanScale); + struct Outcome + { + LoanPaymentParts parts; + LoanState oldState; + LoanState newState; + }; - auto const ret = tryOverpayment( - env.current()->rules(), - asset, - loanScale, - overpaymentComponents, - loanProperties.loanState, - loanProperties.periodicPayment, - periodicRate, - paymentsRemaining, - managementFeeRate, - env.journal); + // Run tryOverpayment under a given amendment set. At this (non-near-zero) + // rate computeLoanProperties is amendment-independent, so the loan state + // is identical across the amendment; only tryOverpayment's fixCleanup3_2_0 + // behaviour (the exact-principal pin and the management-fee re-derivation + // from that principal) differs. + auto run = [&](FeatureBitset features) -> std::optional { + Env const env{*this, features}; + auto const loanProperties = computeLoanProperties( + env.current()->rules(), + asset, + loanPrincipal, + loanInterestRate, + paymentInterval, + paymentsRemaining, + managementFeeRate, + loanScale); + auto const ret = tryOverpayment( + env.current()->rules(), + asset, + loanScale, + overpaymentComponents, + loanProperties.loanState, + loanProperties.periodicPayment, + periodicRate, + paymentsRemaining, + managementFeeRate, + env.journal); + if (!BEAST_EXPECT(ret)) + return std::nullopt; + return Outcome{ + .parts = ret->first, + .oldState = loanProperties.loanState, + .newState = ret->second.loanState}; + }; - BEAST_EXPECT(ret); + auto const fixedOpt = run(testableAmendments()); + auto const legacyOpt = run(testableAmendments() - fixCleanup3_2_0); + if (!fixedOpt || !legacyOpt) + { + BEAST_EXPECT(fixedOpt.has_value()); + BEAST_EXPECT(legacyOpt.has_value()); + return; + } + Outcome const& fixed = *fixedOpt; + Outcome const& legacy = *legacyOpt; - auto const& [actualPaymentParts, newLoanProperties] = *ret; - auto const& newState = newLoanProperties.loanState; + // Components that the amendment does not change. The management fee is + // charged against the overpayment interest portion first, so interest + // paid stays 4.5 and fee paid 5.5; the principal repaid is 40 in both. + auto checkCommon = [&](Outcome const& o, char const* tag) { + BEAST_EXPECTS( + (o.parts.interestPaid == Number{45, -1}), + std::string(tag) + " interestPaid " + to_string(o.parts.interestPaid)); + BEAST_EXPECTS( + (o.parts.feePaid == Number{55, -1}), + std::string(tag) + " feePaid " + to_string(o.parts.feePaid)); + BEAST_EXPECTS( + o.parts.principalPaid == 40, + std::string(tag) + " principalPaid " + to_string(o.parts.principalPaid)); + BEAST_EXPECT( + o.parts.principalPaid == + o.oldState.principalOutstanding - o.newState.principalOutstanding); + // v = p + i + m identity: the non-interest part of valueChange equals + // the interest-due change. + BEAST_EXPECT( + o.parts.valueChange - o.parts.interestPaid == + o.newState.interestDue - o.oldState.interestDue); + }; + checkCommon(fixed, "fixed"); + checkCommon(legacy, "legacy"); - // =========== VALIDATE PAYMENT PARTS =========== - - // Since there is loan management fee, the fee is charged against - // overpayment interest portion first, so interest paid remains 4.5 - BEAST_EXPECTS( - (actualPaymentParts.interestPaid == Number{45, -1}), - " interestPaid mismatch: expected 4.5, got " + - to_string(actualPaymentParts.interestPaid)); - - // With overpayment interest portion, value change should equal the - // interest decrease plus overpayment interest portion - BEAST_EXPECTS( - (actualPaymentParts.valueChange == - Number{-164737, -5} + actualPaymentParts.interestPaid), - " valueChange mismatch: expected " + - to_string(Number{-164737, -5} + actualPaymentParts.interestPaid) + ", got " + - to_string(actualPaymentParts.valueChange)); - - // While there is no overpayment fee, fee paid should equal the - // management fee charged against the overpayment interest portion - BEAST_EXPECTS( - (actualPaymentParts.feePaid == Number{55, -1}), - " feePaid mismatch: expected 5.5, got " + to_string(actualPaymentParts.feePaid)); - - BEAST_EXPECTS( - actualPaymentParts.principalPaid == 40, - " principalPaid mismatch: expected 40, got `" + - to_string(actualPaymentParts.principalPaid)); - - // =========== VALIDATE STATE CHANGES =========== - - BEAST_EXPECTS( - actualPaymentParts.principalPaid == - loanProperties.loanState.principalOutstanding - newState.principalOutstanding, - " principalPaid mismatch: expected " + - to_string( - loanProperties.loanState.principalOutstanding - newState.principalOutstanding) + - ", got " + to_string(actualPaymentParts.principalPaid)); - - // Note that the management fee value change is not captured, as this - // value is not needed to correctly update the Vault state. - BEAST_EXPECTS( - (newState.managementFeeDue - loanProperties.loanState.managementFeeDue == - Number{-18304, -5}), - " management fee change mismatch: expected " + to_string(Number{-18304, -5}) + - ", got " + - to_string(newState.managementFeeDue - loanProperties.loanState.managementFeeDue)); - - BEAST_EXPECTS( - actualPaymentParts.valueChange - actualPaymentParts.interestPaid == - newState.interestDue - loanProperties.loanState.interestDue, - " valueChange mismatch: expected " + - to_string(newState.interestDue - loanProperties.loanState.interestDue) + ", got " + - to_string(actualPaymentParts.valueChange - actualPaymentParts.interestPaid)); + // With fixCleanup3_2_0 the management fee is re-derived from the exact + // principal; without it, from the one-scale-unit-high round-trip + // principal. So the management fee outstanding (and hence the value + // change, via v = p + i + m) differ by exactly one scale-unit (1e-5 at + // loanScale -5) between the two paths. + BEAST_EXPECT((fixed.parts.valueChange == Number{-164738, -5} + fixed.parts.interestPaid)); + BEAST_EXPECT( + (fixed.newState.managementFeeDue - fixed.oldState.managementFeeDue == + Number{-18303, -5})); + BEAST_EXPECT((legacy.parts.valueChange == Number{-164737, -5} + legacy.parts.interestPaid)); + BEAST_EXPECT( + (legacy.newState.managementFeeDue - legacy.oldState.managementFeeDue == + Number{-18304, -5})); } public: diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index c380655563..c3b5850231 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -7580,6 +7580,366 @@ protected: attemptWithdrawShares(depositorB, sharesLpB, tesSUCCESS); } + // A residual overpayment can reduce the stored principal by one scale-unit + // *less* than computeOverpaymentComponents predicts, firing the + // "principal change agrees" XRPL_ASSERT_PARTS in doOverpayment: + // + // trackedPrincipalDelta == principalOutstanding - newPrincipalOutstanding + // + // tryOverpayment re-amortizes the loan at the reduced principal, then + // re-derives the theoretical principal from the new periodic payment via + // (P * paymentFactor) / paymentFactor. That round-trip is not exact in + // Number's 19-digit arithmetic; a positive residual pushes the recomputed + // principal a hair above the exact grid point `oldPrincipal - delta`, and + // the Upward rounding in tryOverpayment then bumps it a full scale-unit + // higher. The principal therefore drops by `delta - 1 unit`, not `delta`. + // + // Concrete case (isolated, at the tryOverpayment level): + // A 100 USD loan at the minimum non-zero rate, 3 payments, loanScale -10. + // After one regular payment (principalOutstanding 66.6666666674) a residual overpayment of + // 0.049999998 yields trackedPrincipalDelta 0.048999998 but only reduces the principal by + // 0.0489999979 (newPrincipal 66.6176666695) — short by 1e-10. + // + // With fixCleanup3_2_0, tryOverpayment pins the new principal to the exact, + // on-grid reduction (oldPrincipal - trackedPrincipalDelta) instead of the + // lossy (P*factor)/factor round-trip, so the assertion holds and the + // overpayment applies cleanly. The three "principal change agrees" / + // "interest paid agrees" / "principal payment matches" assertions are + // gated behind the same amendment, so without it they are disabled (the + // server does not abort) and the loan keeps the pre-amendment computation. + // + // The test runs the same scenario under both amendment settings and checks + // the stored principal against a ground-truth value derived independently of + // the loan-state computation under test. + void + testBugOverpaymentPrincipalChange() + { + testcase("bug: doOverpayment asserts 'principal change agrees'"); + + using namespace jtx; + using namespace loan; + using namespace xrpl::detail; + + struct Params + { + TenthBips32 interestRate; + TenthBips16 managementFeeRate; + std::uint32_t paymentTotal; + std::uint32_t paymentInterval; + std::int64_t principal; + Number overpayment; + TenthBips32 overpaymentInterestRate; + TenthBips32 overpaymentFeeRate; + std::optional vaultScale; + }; + + struct Result + { + Number principalOutstanding; // stored principal after the LoanPay + Number expectedNewPrincipal; // ground truth, independent of the fix + Number managementFeeChange; // managementFeeOutstanding after - before + Number unit; // one scale-unit at the loan scale + }; + + auto runScenario = [this](FeatureBitset features, Params const& p) -> Result { + Env env(*this, features); + + Account const issuer{"issuer"}; + Account const lender{"vaultOwner"}; + Account const borrower{"borrower"}; + + env.fund(XRP(1'000'000), issuer, lender, borrower); + env(fset(issuer, asfDefaultRipple)); + env.close(); + + PrettyAsset const iouAsset = issuer["USD"]; + Asset const asset = iouAsset.raw(); + STAmount const iouLimit{asset, Number{9'999'999'999'999'999LL}}; + env(trust(lender, iouLimit)); + env(trust(borrower, iouLimit)); + env(pay(issuer, lender, iouAsset(1'000'000))); + env(pay(issuer, borrower, iouAsset(1'000'000))); + env.close(); + + auto const broker = createVaultAndBroker( + env, + iouAsset, + lender, + {.vaultDeposit = 900'000, + .debtMax = 0, + .managementFeeRate = p.managementFeeRate, + .vaultScale = p.vaultScale}); + + auto const brokerSle = env.le(broker.brokerKeylet()); + BEAST_EXPECT(brokerSle); + auto const loanSequence = brokerSle ? brokerSle->at(sfLoanSequence) : 0; + auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence); + + env(set(borrower, broker.brokerID, Number{p.principal}, tfLoanOverpayment), + Sig(sfCounterpartySignature, lender), + kInterestRate(p.interestRate), + kPaymentTotal(p.paymentTotal), + kPaymentInterval(p.paymentInterval), + kGracePeriod(p.paymentInterval), + kOverpaymentFee(p.overpaymentFeeRate), + kOverpaymentInterestRate(p.overpaymentInterestRate), + Fee(env.current()->fees().base * 2), + Ter(tesSUCCESS)); + env.close(); + + // The single LoanPay below makes one regular payment (the overpayment + // is smaller than one period) and leaves the residual as an + // overpayment. + auto const s = getCurrentState(env, broker, loanKeylet); + auto const periodicRate = loanPeriodicRate(s.interestRate, s.paymentInterval); + auto const onePeriod = computePaymentComponents( + env.current()->rules(), + asset, + s.loanScale, + s.totalValue, + s.principalOutstanding, + s.managementFeeOutstanding, + s.periodicPayment, + periodicRate, + s.paymentRemaining, + p.managementFeeRate); + + // Ground truth: the stored principal must drop by exactly the regular + // payment's principal portion plus the overpayment's principal + // portion. computeOverpaymentComponents depends only on the + // overpayment amount and rates (not on the loan-state computation + // under test), so it is an independent oracle. Both components are + // computed under the same rules as the env so the payment factor + // matches. + auto const overpaymentComponents = computeOverpaymentComponents( + env.current()->rules(), + asset, + s.loanScale, + p.overpayment, + p.overpaymentInterestRate, + p.overpaymentFeeRate, + p.managementFeeRate); + Number const expectedNewPrincipal = s.principalOutstanding - + onePeriod.trackedPrincipalDelta - overpaymentComponents.trackedPrincipalDelta; + + Number const managementFeeBefore = s.managementFeeOutstanding; + + STAmount const payAmount{asset, onePeriod.trackedValueDelta + p.overpayment}; + env(pay(borrower, loanKeylet.key, payAmount), + Txflags(tfLoanOverpayment), + Ter(tesSUCCESS)); + env.close(); + + auto const loanSle = env.le(loanKeylet); + BEAST_EXPECT(loanSle); + + return Result{ + .principalOutstanding = loanSle ? Number{loanSle->at(sfPrincipalOutstanding)} : 0, + .expectedNewPrincipal = expectedNewPrincipal, + .managementFeeChange = + (loanSle ? Number{loanSle->at(sfManagementFeeOutstanding)} : Number{0}) - + managementFeeBefore, + .unit = Number{1, s.loanScale}}; + }; + + // Scenario 1: the original near-zero-rate principal reproduction + // (loanScale -10, no management fee). 0.049999998 is smaller than one + // period, so it stays a residual overpayment. + Params const principalCase{ + .interestRate = TenthBips32{1}, + .managementFeeRate = TenthBips16{0}, + .paymentTotal = 3, + .paymentInterval = 60, + .principal = 100, + .overpayment = Number{49999998, -9}, + .overpaymentInterestRate = TenthBips32{1000}, + .overpaymentFeeRate = TenthBips32{1000}, + .vaultScale = 1}; + + // With fixCleanup3_2_0 the stored principal lands exactly on the + // ground-truth grid point: it is reduced by exactly the overpayment's + // principal portion. This is the key correctness check: if the principal + // pin were removed (even with the assertions still gated off), the lossy + // (P * factor) / factor round-trip would leave the principal one + // scale-unit high and this would fail. + Result const fixed = runScenario(all_, principalCase); + BEAST_EXPECTS( + fixed.principalOutstanding == fixed.expectedNewPrincipal, + "fixed principal " + to_string(fixed.principalOutstanding) + " != expected " + + to_string(fixed.expectedNewPrincipal)); + + // Without the amendment the loan amortizes with the catastrophically + // cancelling near-zero payment factor, so its schedule (and ground truth) + // differ from the fixed case; the gated assertions keep the server from + // aborting and the overpayment still lands exactly on that schedule. + Result const legacy = runScenario(all_ - fixCleanup3_2_0, principalCase); + BEAST_EXPECTS( + legacy.principalOutstanding == legacy.expectedNewPrincipal, + "legacy principal " + to_string(legacy.principalOutstanding) + " != expected " + + to_string(legacy.expectedNewPrincipal)); + + // Scenario 2: a normal-rate loan with a 10% management fee. At a normal + // rate the payment factor is identical across the amendment, so toggling + // fixCleanup3_2_0 isolates the fix. This overpayment (found by search) + // lands on a state where both the principal and the management fee differ + // by one scale-unit between the fixed and legacy paths. + Params const feeCase{ + .interestRate = TenthBips32{10000}, + .managementFeeRate = TenthBips16{10000}, + .paymentTotal = 6, + .paymentInterval = 30u * 24 * 60 * 60, + .principal = 1000, + .overpayment = Number{214367363, -10}, + .overpaymentInterestRate = TenthBips32{0}, + .overpaymentFeeRate = TenthBips32{0}, + .vaultScale = std::nullopt}; + + Result const feeFixed = runScenario(all_, feeCase); + Result const feeLegacy = runScenario(all_ - fixCleanup3_2_0, feeCase); + + // With the fix the principal is the exact reduction; without it the lossy + // (P * factor) / factor round-trip leaves it one scale-unit high. + BEAST_EXPECTS( + feeFixed.principalOutstanding == feeFixed.expectedNewPrincipal, + "fee-case fixed principal " + to_string(feeFixed.principalOutstanding) + + " != expected " + to_string(feeFixed.expectedNewPrincipal)); + BEAST_EXPECTS( + feeLegacy.principalOutstanding == feeLegacy.expectedNewPrincipal + feeLegacy.unit, + "fee-case legacy principal " + to_string(feeLegacy.principalOutstanding) + + " != expected " + to_string(feeLegacy.expectedNewPrincipal + feeLegacy.unit)); + + // Management fee: the overpayment re-amortizes a fee-bearing loan, so the management fee + // outstanding drops. + // + // Unlike the principal that is already at the correct precision, the re-amortized + // management fee is tenthBipsOfValue of the new schedule's gross interest, which depends + // on the recomputed periodic payment. So the expected change below is a pinned constant + // captured from a passing run a magic value only because there is nothing simpler to + // compare against. + // + // At the integration level, toggling the amendment also changes the regular payment's + // rounding so a fixed-vs-legacy comparison cannot isolate the overpayment management-fee + // fix. + BEAST_EXPECT(feeFixed.managementFeeChange == feeLegacy.managementFeeChange); + BEAST_EXPECTS( + (feeFixed.managementFeeChange == Number{-8219709543, -10}), + "fee-case mgmt fee change " + to_string(feeFixed.managementFeeChange)); + } + + // A LoanSet with InterestRate = 1 (0.001% annualized, the minimum non-zero + // rate). At such a near-zero rate the closed-form payment factor + // (1 + r)^n - 1 cancels catastrophically. + // + // Without fixCleanup3_2_0 the resulting amortization is degenerate and the + // LoanSet is rejected with tecPRECISION_LOSS (no loan created). With the + // amendment, computePowerMinusOneHybrid uses a numerically-stable series + // expansion, so the loan is created and the scheduled payments + // (2 * periodicPayment) cover the principal — no economic underpayment + // (yield theft). + // + // The test runs the same LoanSet under both amendment settings and pins the + // exact outcome for each. + void + testLoanSetNearZeroInterestRateSucceeds() + { + testcase("LoanSet near-zero interest rate covers principal"); + + using namespace jtx; + using namespace loan; + + Number const principalRequested{1000}; + + struct Result + { + TER ter = tesSUCCESS; + bool created = false; + std::int32_t loanScale = 0; + Number principal; + Number totalValue; + Number managementFee; + Number periodicPayment; + }; + + auto runScenario = [&](FeatureBitset features, TER expectedTer) -> Result { + Env env(*this, features); + + Account const issuer{"issuer"}; + Account const lender{"vaultOwner"}; + Account const borrower{"borrower"}; + + env.fund(XRP(1'000'000), issuer, lender, borrower); + env(fset(issuer, asfDefaultRipple)); + env.close(); + + PrettyAsset const iouAsset = issuer["USD"]; + STAmount const iouLimit{iouAsset.raw(), Number{9'999'999'999'999'999LL}}; + env(trust(lender, iouLimit)); + env(trust(borrower, iouLimit)); + env(pay(issuer, lender, iouAsset(1'000'000))); + env(pay(issuer, borrower, iouAsset(1'000'000))); + env.close(); + + auto const broker = createVaultAndBroker( + env, + iouAsset, + lender, + {.vaultDeposit = 100'000, .debtMax = 0, .managementFeeRate = TenthBips16{0}}); + + auto const brokerSle = env.le(broker.brokerKeylet()); + BEAST_EXPECT(brokerSle); + auto const loanSequence = brokerSle ? brokerSle->at(sfLoanSequence) : 0; + auto const loanKeylet = keylet::loan(broker.brokerID, loanSequence); + + env(set(borrower, broker.brokerID, principalRequested), + Sig(sfCounterpartySignature, lender), + kInterestRate(TenthBips32{1}), + kPaymentTotal(2), + kPaymentInterval(400), + Fee(env.current()->fees().base * 2), + Ter(expectedTer)); + env.close(); + + Result r; + r.ter = env.ter(); + if (auto const loanSle = env.le(loanKeylet)) + { + r.created = true; + r.loanScale = loanSle->at(sfLoanScale); + r.principal = loanSle->at(sfPrincipalOutstanding); + r.totalValue = loanSle->at(sfTotalValueOutstanding); + r.managementFee = loanSle->at(sfManagementFeeOutstanding); + r.periodicPayment = loanSle->at(sfPeriodicPayment); + } + return r; + }; + + Result const fixed = runScenario(all_, tesSUCCESS); + Result const legacy = runScenario(all_ - fixCleanup3_2_0, tecPRECISION_LOSS); + + // Without the amendment, the catastrophically-cancelling closed-form + // payment factor produces a degenerate amortization that fails + // checkLoanGuards: the LoanSet is rejected with tecPRECISION_LOSS and no + // loan is created. + BEAST_EXPECT(legacy.ter == tecPRECISION_LOSS); + BEAST_EXPECT(!legacy.created); + + // With the amendment the stable series expansion produces a valid loan + // at loanScale -10. + BEAST_EXPECT(fixed.ter == tesSUCCESS); + BEAST_EXPECT(fixed.created); + BEAST_EXPECT(fixed.loanScale == -10); + BEAST_EXPECT(fixed.principal == principalRequested); + BEAST_EXPECT((fixed.totalValue == Number{10000000001903, -10})); + BEAST_EXPECT(fixed.managementFee == beast::kZero); + + // Periodic payment from the numerically-stable series expansion, and the + // scheduled total (2 * periodicPayment) which exceeds the 1000 principal + // — no economic underpayment / yield theft. + BEAST_EXPECT((fixed.periodicPayment == Number{5000000000951293762, -16})); + BEAST_EXPECT((fixed.periodicPayment * 2 == Number{1000000000190258752, -15})); + BEAST_EXPECT(fixed.periodicPayment * 2 > principalRequested); + } + // An overpayment whose residual amount has more precision than loanScale // fires the isRounded(asset, overpayment, loanScale) assertion in // computeOverpaymentComponents (and a downstream "interest paid agrees" @@ -8358,12 +8718,14 @@ protected: testLimitExceeded(); testLoanSetBlockedLoanPayAllowedWhenCanTransferCleared(); testLendingCanTradeClearedNoImpact(); + testBugOverpaymentPrincipalChange(); testBugOverpayUnroundedAmount(); for (auto const flags : {0u, tfLoanOverpayment}) testYieldTheftRounding(flags); testBugInterestDueDeltaCrash(); testFullLifecycleVaultPnLNearZeroRate(); + testLoanSetNearZeroInterestRateSucceeds(); } // Tests run under each entry in amendmentCombinations(). From 7e15621e7b79686da649f5bffb0b56d7eaa33d66 Mon Sep 17 00:00:00 2001 From: Bart Date: Sun, 31 May 2026 18:55:18 -0400 Subject: [PATCH 048/135] release: Bump version to 3.2.0-rc3 (#7371) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- 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 2e33d03088..7de1862dfc 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -23,7 +23,7 @@ namespace { //------------------------------------------------------------------------------ // clang-format off // NOLINTNEXTLINE(readability-identifier-naming) -char const* const versionString = "3.2.0-rc2" +char const* const versionString = "3.2.0-rc3" // clang-format on ; From 0fffe23abc3a42e7d8016fbbd9a0beed3c40bbc9 Mon Sep 17 00:00:00 2001 From: Michael Legleux Date: Sun, 31 May 2026 20:33:19 -0700 Subject: [PATCH 049/135] fix: Adjust xrpld systemd service and update timer (#7374) --- package/debian/rules | 2 +- package/shared/update-xrpld.timer | 2 +- package/shared/xrpld.service | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package/debian/rules b/package/debian/rules index cd94da7e5b..612fe1b1a9 100644 --- a/package/debian/rules +++ b/package/debian/rules @@ -10,7 +10,7 @@ override_dh_auto_configure override_dh_auto_build override_dh_auto_test: override_dh_installsystemd: dh_installsystemd --no-stop-on-upgrade xrpld.service - dh_installsystemd --name=update-xrpld --no-start update-xrpld.service update-xrpld.timer + dh_installsystemd --name=update-xrpld --no-enable --no-start update-xrpld.service update-xrpld.timer execute_before_dh_installtmpfiles: dh_installsysusers diff --git a/package/shared/update-xrpld.timer b/package/shared/update-xrpld.timer index 21dabf1400..9fba09d30a 100644 --- a/package/shared/update-xrpld.timer +++ b/package/shared/update-xrpld.timer @@ -3,7 +3,7 @@ Description=Daily xrpld update check [Timer] OnCalendar=*-*-* 00:00:00 -RandomizedDelaySec=24h +RandomizedDelaySec=4h Persistent=true [Install] diff --git a/package/shared/xrpld.service b/package/shared/xrpld.service index 72b6cc9938..8e10ed2eee 100644 --- a/package/shared/xrpld.service +++ b/package/shared/xrpld.service @@ -2,14 +2,15 @@ Description=XRP Ledger Daemon After=network-online.target Wants=network-online.target -StartLimitIntervalSec=300 +StartLimitIntervalSec=5min StartLimitBurst=5 [Service] Type=simple ExecStart=/usr/bin/xrpld --net --silent --conf /etc/xrpld/xrpld.cfg -Restart=always +Restart=on-failure RestartSec=5s +TimeoutStopSec=5min NoNewPrivileges=true ProtectSystem=full ProtectHome=true From 109b64910616a84aad9b7a06514526431c8fa489 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Mon, 1 Jun 2026 17:27:13 +0200 Subject: [PATCH 050/135] refactor: Use `STLedgerEntry` type aliases instead of `std::shared_ptr` (#7282) --- .../codegen/templates/LedgerEntry.h.mako | 4 +- include/xrpl/basics/TaggedCache.h | 2 +- include/xrpl/ledger/ApplyView.h | 20 ++--- include/xrpl/ledger/ApplyViewImpl.h | 4 +- include/xrpl/ledger/BookDirs.h | 6 +- include/xrpl/ledger/CachedView.h | 2 +- include/xrpl/ledger/Dir.h | 6 +- include/xrpl/ledger/Ledger.h | 10 +-- include/xrpl/ledger/OpenView.h | 8 +- include/xrpl/ledger/RawView.h | 6 +- include/xrpl/ledger/ReadView.h | 6 +- include/xrpl/ledger/View.h | 7 +- include/xrpl/ledger/detail/ApplyStateTable.h | 30 +++---- include/xrpl/ledger/detail/ApplyViewBase.h | 16 ++-- include/xrpl/ledger/detail/RawStateTable.h | 12 +-- include/xrpl/ledger/helpers/AMMHelpers.h | 4 +- .../xrpl/ledger/helpers/AccountRootHelpers.h | 13 +-- .../xrpl/ledger/helpers/CredentialHelpers.h | 4 +- include/xrpl/ledger/helpers/DelegateHelpers.h | 4 +- .../xrpl/ledger/helpers/DirectoryHelpers.h | 22 ++--- include/xrpl/ledger/helpers/EscrowHelpers.h | 6 +- include/xrpl/ledger/helpers/NFTokenHelpers.h | 13 +-- include/xrpl/ledger/helpers/OfferHelpers.h | 4 +- .../ledger/helpers/PaymentChannelHelpers.h | 6 +- .../xrpl/ledger/helpers/RippleStateHelpers.h | 6 +- include/xrpl/ledger/helpers/VaultHelpers.h | 24 ++---- .../xrpl/protocol_autogen/LedgerEntryBase.h | 6 +- .../protocol_autogen/ledger_entries/AMM.h | 4 +- .../ledger_entries/AccountRoot.h | 4 +- .../ledger_entries/Amendments.h | 4 +- .../protocol_autogen/ledger_entries/Bridge.h | 4 +- .../protocol_autogen/ledger_entries/Check.h | 4 +- .../ledger_entries/Credential.h | 4 +- .../protocol_autogen/ledger_entries/DID.h | 4 +- .../ledger_entries/Delegate.h | 4 +- .../ledger_entries/DepositPreauth.h | 4 +- .../ledger_entries/DirectoryNode.h | 4 +- .../protocol_autogen/ledger_entries/Escrow.h | 4 +- .../ledger_entries/FeeSettings.h | 4 +- .../ledger_entries/LedgerHashes.h | 4 +- .../protocol_autogen/ledger_entries/Loan.h | 4 +- .../ledger_entries/LoanBroker.h | 4 +- .../protocol_autogen/ledger_entries/MPToken.h | 4 +- .../ledger_entries/MPTokenIssuance.h | 4 +- .../ledger_entries/NFTokenOffer.h | 4 +- .../ledger_entries/NFTokenPage.h | 4 +- .../ledger_entries/NegativeUNL.h | 4 +- .../protocol_autogen/ledger_entries/Offer.h | 4 +- .../protocol_autogen/ledger_entries/Oracle.h | 4 +- .../ledger_entries/PayChannel.h | 4 +- .../ledger_entries/PermissionedDomain.h | 4 +- .../ledger_entries/RippleState.h | 4 +- .../ledger_entries/SignerList.h | 4 +- .../protocol_autogen/ledger_entries/Ticket.h | 4 +- .../protocol_autogen/ledger_entries/Vault.h | 4 +- .../ledger_entries/XChainOwnedClaimID.h | 4 +- .../XChainOwnedCreateAccountClaimID.h | 4 +- include/xrpl/tx/ApplyContext.h | 4 +- include/xrpl/tx/Transactor.h | 7 +- include/xrpl/tx/invariants/AMMInvariant.h | 2 +- include/xrpl/tx/invariants/FreezeInvariant.h | 19 ++-- include/xrpl/tx/invariants/InvariantCheck.h | 35 ++++---- .../xrpl/tx/invariants/LoanBrokerInvariant.h | 2 +- include/xrpl/tx/invariants/LoanInvariant.h | 2 +- include/xrpl/tx/invariants/MPTInvariant.h | 4 +- include/xrpl/tx/invariants/NFTInvariant.h | 4 +- .../tx/invariants/PermissionedDEXInvariant.h | 2 +- .../invariants/PermissionedDomainInvariant.h | 2 +- include/xrpl/tx/invariants/VaultInvariant.h | 2 +- include/xrpl/tx/paths/BookTip.h | 2 +- .../tx/transactors/account/AccountDelete.h | 5 +- .../xrpl/tx/transactors/account/AccountSet.h | 5 +- .../tx/transactors/account/SetRegularKey.h | 5 +- .../tx/transactors/account/SignerListSet.h | 5 +- .../xrpl/tx/transactors/bridge/XChainBridge.h | 40 ++------- .../xrpl/tx/transactors/check/CheckCancel.h | 5 +- include/xrpl/tx/transactors/check/CheckCash.h | 5 +- .../xrpl/tx/transactors/check/CheckCreate.h | 5 +- .../credentials/CredentialAccept.h | 5 +- .../credentials/CredentialCreate.h | 5 +- .../credentials/CredentialDelete.h | 5 +- .../tx/transactors/delegate/DelegateSet.h | 7 +- include/xrpl/tx/transactors/dex/AMMBid.h | 5 +- include/xrpl/tx/transactors/dex/AMMClawback.h | 5 +- include/xrpl/tx/transactors/dex/AMMCreate.h | 5 +- include/xrpl/tx/transactors/dex/AMMDelete.h | 5 +- include/xrpl/tx/transactors/dex/AMMDeposit.h | 5 +- include/xrpl/tx/transactors/dex/AMMVote.h | 5 +- include/xrpl/tx/transactors/dex/AMMWithdraw.h | 7 +- include/xrpl/tx/transactors/dex/OfferCancel.h | 5 +- include/xrpl/tx/transactors/dex/OfferCreate.h | 7 +- include/xrpl/tx/transactors/did/DIDDelete.h | 7 +- include/xrpl/tx/transactors/did/DIDSet.h | 5 +- .../xrpl/tx/transactors/escrow/EscrowCancel.h | 5 +- .../xrpl/tx/transactors/escrow/EscrowCreate.h | 5 +- .../xrpl/tx/transactors/escrow/EscrowFinish.h | 5 +- .../lending/LoanBrokerCoverClawback.h | 5 +- .../lending/LoanBrokerCoverDeposit.h | 5 +- .../lending/LoanBrokerCoverWithdraw.h | 5 +- .../tx/transactors/lending/LoanBrokerDelete.h | 5 +- .../tx/transactors/lending/LoanBrokerSet.h | 5 +- .../xrpl/tx/transactors/lending/LoanDelete.h | 5 +- .../xrpl/tx/transactors/lending/LoanManage.h | 5 +- include/xrpl/tx/transactors/lending/LoanPay.h | 5 +- include/xrpl/tx/transactors/lending/LoanSet.h | 5 +- .../tx/transactors/nft/NFTokenAcceptOffer.h | 9 +- include/xrpl/tx/transactors/nft/NFTokenBurn.h | 5 +- .../tx/transactors/nft/NFTokenCancelOffer.h | 5 +- .../tx/transactors/nft/NFTokenCreateOffer.h | 5 +- include/xrpl/tx/transactors/nft/NFTokenMint.h | 5 +- .../xrpl/tx/transactors/nft/NFTokenModify.h | 5 +- .../xrpl/tx/transactors/oracle/OracleDelete.h | 11 +-- .../xrpl/tx/transactors/oracle/OracleSet.h | 5 +- .../tx/transactors/payment/DepositPreauth.h | 5 +- include/xrpl/tx/transactors/payment/Payment.h | 5 +- .../payment_channel/PaymentChannelClaim.h | 5 +- .../payment_channel/PaymentChannelCreate.h | 5 +- .../payment_channel/PaymentChannelFund.h | 5 +- .../PermissionedDomainDelete.h | 5 +- .../PermissionedDomainSet.h | 5 +- include/xrpl/tx/transactors/system/Batch.h | 5 +- include/xrpl/tx/transactors/system/Change.h | 5 +- .../tx/transactors/system/LedgerStateFix.h | 5 +- .../xrpl/tx/transactors/system/TicketCreate.h | 5 +- include/xrpl/tx/transactors/token/Clawback.h | 5 +- .../tx/transactors/token/MPTokenAuthorize.h | 5 +- .../transactors/token/MPTokenIssuanceCreate.h | 5 +- .../token/MPTokenIssuanceDestroy.h | 5 +- .../tx/transactors/token/MPTokenIssuanceSet.h | 5 +- include/xrpl/tx/transactors/token/TrustSet.h | 5 +- .../xrpl/tx/transactors/vault/VaultClawback.h | 9 +- .../xrpl/tx/transactors/vault/VaultCreate.h | 5 +- .../xrpl/tx/transactors/vault/VaultDelete.h | 5 +- .../xrpl/tx/transactors/vault/VaultDeposit.h | 5 +- include/xrpl/tx/transactors/vault/VaultSet.h | 5 +- .../xrpl/tx/transactors/vault/VaultWithdraw.h | 5 +- src/libxrpl/ledger/ApplyStateTable.cpp | 28 +++--- src/libxrpl/ledger/ApplyView.cpp | 6 +- src/libxrpl/ledger/ApplyViewBase.cpp | 16 ++-- src/libxrpl/ledger/ApplyViewImpl.cpp | 9 +- src/libxrpl/ledger/CachedView.cpp | 3 +- src/libxrpl/ledger/Ledger.cpp | 10 +-- src/libxrpl/ledger/OpenView.cpp | 8 +- src/libxrpl/ledger/RawStateTable.cpp | 14 +-- src/libxrpl/ledger/View.cpp | 9 +- src/libxrpl/ledger/helpers/AMMHelpers.cpp | 9 +- .../ledger/helpers/AccountRootHelpers.cpp | 12 +-- .../ledger/helpers/CredentialHelpers.cpp | 7 +- .../ledger/helpers/DirectoryHelpers.cpp | 19 ++-- src/libxrpl/ledger/helpers/NFTokenHelpers.cpp | 36 ++++---- src/libxrpl/ledger/helpers/OfferHelpers.cpp | 4 +- .../ledger/helpers/PaymentChannelHelpers.cpp | 8 +- .../ledger/helpers/RippleStateHelpers.cpp | 6 +- src/libxrpl/ledger/helpers/VaultHelpers.cpp | 19 ++-- src/libxrpl/tx/ApplyContext.cpp | 19 ++-- src/libxrpl/tx/Transactor.cpp | 9 +- src/libxrpl/tx/invariants/AMMInvariant.cpp | 6 +- src/libxrpl/tx/invariants/FreezeInvariant.cpp | 22 ++--- src/libxrpl/tx/invariants/InvariantCheck.cpp | 70 +++------------ .../tx/invariants/LoanBrokerInvariant.cpp | 7 +- src/libxrpl/tx/invariants/LoanInvariant.cpp | 7 +- src/libxrpl/tx/invariants/MPTInvariant.cpp | 11 +-- src/libxrpl/tx/invariants/NFTInvariant.cpp | 13 +-- .../invariants/PermissionedDEXInvariant.cpp | 7 +- .../PermissionedDomainInvariant.cpp | 8 +- src/libxrpl/tx/invariants/VaultInvariant.cpp | 6 +- src/libxrpl/tx/paths/BookTip.cpp | 4 +- src/libxrpl/tx/paths/DirectStep.cpp | 8 +- src/libxrpl/tx/paths/MPTEndpointStep.cpp | 9 +- src/libxrpl/tx/paths/OfferStream.cpp | 3 +- .../tx/transactors/account/AccountDelete.cpp | 30 +++---- .../tx/transactors/account/AccountSet.cpp | 6 +- .../tx/transactors/account/SetRegularKey.cpp | 7 +- .../tx/transactors/account/SignerListSet.cpp | 5 +- .../tx/transactors/bridge/XChainBridge.cpp | 48 +++-------- .../tx/transactors/check/CheckCancel.cpp | 7 +- .../tx/transactors/check/CheckCash.cpp | 8 +- .../tx/transactors/check/CheckCreate.cpp | 5 +- .../credentials/CredentialAccept.cpp | 7 +- .../credentials/CredentialCreate.cpp | 5 +- .../credentials/CredentialDelete.cpp | 7 +- .../tx/transactors/delegate/DelegateSet.cpp | 7 +- .../tx/transactors/delegate/DelegateUtils.cpp | 5 +- src/libxrpl/tx/transactors/dex/AMMBid.cpp | 6 +- .../tx/transactors/dex/AMMClawback.cpp | 6 +- src/libxrpl/tx/transactors/dex/AMMCreate.cpp | 5 +- src/libxrpl/tx/transactors/dex/AMMDelete.cpp | 7 +- src/libxrpl/tx/transactors/dex/AMMDeposit.cpp | 6 +- src/libxrpl/tx/transactors/dex/AMMVote.cpp | 6 +- .../tx/transactors/dex/AMMWithdraw.cpp | 8 +- .../tx/transactors/dex/OfferCancel.cpp | 7 +- .../tx/transactors/dex/OfferCreate.cpp | 7 +- src/libxrpl/tx/transactors/did/DIDDelete.cpp | 13 +-- src/libxrpl/tx/transactors/did/DIDSet.cpp | 7 +- .../tx/transactors/escrow/EscrowCancel.cpp | 6 +- .../tx/transactors/escrow/EscrowCreate.cpp | 5 +- .../tx/transactors/escrow/EscrowFinish.cpp | 6 +- .../lending/LoanBrokerCoverClawback.cpp | 6 +- .../lending/LoanBrokerCoverDeposit.cpp | 7 +- .../lending/LoanBrokerCoverWithdraw.cpp | 7 +- .../transactors/lending/LoanBrokerDelete.cpp | 7 +- .../tx/transactors/lending/LoanBrokerSet.cpp | 5 +- .../tx/transactors/lending/LoanDelete.cpp | 7 +- .../tx/transactors/lending/LoanManage.cpp | 7 +- .../tx/transactors/lending/LoanPay.cpp | 6 +- .../tx/transactors/lending/LoanSet.cpp | 5 +- .../tx/transactors/nft/NFTokenAcceptOffer.cpp | 15 ++-- .../tx/transactors/nft/NFTokenBurn.cpp | 7 +- .../tx/transactors/nft/NFTokenCancelOffer.cpp | 7 +- .../tx/transactors/nft/NFTokenCreateOffer.cpp | 7 +- .../tx/transactors/nft/NFTokenMint.cpp | 6 +- .../tx/transactors/nft/NFTokenModify.cpp | 7 +- .../tx/transactors/oracle/OracleDelete.cpp | 9 +- .../tx/transactors/oracle/OracleSet.cpp | 5 +- .../tx/transactors/payment/DepositPreauth.cpp | 5 +- .../tx/transactors/payment/Payment.cpp | 5 +- .../payment_channel/PaymentChannelClaim.cpp | 6 +- .../payment_channel/PaymentChannelCreate.cpp | 5 +- .../payment_channel/PaymentChannelFund.cpp | 7 +- .../PermissionedDomainDelete.cpp | 7 +- .../PermissionedDomainSet.cpp | 5 +- src/libxrpl/tx/transactors/system/Batch.cpp | 6 +- src/libxrpl/tx/transactors/system/Change.cpp | 5 +- .../tx/transactors/system/LedgerStateFix.cpp | 6 +- .../tx/transactors/system/TicketCreate.cpp | 5 +- src/libxrpl/tx/transactors/token/Clawback.cpp | 6 +- .../tx/transactors/token/MPTokenAuthorize.cpp | 9 +- .../token/MPTokenIssuanceCreate.cpp | 5 +- .../token/MPTokenIssuanceDestroy.cpp | 7 +- .../transactors/token/MPTokenIssuanceSet.cpp | 8 +- src/libxrpl/tx/transactors/token/TrustSet.cpp | 6 +- .../tx/transactors/vault/VaultClawback.cpp | 12 +-- .../tx/transactors/vault/VaultCreate.cpp | 5 +- .../tx/transactors/vault/VaultDelete.cpp | 7 +- .../tx/transactors/vault/VaultDeposit.cpp | 6 +- src/libxrpl/tx/transactors/vault/VaultSet.cpp | 7 +- .../tx/transactors/vault/VaultWithdraw.cpp | 6 +- src/test/app/CheckMPT_test.cpp | 7 +- src/test/app/Check_test.cpp | 7 +- src/test/app/FlowMPT_test.cpp | 51 ++++++----- src/test/app/Flow_test.cpp | 9 +- src/test/app/Invariants_test.cpp | 2 +- src/test/app/OfferMPT_test.cpp | 45 ++++------ src/test/app/Offer_test.cpp | 37 ++++---- src/test/app/PayChan_test.cpp | 10 +-- src/test/app/XChain_test.cpp | 10 +-- src/test/jtx/Env.h | 4 +- src/test/jtx/PathSet.h | 4 +- src/test/jtx/TestHelpers.h | 2 +- src/test/jtx/impl/Env.cpp | 4 +- src/test/jtx/impl/TestHelpers.cpp | 2 +- src/test/jtx/impl/owners.cpp | 4 +- src/test/ledger/View_test.cpp | 4 +- src/xrpld/app/misc/NetworkOPs.cpp | 2 +- src/xrpld/app/misc/TxQ.h | 8 +- src/xrpld/app/misc/detail/TxQ.cpp | 12 ++- src/xrpld/consensus/DisputedTx.h | 2 +- src/xrpld/rpc/detail/AssetCache.cpp | 2 +- src/xrpld/rpc/detail/RPCHelpers.cpp | 8 +- src/xrpld/rpc/detail/RPCHelpers.h | 7 +- src/xrpld/rpc/detail/TransactionSign.cpp | 9 +- src/xrpld/rpc/detail/TrustLine.cpp | 22 ++--- src/xrpld/rpc/detail/TrustLine.h | 8 +- .../rpc/handlers/account/AccountChannels.cpp | 5 +- .../rpc/handlers/account/AccountLines.cpp | 3 +- .../rpc/handlers/account/AccountOffers.cpp | 7 +- .../rpc/handlers/account/GatewayBalances.cpp | 2 +- .../rpc/handlers/account/NoRippleCheck.cpp | 86 +++++++++---------- src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp | 2 +- .../handlers/orderbook/DepositAuthorized.cpp | 4 +- .../handlers/orderbook/GetAggregatePrice.cpp | 2 +- .../rpc/handlers/orderbook/NFTOffersHelpers.h | 14 +-- .../rpc/handlers/transaction/Simulate.cpp | 2 +- 273 files changed, 719 insertions(+), 1475 deletions(-) diff --git a/cmake/scripts/codegen/templates/LedgerEntry.h.mako b/cmake/scripts/codegen/templates/LedgerEntry.h.mako index 31029cd311..63f5f39ef9 100644 --- a/cmake/scripts/codegen/templates/LedgerEntry.h.mako +++ b/cmake/scripts/codegen/templates/LedgerEntry.h.mako @@ -33,7 +33,7 @@ public: * @brief Construct a ${name} ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit ${name}(std::shared_ptr sle) + explicit ${name}(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -168,7 +168,7 @@ ${field['typeData']['setter_type']} ${field['paramName']}${',' if i < len(requir * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - ${name}Builder(std::shared_ptr sle) + ${name}Builder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ${tag}) { diff --git a/include/xrpl/basics/TaggedCache.h b/include/xrpl/basics/TaggedCache.h index fca9eabde2..380b7c687f 100644 --- a/include/xrpl/basics/TaggedCache.h +++ b/include/xrpl/basics/TaggedCache.h @@ -157,7 +157,7 @@ public: /** Fetch an item from the cache. If the digest was not found, Handler will be called with this signature: - std::shared_ptr(void) + SLE::const_pointer(void) */ template SharedPointerType diff --git a/include/xrpl/ledger/ApplyView.h b/include/xrpl/ledger/ApplyView.h index f825311e1d..362eae0f79 100644 --- a/include/xrpl/ledger/ApplyView.h +++ b/include/xrpl/ledger/ApplyView.h @@ -123,7 +123,7 @@ private: bool preserveOrder, Keylet const& directory, uint256 const& key, - std::function const&)> const& describe); + std::function const& describe); public: ApplyView() = default; @@ -153,7 +153,7 @@ public: @return `nullptr` if the key is not present */ - virtual std::shared_ptr + virtual SLE::pointer peek(Keylet const& k) = 0; /** Remove a peeked SLE. @@ -168,7 +168,7 @@ public: The key is no longer associated with the SLE. */ virtual void - erase(std::shared_ptr const& sle) = 0; + erase(SLE::ref sle) = 0; /** Insert a new state SLE @@ -189,7 +189,7 @@ public: @note The key is taken from the SLE */ virtual void - insert(std::shared_ptr const& sle) = 0; + insert(SLE::ref sle) = 0; /** Indicate changes to a peeked SLE @@ -208,7 +208,7 @@ public: */ /** @{ */ virtual void - update(std::shared_ptr const& sle) = 0; + update(SLE::ref sle) = 0; //-------------------------------------------------------------------------- @@ -301,7 +301,7 @@ public: dirAppend( Keylet const& directory, Keylet const& key, - std::function const&)> const& describe) + std::function const& describe) { if (key.type != ltOFFER) { @@ -340,7 +340,7 @@ public: dirInsert( Keylet const& directory, uint256 const& key, - std::function const&)> const& describe) + std::function const& describe) { return dirAdd(false, directory, key, describe); } @@ -349,7 +349,7 @@ public: dirInsert( Keylet const& directory, Keylet const& key, - std::function const&)> const& describe) + std::function const& describe) { return dirAdd(false, directory, key.key, describe); } @@ -411,7 +411,7 @@ createRoot( ApplyView& view, Keylet const& directory, uint256 const& key, - std::function const&)> const& describe); + std::function const& describe); auto findPreviousPage(ApplyView& view, Keylet const& directory, SLE::ref start); @@ -434,7 +434,7 @@ insertPage( SLE::ref next, uint256 const& key, Keylet const& directory, - std::function const&)> const& describe); + std::function const& describe); } // namespace directory } // namespace xrpl diff --git a/include/xrpl/ledger/ApplyViewImpl.h b/include/xrpl/ledger/ApplyViewImpl.h index 7f790f2be5..1245568630 100644 --- a/include/xrpl/ledger/ApplyViewImpl.h +++ b/include/xrpl/ledger/ApplyViewImpl.h @@ -67,8 +67,8 @@ public: std::function const& before, - std::shared_ptr const& after)> const& func); + SLE::const_ref before, + SLE::const_ref after)> const& func); private: std::optional deliver_; diff --git a/include/xrpl/ledger/BookDirs.h b/include/xrpl/ledger/BookDirs.h index 7eaede14ec..36798934da 100644 --- a/include/xrpl/ledger/BookDirs.h +++ b/include/xrpl/ledger/BookDirs.h @@ -11,13 +11,13 @@ private: uint256 const root_; uint256 const nextQuality_; uint256 const key_; - std::shared_ptr sle_ = nullptr; + SLE::const_pointer sle_ = nullptr; unsigned int entry_ = 0; uint256 index_; public: class const_iterator; // NOLINT(readability-identifier-naming) - using value_type = std::shared_ptr; + using value_type = SLE::const_pointer; BookDirs(ReadView const&, Book const&); @@ -76,7 +76,7 @@ private: uint256 nextQuality_; uint256 key_; uint256 curKey_; - std::shared_ptr sle_; + SLE::const_pointer sle_; unsigned int entry_ = 0; uint256 index_; std::optional mutable cache_; diff --git a/include/xrpl/ledger/CachedView.h b/include/xrpl/ledger/CachedView.h index 34a75e4c07..462db48ee3 100644 --- a/include/xrpl/ledger/CachedView.h +++ b/include/xrpl/ledger/CachedView.h @@ -36,7 +36,7 @@ public: bool exists(Keylet const& k) const override; - std::shared_ptr + SLE::const_pointer read(Keylet const& k) const override; bool diff --git a/include/xrpl/ledger/Dir.h b/include/xrpl/ledger/Dir.h index cfbef357b1..d305e21938 100644 --- a/include/xrpl/ledger/Dir.h +++ b/include/xrpl/ledger/Dir.h @@ -22,12 +22,12 @@ class Dir private: ReadView const* view_ = nullptr; Keylet root_; - std::shared_ptr sle_; + SLE::const_pointer sle_; STVector256 const* indexes_ = nullptr; public: class ConstIterator; - using value_type = std::shared_ptr; + using value_type = SLE::const_pointer; Dir(ReadView const&, Keylet const&); @@ -102,7 +102,7 @@ private: Keylet page_; uint256 index_; std::optional mutable cache_; - std::shared_ptr sle_; + SLE::const_pointer sle_; STVector256 const* indexes_ = nullptr; std::vector::const_iterator it_; }; diff --git a/include/xrpl/ledger/Ledger.h b/include/xrpl/ledger/Ledger.h index 351f7d80e5..5f7d79c61d 100644 --- a/include/xrpl/ledger/Ledger.h +++ b/include/xrpl/ledger/Ledger.h @@ -166,7 +166,7 @@ public: std::optional succ(uint256 const& key, std::optional const& last = std::nullopt) const override; - std::shared_ptr + SLE::const_pointer read(Keylet const& k) const override; std::unique_ptr @@ -202,16 +202,16 @@ public: // void - rawErase(std::shared_ptr const& sle) override; + rawErase(SLE::ref sle) override; void - rawInsert(std::shared_ptr const& sle) override; + rawInsert(SLE::ref sle) override; void rawErase(uint256 const& key); void - rawReplace(std::shared_ptr const& sle) override; + rawReplace(SLE::ref sle) override; void rawDestroyXRP(XRPAmount const& fee) override @@ -361,7 +361,7 @@ public: bool isVotingLedger() const; - std::shared_ptr + SLE::pointer peek(Keylet const& k) const; private: diff --git a/include/xrpl/ledger/OpenView.h b/include/xrpl/ledger/OpenView.h index 18d1a9399c..4ba2a7759b 100644 --- a/include/xrpl/ledger/OpenView.h +++ b/include/xrpl/ledger/OpenView.h @@ -197,7 +197,7 @@ public: std::optional succ(key_type const& key, std::optional const& last = std::nullopt) const override; - std::shared_ptr + SLE::const_pointer read(Keylet const& k) const override; std::unique_ptr @@ -224,13 +224,13 @@ public: // RawView void - rawErase(std::shared_ptr const& sle) override; + rawErase(SLE::ref sle) override; void - rawInsert(std::shared_ptr const& sle) override; + rawInsert(SLE::ref sle) override; void - rawReplace(std::shared_ptr const& sle) override; + rawReplace(SLE::ref sle) override; void rawDestroyXRP(XRPAmount const& fee) override; diff --git a/include/xrpl/ledger/RawView.h b/include/xrpl/ledger/RawView.h index cfcf807e13..cf61c3e814 100644 --- a/include/xrpl/ledger/RawView.h +++ b/include/xrpl/ledger/RawView.h @@ -25,7 +25,7 @@ public: can calculate metadata. */ virtual void - rawErase(std::shared_ptr const& sle) = 0; + rawErase(SLE::ref sle) = 0; /** Unconditionally insert a state item. @@ -39,7 +39,7 @@ public: @note The key is taken from the SLE */ virtual void - rawInsert(std::shared_ptr const& sle) = 0; + rawInsert(SLE::ref sle) = 0; /** Unconditionally replace a state item. @@ -54,7 +54,7 @@ public: @note The key is taken from the SLE */ virtual void - rawReplace(std::shared_ptr const& sle) = 0; + rawReplace(SLE::ref sle) = 0; /** Destroy XRP. diff --git a/include/xrpl/ledger/ReadView.h b/include/xrpl/ledger/ReadView.h index 4f9bf9c31d..f4ee7e6fd2 100644 --- a/include/xrpl/ledger/ReadView.h +++ b/include/xrpl/ledger/ReadView.h @@ -34,9 +34,9 @@ public: using key_type = uint256; - using mapped_type = std::shared_ptr; + using mapped_type = SLE::const_pointer; - struct SlesType : detail::ReadViewFwdRange> + struct SlesType : detail::ReadViewFwdRange { explicit SlesType(ReadView const& view); [[nodiscard]] Iterator @@ -143,7 +143,7 @@ public: @return `nullptr` if the key is not present or if the type does not match. */ - [[nodiscard]] virtual std::shared_ptr + [[nodiscard]] virtual SLE::const_pointer read(Keylet const& k) const = 0; // Accounts in a payment are not allowed to use assets acquired during that diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index 0d76c98a73..255413e459 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -135,7 +134,7 @@ areCompatible( dirLink( ApplyView& view, AccountID const& owner, - std::shared_ptr& object, + SLE::pointer& object, SF_UINT64 const& node = sfOwnerNode); /** Checks that can withdraw funds from an object to itself or a destination. @@ -215,8 +214,8 @@ doWithdraw( * (if should not be skipped) and if the entry should be skipped. The status * is always tesSUCCESS if the entry should be skipped. */ -using EntryDeleter = std::function< - std::pair(LedgerEntryType, uint256 const&, std::shared_ptr&)>; +using EntryDeleter = + std::function(LedgerEntryType, uint256 const&, SLE::pointer&)>; /** Cleanup owner directory entries on account delete. * Used for a regular and AMM accounts deletion. The caller * has to provide the deleter function, which handles details of diff --git a/include/xrpl/ledger/detail/ApplyStateTable.h b/include/xrpl/ledger/detail/ApplyStateTable.h index 7b18f742b4..f40e3d0d1c 100644 --- a/include/xrpl/ledger/detail/ApplyStateTable.h +++ b/include/xrpl/ledger/detail/ApplyStateTable.h @@ -8,8 +8,6 @@ #include #include -#include - namespace xrpl::detail { // Helper class that buffers modifications @@ -26,7 +24,7 @@ private: Modify, }; - using items_t = std::map>>; + using items_t = std::map>; items_t items_; XRPAmount dropsDestroyed_{0}; @@ -60,10 +58,10 @@ public: [[nodiscard]] std::optional succ(ReadView const& base, key_type const& key, std::optional const& last) const; - [[nodiscard]] std::shared_ptr + [[nodiscard]] SLE::const_pointer read(ReadView const& base, Keylet const& k) const; - std::shared_ptr + SLE::pointer peek(ReadView const& base, Keylet const& k); [[nodiscard]] std::size_t @@ -75,23 +73,23 @@ public: std::function const& before, - std::shared_ptr const& after)> const& func) const; + SLE::const_ref before, + SLE::const_ref after)> const& func) const; void - erase(ReadView const& base, std::shared_ptr const& sle); + erase(ReadView const& base, SLE::ref sle); void - rawErase(ReadView const& base, std::shared_ptr const& sle); + rawErase(ReadView const& base, SLE::ref sle); void - insert(ReadView const& base, std::shared_ptr const& sle); + insert(ReadView const& base, SLE::ref sle); void - update(ReadView const& base, std::shared_ptr const& sle); + update(ReadView const& base, SLE::ref sle); void - replace(ReadView const& base, std::shared_ptr const& sle); + replace(ReadView const& base, SLE::ref sle); void destroyXRP(XRPAmount const& fee); @@ -104,12 +102,12 @@ public: } private: - using Mods = hash_map>; + using Mods = hash_map; static void - threadItem(TxMeta& meta, std::shared_ptr const& to); + threadItem(TxMeta& meta, SLE::ref to); - std::shared_ptr + SLE::pointer getForMod(ReadView const& base, key_type const& key, Mods& mods, beast::Journal j); void @@ -119,7 +117,7 @@ private: threadOwners( ReadView const& base, TxMeta& meta, - std::shared_ptr const& sle, + SLE::const_ref sle, Mods& mods, beast::Journal j); }; diff --git a/include/xrpl/ledger/detail/ApplyViewBase.h b/include/xrpl/ledger/detail/ApplyViewBase.h index 558c9e5d4d..d6493c46a8 100644 --- a/include/xrpl/ledger/detail/ApplyViewBase.h +++ b/include/xrpl/ledger/detail/ApplyViewBase.h @@ -40,7 +40,7 @@ public: [[nodiscard]] std::optional succ(key_type const& key, std::optional const& last = std::nullopt) const override; - [[nodiscard]] std::shared_ptr + [[nodiscard]] SLE::const_pointer read(Keylet const& k) const override; [[nodiscard]] std::unique_ptr @@ -69,28 +69,28 @@ public: [[nodiscard]] ApplyFlags flags() const override; - std::shared_ptr + SLE::pointer peek(Keylet const& k) override; void - erase(std::shared_ptr const& sle) override; + erase(SLE::ref sle) override; void - insert(std::shared_ptr const& sle) override; + insert(SLE::ref sle) override; void - update(std::shared_ptr const& sle) override; + update(SLE::ref sle) override; // RawView void - rawErase(std::shared_ptr const& sle) override; + rawErase(SLE::ref sle) override; void - rawInsert(std::shared_ptr const& sle) override; + rawInsert(SLE::ref sle) override; void - rawReplace(std::shared_ptr const& sle) override; + rawReplace(SLE::ref sle) override; void rawDestroyXRP(XRPAmount const& feeDrops) override; diff --git a/include/xrpl/ledger/detail/RawStateTable.h b/include/xrpl/ledger/detail/RawStateTable.h index e4329bf6fc..d2567e34f1 100644 --- a/include/xrpl/ledger/detail/RawStateTable.h +++ b/include/xrpl/ledger/detail/RawStateTable.h @@ -49,15 +49,15 @@ public: succ(ReadView const& base, key_type const& key, std::optional const& last) const; void - erase(std::shared_ptr const& sle); + erase(SLE::ref sle); void - insert(std::shared_ptr const& sle); + insert(SLE::ref sle); void - replace(std::shared_ptr const& sle); + replace(SLE::ref sle); - [[nodiscard]] std::shared_ptr + [[nodiscard]] SLE::const_pointer read(ReadView const& base, Keylet const& k) const; void @@ -84,10 +84,10 @@ private: struct SleAction { Action action; - std::shared_ptr sle; + SLE::pointer sle; // Constructor needed for emplacement in std::map - SleAction(Action action, std::shared_ptr const& sle) : action(action), sle(sle) + SleAction(Action action, SLE::pointer sle) : action(action), sle(std::move(sle)) { } }; diff --git a/include/xrpl/ledger/helpers/AMMHelpers.h b/include/xrpl/ledger/helpers/AMMHelpers.h index a146ef753b..61d6e9d2fb 100644 --- a/include/xrpl/ledger/helpers/AMMHelpers.h +++ b/include/xrpl/ledger/helpers/AMMHelpers.h @@ -792,7 +792,7 @@ deleteAMMAccount(Sandbox& view, Asset const& asset, Asset const& asset2, beast:: void initializeFeeAuctionVote( ApplyView& view, - std::shared_ptr& ammSle, + SLE::pointer& ammSle, AccountID const& account, Asset const& lptAsset, std::uint16_t tfee); @@ -812,7 +812,7 @@ Expected verifyAndAdjustLPTokenBalance( Sandbox& sb, STAmount const& lpTokens, - std::shared_ptr& ammSle, + SLE::pointer& ammSle, AccountID const& account); } // namespace xrpl diff --git a/include/xrpl/ledger/helpers/AccountRootHelpers.h b/include/xrpl/ledger/helpers/AccountRootHelpers.h index 353c27fe41..c02cad98d8 100644 --- a/include/xrpl/ledger/helpers/AccountRootHelpers.h +++ b/include/xrpl/ledger/helpers/AccountRootHelpers.h @@ -9,7 +9,6 @@ #include #include -#include #include #include @@ -36,11 +35,7 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, /** Adjust the owner count up or down. */ void -adjustOwnerCount( - ApplyView& view, - std::shared_ptr const& sle, - std::int32_t amount, - beast::Journal j); +adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Journal j); /** Returns IOU issuer transfer fee as Rate. Rate specifies * the fee as fractions of 1 billion. For example, 1% transfer rate @@ -76,9 +71,7 @@ getPseudoAccountFields(); - null pointer */ [[nodiscard]] bool -isPseudoAccount( - std::shared_ptr sleAcct, - std::set const& pseudoFieldFilter = {}); +isPseudoAccount(SLE::const_pointer sleAcct, std::set const& pseudoFieldFilter = {}); /** Convenience overload that reads the account from the view. */ [[nodiscard]] inline bool @@ -98,7 +91,7 @@ isPseudoAccount( * before using a field. The amendment check is **not** performed in * createPseudoAccount. */ -[[nodiscard]] Expected, TER> +[[nodiscard]] Expected createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField); /** Checks the destination and tag. diff --git a/include/xrpl/ledger/helpers/CredentialHelpers.h b/include/xrpl/ledger/helpers/CredentialHelpers.h index e06d225934..549644764f 100644 --- a/include/xrpl/ledger/helpers/CredentialHelpers.h +++ b/include/xrpl/ledger/helpers/CredentialHelpers.h @@ -23,7 +23,7 @@ checkExpired(SLE const& sleCredential, NetClock::time_point const& closed); // Actually remove a credentials object from the ledger [[nodiscard]] TER -deleteSLE(ApplyView& view, std::shared_ptr const& sleCredential, beast::Journal j); +deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j); // Amendment and parameters checks for sfCredentialIDs field NotTEC @@ -70,7 +70,7 @@ verifyDepositPreauth( ApplyView& view, AccountID const& src, AccountID const& dst, - std::shared_ptr const& sleDst, + SLE::const_ref sleDst, beast::Journal j); } // namespace xrpl diff --git a/include/xrpl/ledger/helpers/DelegateHelpers.h b/include/xrpl/ledger/helpers/DelegateHelpers.h index 78ccc46d0b..9cdad7173d 100644 --- a/include/xrpl/ledger/helpers/DelegateHelpers.h +++ b/include/xrpl/ledger/helpers/DelegateHelpers.h @@ -15,7 +15,7 @@ namespace xrpl { * if not. */ NotTEC -checkTxPermission(std::shared_ptr const& delegate, STTx const& tx); +checkTxPermission(SLE::const_ref delegate, STTx const& tx); /** * Load the granular permissions granted to the delegate account for the @@ -28,7 +28,7 @@ checkTxPermission(std::shared_ptr const& delegate, STTx const& tx); */ void loadGranularPermission( - std::shared_ptr const& delegate, + SLE::const_ref delegate, TxType const& type, std::unordered_set& granularPermissions); diff --git a/include/xrpl/ledger/helpers/DirectoryHelpers.h b/include/xrpl/ledger/helpers/DirectoryHelpers.h index 2ae188182d..a0be52df99 100644 --- a/include/xrpl/ledger/helpers/DirectoryHelpers.h +++ b/include/xrpl/ledger/helpers/DirectoryHelpers.h @@ -115,7 +115,7 @@ bool cdirFirst( ReadView const& view, uint256 const& root, - std::shared_ptr& page, + SLE::const_pointer& page, unsigned int& index, uint256& entry); @@ -123,7 +123,7 @@ bool dirFirst( ApplyView& view, uint256 const& root, - std::shared_ptr& page, + SLE::pointer& page, unsigned int& index, uint256& entry); /** @} */ @@ -147,7 +147,7 @@ bool cdirNext( ReadView const& view, uint256 const& root, - std::shared_ptr& page, + SLE::const_pointer& page, unsigned int& index, uint256& entry); @@ -155,17 +155,14 @@ bool dirNext( ApplyView& view, uint256 const& root, - std::shared_ptr& page, + SLE::pointer& page, unsigned int& index, uint256& entry); /** @} */ /** Iterate all items in the given directory. */ void -forEachItem( - ReadView const& view, - Keylet const& root, - std::function const&)> const& f); +forEachItem(ReadView const& view, Keylet const& root, std::function const& f); /** Iterate all items after an item in the given directory. @param after The key of the item to start after @@ -180,14 +177,11 @@ forEachItemAfter( uint256 const& after, std::uint64_t const hint, unsigned int limit, - std::function const&)> const& f); + std::function const& f); /** Iterate all items in an account's owner directory. */ inline void -forEachItem( - ReadView const& view, - AccountID const& id, - std::function const&)> const& f) +forEachItem(ReadView const& view, AccountID const& id, std::function const& f) { forEachItem(view, keylet::ownerDir(id), f); } @@ -205,7 +199,7 @@ forEachItemAfter( uint256 const& after, std::uint64_t const hint, unsigned int limit, - std::function const&)> const& f) + std::function const& f) { return forEachItemAfter(view, keylet::ownerDir(id), after, hint, limit, f); } diff --git a/include/xrpl/ledger/helpers/EscrowHelpers.h b/include/xrpl/ledger/helpers/EscrowHelpers.h index 859981cf05..bdb83230eb 100644 --- a/include/xrpl/ledger/helpers/EscrowHelpers.h +++ b/include/xrpl/ledger/helpers/EscrowHelpers.h @@ -18,7 +18,7 @@ TER escrowUnlockApplyHelper( ApplyView& view, Rate lockedRate, - std::shared_ptr const& sleDest, + SLE::ref sleDest, STAmount const& xrpBalance, STAmount const& amount, AccountID const& issuer, @@ -32,7 +32,7 @@ inline TER escrowUnlockApplyHelper( ApplyView& view, Rate lockedRate, - std::shared_ptr const& sleDest, + SLE::ref sleDest, STAmount const& xrpBalance, STAmount const& amount, AccountID const& issuer, @@ -162,7 +162,7 @@ inline TER escrowUnlockApplyHelper( ApplyView& view, Rate lockedRate, - std::shared_ptr const& sleDest, + SLE::ref sleDest, STAmount const& xrpBalance, STAmount const& amount, AccountID const& issuer, diff --git a/include/xrpl/ledger/helpers/NFTokenHelpers.h b/include/xrpl/ledger/helpers/NFTokenHelpers.h index 4294e1ca13..362cfe5a8c 100644 --- a/include/xrpl/ledger/helpers/NFTokenHelpers.h +++ b/include/xrpl/ledger/helpers/NFTokenHelpers.h @@ -28,10 +28,9 @@ findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID struct TokenAndPage { STObject token; - std::shared_ptr page; + SLE::pointer page; - TokenAndPage(STObject token, std::shared_ptr page) - : token(std::move(token)), page(std::move(page)) + TokenAndPage(STObject token, SLE::pointer page) : token(std::move(token)), page(std::move(page)) { } }; @@ -47,11 +46,7 @@ TER removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID); TER -removeToken( - ApplyView& view, - AccountID const& owner, - uint256 const& nftokenID, - std::shared_ptr const& page); +removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, SLE::ref page); /** Deletes the given token offer. @@ -63,7 +58,7 @@ removeToken( The offer also consumes one incremental reserve. */ bool -deleteTokenOffer(ApplyView& view, std::shared_ptr const& offer); +deleteTokenOffer(ApplyView& view, SLE::ref offer); /** Repairs the links in an NFTokenPage directory. diff --git a/include/xrpl/ledger/helpers/OfferHelpers.h b/include/xrpl/ledger/helpers/OfferHelpers.h index 9096071811..fc863dff0a 100644 --- a/include/xrpl/ledger/helpers/OfferHelpers.h +++ b/include/xrpl/ledger/helpers/OfferHelpers.h @@ -5,8 +5,6 @@ #include #include -#include - namespace xrpl { /** Delete an offer. @@ -23,6 +21,6 @@ namespace xrpl { */ // [[nodiscard]] // nodiscard commented out so Flow, BookTip and others compile. TER -offerDelete(ApplyView& view, std::shared_ptr const& sle, beast::Journal j); +offerDelete(ApplyView& view, SLE::ref sle, beast::Journal j); } // namespace xrpl diff --git a/include/xrpl/ledger/helpers/PaymentChannelHelpers.h b/include/xrpl/ledger/helpers/PaymentChannelHelpers.h index 24838f1331..810907b0af 100644 --- a/include/xrpl/ledger/helpers/PaymentChannelHelpers.h +++ b/include/xrpl/ledger/helpers/PaymentChannelHelpers.h @@ -8,10 +8,6 @@ namespace xrpl { TER -closeChannel( - std::shared_ptr const& slep, - ApplyView& view, - uint256 const& key, - beast::Journal j); +closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal j); } // namespace xrpl diff --git a/include/xrpl/ledger/helpers/RippleStateHelpers.h b/include/xrpl/ledger/helpers/RippleStateHelpers.h index 2616a6d5c9..3aaaa541fd 100644 --- a/include/xrpl/ledger/helpers/RippleStateHelpers.h +++ b/include/xrpl/ledger/helpers/RippleStateHelpers.h @@ -154,7 +154,7 @@ trustCreate( [[nodiscard]] TER trustDelete( ApplyView& view, - std::shared_ptr const& sleRippleState, + SLE::ref sleRippleState, AccountID const& uLowAccountID, AccountID const& uHighAccountID, beast::Journal j); @@ -248,7 +248,7 @@ removeEmptyHolding( [[nodiscard]] TER deleteAMMTrustLine( ApplyView& view, - std::shared_ptr sleState, + SLE::pointer sleState, std::optional const& ammAccountID, beast::Journal j); @@ -258,7 +258,7 @@ deleteAMMTrustLine( [[nodiscard]] TER deleteAMMMPToken( ApplyView& view, - std::shared_ptr sleMPT, + SLE::pointer sleMPT, AccountID const& ammAccountID, beast::Journal j); diff --git a/include/xrpl/ledger/helpers/VaultHelpers.h b/include/xrpl/ledger/helpers/VaultHelpers.h index 29270d913f..2344b4de77 100644 --- a/include/xrpl/ledger/helpers/VaultHelpers.h +++ b/include/xrpl/ledger/helpers/VaultHelpers.h @@ -5,7 +5,6 @@ #include #include -#include #include namespace xrpl { @@ -21,10 +20,7 @@ namespace xrpl { @return The number of shares, or nullopt on error. */ [[nodiscard]] std::optional -assetsToSharesDeposit( - std::shared_ptr const& vault, - std::shared_ptr const& issuance, - STAmount const& assets); +assetsToSharesDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount const& assets); /** From the perspective of a vault, return the number of assets to take from depositor when they receive a fixed amount of shares. Note, since shares are @@ -37,10 +33,7 @@ assetsToSharesDeposit( @return The number of assets, or nullopt on error. */ [[nodiscard]] std::optional -sharesToAssetsDeposit( - std::shared_ptr const& vault, - std::shared_ptr const& issuance, - STAmount const& shares); +sharesToAssetsDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount const& shares); /** Controls whether to truncate shares instead of rounding. */ enum class TruncateShares : bool { No = false, Yes = true }; @@ -69,8 +62,8 @@ enum class WaiveUnrealizedLoss : bool { No = false, Yes = true }; */ [[nodiscard]] std::optional assetsToSharesWithdraw( - std::shared_ptr const& vault, - std::shared_ptr const& issuance, + SLE::const_ref vault, + SLE::const_ref issuance, STAmount const& assets, TruncateShares truncate = TruncateShares::No, WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No); @@ -89,8 +82,8 @@ assetsToSharesWithdraw( */ [[nodiscard]] std::optional sharesToAssetsWithdraw( - std::shared_ptr const& vault, - std::shared_ptr const& issuance, + SLE::const_ref vault, + SLE::const_ref issuance, STAmount const& shares, WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No); @@ -104,9 +97,6 @@ sharesToAssetsWithdraw( both the share MPTID and the outstanding-amount total. */ [[nodiscard]] bool -isSoleShareholder( - ReadView const& view, - AccountID const& account, - std::shared_ptr const& issuance); +isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref issuance); } // namespace xrpl diff --git a/include/xrpl/protocol_autogen/LedgerEntryBase.h b/include/xrpl/protocol_autogen/LedgerEntryBase.h index ad513992c7..5758adbb24 100644 --- a/include/xrpl/protocol_autogen/LedgerEntryBase.h +++ b/include/xrpl/protocol_autogen/LedgerEntryBase.h @@ -27,7 +27,7 @@ public: * @brief Construct a ledger entry wrapper from an existing SLE object. * @param sle The underlying serialized ledger entry to wrap */ - explicit LedgerEntryBase(std::shared_ptr sle) : sle_(std::move(sle)) + explicit LedgerEntryBase(SLE::const_pointer sle) : sle_(std::move(sle)) { } @@ -151,7 +151,7 @@ public: * @return A constant reference to the underlying SLE object */ [[nodiscard]] - std::shared_ptr + SLE::const_pointer getSle() const { return sle_; @@ -159,7 +159,7 @@ public: protected: /** @brief The underlying serialized ledger entry being wrapped. */ - std::shared_ptr sle_; + SLE::const_pointer sle_; }; } // namespace xrpl::ledger_entries diff --git a/include/xrpl/protocol_autogen/ledger_entries/AMM.h b/include/xrpl/protocol_autogen/ledger_entries/AMM.h index 529e3f4df7..11fd3738c9 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/AMM.h +++ b/include/xrpl/protocol_autogen/ledger_entries/AMM.h @@ -33,7 +33,7 @@ public: * @brief Construct a AMM ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit AMM(std::shared_ptr sle) + explicit AMM(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -256,7 +256,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - AMMBuilder(std::shared_ptr sle) + AMMBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltAMM) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h b/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h index e20259330d..f9a12a027f 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h +++ b/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h @@ -33,7 +33,7 @@ public: * @brief Construct a AccountRoot ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit AccountRoot(std::shared_ptr sle) + explicit AccountRoot(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -555,7 +555,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - AccountRootBuilder(std::shared_ptr sle) + AccountRootBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltACCOUNT_ROOT) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/Amendments.h b/include/xrpl/protocol_autogen/ledger_entries/Amendments.h index 4f1b316b99..6a801308ca 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Amendments.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Amendments.h @@ -33,7 +33,7 @@ public: * @brief Construct a Amendments ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit Amendments(std::shared_ptr sle) + explicit Amendments(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -166,7 +166,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - AmendmentsBuilder(std::shared_ptr sle) + AmendmentsBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltAMENDMENTS) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/Bridge.h b/include/xrpl/protocol_autogen/ledger_entries/Bridge.h index fd7df1977f..2c7479b243 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Bridge.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Bridge.h @@ -33,7 +33,7 @@ public: * @brief Construct a Bridge ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit Bridge(std::shared_ptr sle) + explicit Bridge(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -210,7 +210,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - BridgeBuilder(std::shared_ptr sle) + BridgeBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltBRIDGE) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/Check.h b/include/xrpl/protocol_autogen/ledger_entries/Check.h index 750270cad9..5b3fd10b92 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Check.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Check.h @@ -33,7 +33,7 @@ public: * @brief Construct a Check ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit Check(std::shared_ptr sle) + explicit Check(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -269,7 +269,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - CheckBuilder(std::shared_ptr sle) + CheckBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltCHECK) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/Credential.h b/include/xrpl/protocol_autogen/ledger_entries/Credential.h index 5433f00b33..dfce76e45c 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Credential.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Credential.h @@ -33,7 +33,7 @@ public: * @brief Construct a Credential ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit Credential(std::shared_ptr sle) + explicit Credential(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -219,7 +219,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - CredentialBuilder(std::shared_ptr sle) + CredentialBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltCREDENTIAL) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/DID.h b/include/xrpl/protocol_autogen/ledger_entries/DID.h index 2f57961be4..ad423377e7 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/DID.h +++ b/include/xrpl/protocol_autogen/ledger_entries/DID.h @@ -33,7 +33,7 @@ public: * @brief Construct a DID ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit DID(std::shared_ptr sle) + explicit DID(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -193,7 +193,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - DIDBuilder(std::shared_ptr sle) + DIDBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltDID) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/Delegate.h b/include/xrpl/protocol_autogen/ledger_entries/Delegate.h index 7458b8103a..bfe5f5587a 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Delegate.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Delegate.h @@ -33,7 +33,7 @@ public: * @brief Construct a Delegate ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit Delegate(std::shared_ptr sle) + explicit Delegate(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -172,7 +172,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - DelegateBuilder(std::shared_ptr sle) + DelegateBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltDELEGATE) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/DepositPreauth.h b/include/xrpl/protocol_autogen/ledger_entries/DepositPreauth.h index 42b430a665..069bed6b77 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/DepositPreauth.h +++ b/include/xrpl/protocol_autogen/ledger_entries/DepositPreauth.h @@ -33,7 +33,7 @@ public: * @brief Construct a DepositPreauth ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit DepositPreauth(std::shared_ptr sle) + explicit DepositPreauth(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -170,7 +170,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - DepositPreauthBuilder(std::shared_ptr sle) + DepositPreauthBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltDEPOSIT_PREAUTH) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h b/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h index e47cb98a26..50659c33f6 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h +++ b/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h @@ -33,7 +33,7 @@ public: * @brief Construct a DirectoryNode ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit DirectoryNode(std::shared_ptr sle) + explicit DirectoryNode(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -431,7 +431,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - DirectoryNodeBuilder(std::shared_ptr sle) + DirectoryNodeBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltDIR_NODE) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/Escrow.h b/include/xrpl/protocol_autogen/ledger_entries/Escrow.h index ae8219a4f0..f3c033d26d 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Escrow.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Escrow.h @@ -33,7 +33,7 @@ public: * @brief Construct a Escrow ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit Escrow(std::shared_ptr sle) + explicit Escrow(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -363,7 +363,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - EscrowBuilder(std::shared_ptr sle) + EscrowBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltESCROW) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h b/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h index cfd7a591e8..8f43d3b782 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h +++ b/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h @@ -33,7 +33,7 @@ public: * @brief Construct a FeeSettings ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit FeeSettings(std::shared_ptr sle) + explicit FeeSettings(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -285,7 +285,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - FeeSettingsBuilder(std::shared_ptr sle) + FeeSettingsBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltFEE_SETTINGS) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/LedgerHashes.h b/include/xrpl/protocol_autogen/ledger_entries/LedgerHashes.h index 8c082be685..f1d3684b55 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/LedgerHashes.h +++ b/include/xrpl/protocol_autogen/ledger_entries/LedgerHashes.h @@ -33,7 +33,7 @@ public: * @brief Construct a LedgerHashes ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit LedgerHashes(std::shared_ptr sle) + explicit LedgerHashes(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -130,7 +130,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - LedgerHashesBuilder(std::shared_ptr sle) + LedgerHashesBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltLEDGER_HASHES) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/Loan.h b/include/xrpl/protocol_autogen/ledger_entries/Loan.h index 4408c278db..5d837736ec 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Loan.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Loan.h @@ -33,7 +33,7 @@ public: * @brief Construct a Loan ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit Loan(std::shared_ptr sle) + explicit Loan(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -607,7 +607,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - LoanBuilder(std::shared_ptr sle) + LoanBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltLOAN) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/LoanBroker.h b/include/xrpl/protocol_autogen/ledger_entries/LoanBroker.h index b1c6dd8a54..88f05e3433 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/LoanBroker.h +++ b/include/xrpl/protocol_autogen/ledger_entries/LoanBroker.h @@ -33,7 +33,7 @@ public: * @brief Construct a LoanBroker ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit LoanBroker(std::shared_ptr sle) + explicit LoanBroker(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -378,7 +378,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - LoanBrokerBuilder(std::shared_ptr sle) + LoanBrokerBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltLOAN_BROKER) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/MPToken.h b/include/xrpl/protocol_autogen/ledger_entries/MPToken.h index 8176b74fe9..0d394020f7 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/MPToken.h +++ b/include/xrpl/protocol_autogen/ledger_entries/MPToken.h @@ -33,7 +33,7 @@ public: * @brief Construct a MPToken ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit MPToken(std::shared_ptr sle) + explicit MPToken(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -182,7 +182,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - MPTokenBuilder(std::shared_ptr sle) + MPTokenBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltMPTOKEN) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h b/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h index 7f772b1c74..d493ac779c 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h +++ b/include/xrpl/protocol_autogen/ledger_entries/MPTokenIssuance.h @@ -33,7 +33,7 @@ public: * @brief Construct a MPTokenIssuance ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit MPTokenIssuance(std::shared_ptr sle) + explicit MPTokenIssuance(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -339,7 +339,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - MPTokenIssuanceBuilder(std::shared_ptr sle) + MPTokenIssuanceBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltMPTOKEN_ISSUANCE) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/NFTokenOffer.h b/include/xrpl/protocol_autogen/ledger_entries/NFTokenOffer.h index c0a6ee6cbc..072d3721f9 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/NFTokenOffer.h +++ b/include/xrpl/protocol_autogen/ledger_entries/NFTokenOffer.h @@ -33,7 +33,7 @@ public: * @brief Construct a NFTokenOffer ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit NFTokenOffer(std::shared_ptr sle) + explicit NFTokenOffer(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -208,7 +208,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - NFTokenOfferBuilder(std::shared_ptr sle) + NFTokenOfferBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltNFTOKEN_OFFER) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/NFTokenPage.h b/include/xrpl/protocol_autogen/ledger_entries/NFTokenPage.h index c4190f9068..5e00cb1120 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/NFTokenPage.h +++ b/include/xrpl/protocol_autogen/ledger_entries/NFTokenPage.h @@ -33,7 +33,7 @@ public: * @brief Construct a NFTokenPage ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit NFTokenPage(std::shared_ptr sle) + explicit NFTokenPage(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -157,7 +157,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - NFTokenPageBuilder(std::shared_ptr sle) + NFTokenPageBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltNFTOKEN_PAGE) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/NegativeUNL.h b/include/xrpl/protocol_autogen/ledger_entries/NegativeUNL.h index b09135e5f8..7ca9729082 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/NegativeUNL.h +++ b/include/xrpl/protocol_autogen/ledger_entries/NegativeUNL.h @@ -33,7 +33,7 @@ public: * @brief Construct a NegativeUNL ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit NegativeUNL(std::shared_ptr sle) + explicit NegativeUNL(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -190,7 +190,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - NegativeUNLBuilder(std::shared_ptr sle) + NegativeUNLBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltNEGATIVE_UNL) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/Offer.h b/include/xrpl/protocol_autogen/ledger_entries/Offer.h index a6d427cbbd..f51b54cfd2 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Offer.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Offer.h @@ -33,7 +33,7 @@ public: * @brief Construct a Offer ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit Offer(std::shared_ptr sle) + explicit Offer(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -259,7 +259,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - OfferBuilder(std::shared_ptr sle) + OfferBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltOFFER) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/Oracle.h b/include/xrpl/protocol_autogen/ledger_entries/Oracle.h index 31735a713c..902032f94f 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Oracle.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Oracle.h @@ -33,7 +33,7 @@ public: * @brief Construct a Oracle ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit Oracle(std::shared_ptr sle) + explicit Oracle(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -222,7 +222,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - OracleBuilder(std::shared_ptr sle) + OracleBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltORACLE) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/PayChannel.h b/include/xrpl/protocol_autogen/ledger_entries/PayChannel.h index 097f287425..61a4e2d044 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/PayChannel.h +++ b/include/xrpl/protocol_autogen/ledger_entries/PayChannel.h @@ -33,7 +33,7 @@ public: * @brief Construct a PayChannel ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit PayChannel(std::shared_ptr sle) + explicit PayChannel(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -330,7 +330,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - PayChannelBuilder(std::shared_ptr sle) + PayChannelBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltPAYCHAN) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/PermissionedDomain.h b/include/xrpl/protocol_autogen/ledger_entries/PermissionedDomain.h index cc5a1649ef..638dda2420 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/PermissionedDomain.h +++ b/include/xrpl/protocol_autogen/ledger_entries/PermissionedDomain.h @@ -33,7 +33,7 @@ public: * @brief Construct a PermissionedDomain ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit PermissionedDomain(std::shared_ptr sle) + explicit PermissionedDomain(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -148,7 +148,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - PermissionedDomainBuilder(std::shared_ptr sle) + PermissionedDomainBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltPERMISSIONED_DOMAIN) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/RippleState.h b/include/xrpl/protocol_autogen/ledger_entries/RippleState.h index cb221bb2ad..e8debfe792 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/RippleState.h +++ b/include/xrpl/protocol_autogen/ledger_entries/RippleState.h @@ -33,7 +33,7 @@ public: * @brief Construct a RippleState ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit RippleState(std::shared_ptr sle) + explicit RippleState(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -278,7 +278,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - RippleStateBuilder(std::shared_ptr sle) + RippleStateBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltRIPPLE_STATE) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/SignerList.h b/include/xrpl/protocol_autogen/ledger_entries/SignerList.h index dfc7eb4506..443e5588f9 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/SignerList.h +++ b/include/xrpl/protocol_autogen/ledger_entries/SignerList.h @@ -33,7 +33,7 @@ public: * @brief Construct a SignerList ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit SignerList(std::shared_ptr sle) + explicit SignerList(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -172,7 +172,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - SignerListBuilder(std::shared_ptr sle) + SignerListBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltSIGNER_LIST) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/Ticket.h b/include/xrpl/protocol_autogen/ledger_entries/Ticket.h index ceadd70765..6fa5b57f6c 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Ticket.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Ticket.h @@ -33,7 +33,7 @@ public: * @brief Construct a Ticket ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit Ticket(std::shared_ptr sle) + explicit Ticket(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -134,7 +134,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - TicketBuilder(std::shared_ptr sle) + TicketBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltTICKET) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/Vault.h b/include/xrpl/protocol_autogen/ledger_entries/Vault.h index 168894d8d2..d1aaeb4ed8 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Vault.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Vault.h @@ -33,7 +33,7 @@ public: * @brief Construct a Vault ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit Vault(std::shared_ptr sle) + explicit Vault(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -330,7 +330,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - VaultBuilder(std::shared_ptr sle) + VaultBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltVAULT) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedClaimID.h b/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedClaimID.h index 4a3e9c9103..3f8058a4a1 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedClaimID.h +++ b/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedClaimID.h @@ -33,7 +33,7 @@ public: * @brief Construct a XChainOwnedClaimID ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit XChainOwnedClaimID(std::shared_ptr sle) + explicit XChainOwnedClaimID(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -187,7 +187,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - XChainOwnedClaimIDBuilder(std::shared_ptr sle) + XChainOwnedClaimIDBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltXCHAIN_OWNED_CLAIM_ID) { diff --git a/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedCreateAccountClaimID.h b/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedCreateAccountClaimID.h index 542de104ea..e24009a4b7 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedCreateAccountClaimID.h +++ b/include/xrpl/protocol_autogen/ledger_entries/XChainOwnedCreateAccountClaimID.h @@ -33,7 +33,7 @@ public: * @brief Construct a XChainOwnedCreateAccountClaimID ledger entry wrapper from an existing SLE object. * @throws std::runtime_error if the ledger entry type doesn't match. */ - explicit XChainOwnedCreateAccountClaimID(std::shared_ptr sle) + explicit XChainOwnedCreateAccountClaimID(SLE::const_pointer sle) : LedgerEntryBase(std::move(sle)) { // Verify ledger entry type @@ -161,7 +161,7 @@ public: * @param sle The existing ledger entry to copy from. * @throws std::runtime_error if the ledger entry type doesn't match. */ - XChainOwnedCreateAccountClaimIDBuilder(std::shared_ptr sle) + XChainOwnedCreateAccountClaimIDBuilder(SLE::const_pointer sle) { if (sle->at(sfLedgerEntryType) != ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID) { diff --git a/include/xrpl/tx/ApplyContext.h b/include/xrpl/tx/ApplyContext.h index 910ec6be42..8540037601 100644 --- a/include/xrpl/tx/ApplyContext.h +++ b/include/xrpl/tx/ApplyContext.h @@ -93,8 +93,8 @@ public: std::function const& before, - std::shared_ptr const& after)> const& func); + SLE::const_ref before, + SLE::const_ref after)> const& func); void destroyXRP(XRPAmount const& fee) diff --git a/include/xrpl/tx/Transactor.h b/include/xrpl/tx/Transactor.h index 1440a5097f..86b1e856b3 100644 --- a/include/xrpl/tx/Transactor.h +++ b/include/xrpl/tx/Transactor.h @@ -263,10 +263,7 @@ protected: * to detect deletions. */ virtual void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) = 0; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) = 0; /** Check transaction-specific post-conditions after all entries have * been visited. @@ -368,7 +365,7 @@ private: ReadView const& view, AccountID const& idSigner, AccountID const& idAccount, - std::shared_ptr sleAccount, + SLE::const_pointer sleAccount, beast::Journal const j); static NotTEC checkMultiSign( diff --git a/include/xrpl/tx/invariants/AMMInvariant.h b/include/xrpl/tx/invariants/AMMInvariant.h index 43d9c5ad0a..ee2fb66a1c 100644 --- a/include/xrpl/tx/invariants/AMMInvariant.h +++ b/include/xrpl/tx/invariants/AMMInvariant.h @@ -22,7 +22,7 @@ public: ValidAMM() = default; void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); diff --git a/include/xrpl/tx/invariants/FreezeInvariant.h b/include/xrpl/tx/invariants/FreezeInvariant.h index 645f444462..a76eb66497 100644 --- a/include/xrpl/tx/invariants/FreezeInvariant.h +++ b/include/xrpl/tx/invariants/FreezeInvariant.h @@ -22,7 +22,7 @@ class TransfersNotFrozen { struct BalanceChange { - std::shared_ptr const line; + SLE::const_pointer const line; int const balanceChangeSign; }; @@ -35,37 +35,34 @@ class TransfersNotFrozen using ByIssuer = std::map; ByIssuer balanceChanges_; - std::map const> possibleIssuers_; + std::map possibleIssuers_; public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); private: bool - isValidEntry(std::shared_ptr const& before, std::shared_ptr const& after); + isValidEntry(SLE::const_ref before, SLE::const_ref after); static STAmount - calculateBalanceChange( - std::shared_ptr const& before, - std::shared_ptr const& after, - bool isDelete); + calculateBalanceChange(SLE::const_ref before, SLE::const_ref after, bool isDelete); void recordBalance(Issue const& issue, BalanceChange change); void - recordBalanceChanges(std::shared_ptr const& after, STAmount const& balanceChange); + recordBalanceChanges(SLE::const_ref after, STAmount const& balanceChange); - std::shared_ptr + SLE::const_pointer findIssuer(AccountID const& issuerID, ReadView const& view); static bool validateIssuerChanges( - std::shared_ptr const& issuer, + SLE::const_ref issuer, IssuerChanges const& changes, STTx const& tx, beast::Journal const& j, diff --git a/include/xrpl/tx/invariants/InvariantCheck.h b/include/xrpl/tx/invariants/InvariantCheck.h index d4c0154269..9378062726 100644 --- a/include/xrpl/tx/invariants/InvariantCheck.h +++ b/include/xrpl/tx/invariants/InvariantCheck.h @@ -70,10 +70,7 @@ public: * @param after ledger entry after modification by the transaction */ void - visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after); + visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after); /** * @brief called after all ledger entries have been visited to determine @@ -111,7 +108,7 @@ class TransactionFeeCheck { public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); static bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); @@ -131,7 +128,7 @@ class XRPNotCreated public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); [[nodiscard]] bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; @@ -151,7 +148,7 @@ class AccountRootsNotDeleted public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); [[nodiscard]] bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; @@ -174,11 +171,11 @@ class AccountRootsDeletedClean // deleted, it can still be found. After is used specifically for any checks // that are expected as part of the deletion, such as zeroing out the // balance. - std::vector, std::shared_ptr>> accountsDeleted_; + std::vector> accountsDeleted_; public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); @@ -197,7 +194,7 @@ class XRPBalanceChecks public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); [[nodiscard]] bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; @@ -214,7 +211,7 @@ class LedgerEntryTypesMatch public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); [[nodiscard]] bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; @@ -232,7 +229,7 @@ class NoXRPTrustLines public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); [[nodiscard]] bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; @@ -251,7 +248,7 @@ class NoDeepFreezeTrustLinesWithoutFreeze public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); [[nodiscard]] bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; @@ -270,7 +267,7 @@ class NoBadOffers public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); [[nodiscard]] bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; @@ -286,7 +283,7 @@ class NoZeroEscrow public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); [[nodiscard]] bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; @@ -306,7 +303,7 @@ class ValidNewAccountRoot public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); [[nodiscard]] bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; @@ -327,7 +324,7 @@ class ValidClawback public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); [[nodiscard]] bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; @@ -347,7 +344,7 @@ class ValidPseudoAccounts public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); @@ -367,7 +364,7 @@ class NoModifiedUnmodifiableFields public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); diff --git a/include/xrpl/tx/invariants/LoanBrokerInvariant.h b/include/xrpl/tx/invariants/LoanBrokerInvariant.h index e7d14a638b..684bbff423 100644 --- a/include/xrpl/tx/invariants/LoanBrokerInvariant.h +++ b/include/xrpl/tx/invariants/LoanBrokerInvariant.h @@ -46,7 +46,7 @@ class ValidLoanBroker public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); diff --git a/include/xrpl/tx/invariants/LoanInvariant.h b/include/xrpl/tx/invariants/LoanInvariant.h index bda9c51653..3f408d169a 100644 --- a/include/xrpl/tx/invariants/LoanInvariant.h +++ b/include/xrpl/tx/invariants/LoanInvariant.h @@ -23,7 +23,7 @@ class ValidLoan public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); diff --git a/include/xrpl/tx/invariants/MPTInvariant.h b/include/xrpl/tx/invariants/MPTInvariant.h index becb752126..b4b76a290f 100644 --- a/include/xrpl/tx/invariants/MPTInvariant.h +++ b/include/xrpl/tx/invariants/MPTInvariant.h @@ -37,7 +37,7 @@ class ValidMPTIssuance public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); [[nodiscard]] bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; @@ -65,7 +65,7 @@ class ValidMPTPayment public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); diff --git a/include/xrpl/tx/invariants/NFTInvariant.h b/include/xrpl/tx/invariants/NFTInvariant.h index fa056ecbb0..698df05247 100644 --- a/include/xrpl/tx/invariants/NFTInvariant.h +++ b/include/xrpl/tx/invariants/NFTInvariant.h @@ -33,7 +33,7 @@ class ValidNFTokenPage public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); [[nodiscard]] bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; @@ -61,7 +61,7 @@ class NFTokenCountTracking public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); [[nodiscard]] bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; diff --git a/include/xrpl/tx/invariants/PermissionedDEXInvariant.h b/include/xrpl/tx/invariants/PermissionedDEXInvariant.h index 654ed3e009..2ec22ded88 100644 --- a/include/xrpl/tx/invariants/PermissionedDEXInvariant.h +++ b/include/xrpl/tx/invariants/PermissionedDEXInvariant.h @@ -18,7 +18,7 @@ class ValidPermissionedDEX public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); diff --git a/include/xrpl/tx/invariants/PermissionedDomainInvariant.h b/include/xrpl/tx/invariants/PermissionedDomainInvariant.h index 2475ed8f6b..19edcc0b39 100644 --- a/include/xrpl/tx/invariants/PermissionedDomainInvariant.h +++ b/include/xrpl/tx/invariants/PermissionedDomainInvariant.h @@ -32,7 +32,7 @@ class ValidPermissionedDomain public: void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); diff --git a/include/xrpl/tx/invariants/VaultInvariant.h b/include/xrpl/tx/invariants/VaultInvariant.h index abc256c880..2a9ffc8282 100644 --- a/include/xrpl/tx/invariants/VaultInvariant.h +++ b/include/xrpl/tx/invariants/VaultInvariant.h @@ -154,7 +154,7 @@ public: computeCoarsestScale(std::vector const& numbers); void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + visitEntry(bool, SLE::const_ref, SLE::const_ref); bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); diff --git a/include/xrpl/tx/paths/BookTip.h b/include/xrpl/tx/paths/BookTip.h index e06a2da86c..c4bdb0415c 100644 --- a/include/xrpl/tx/paths/BookTip.h +++ b/include/xrpl/tx/paths/BookTip.h @@ -21,7 +21,7 @@ private: uint256 end_; uint256 dir_; uint256 index_; - std::shared_ptr entry_; + SLE::pointer entry_; Quality quality_{}; public: diff --git a/include/xrpl/tx/transactors/account/AccountDelete.h b/include/xrpl/tx/transactors/account/AccountDelete.h index d2cbfa5ad2..16661a4b7c 100644 --- a/include/xrpl/tx/transactors/account/AccountDelete.h +++ b/include/xrpl/tx/transactors/account/AccountDelete.h @@ -29,10 +29,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/account/AccountSet.h b/include/xrpl/tx/transactors/account/AccountSet.h index 002779db64..a40a9ec963 100644 --- a/include/xrpl/tx/transactors/account/AccountSet.h +++ b/include/xrpl/tx/transactors/account/AccountSet.h @@ -33,10 +33,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/account/SetRegularKey.h b/include/xrpl/tx/transactors/account/SetRegularKey.h index a9f1ce715d..6ff9c5aa52 100644 --- a/include/xrpl/tx/transactors/account/SetRegularKey.h +++ b/include/xrpl/tx/transactors/account/SetRegularKey.h @@ -23,10 +23,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/account/SignerListSet.h b/include/xrpl/tx/transactors/account/SignerListSet.h index 760f6e9358..46e3191323 100644 --- a/include/xrpl/tx/transactors/account/SignerListSet.h +++ b/include/xrpl/tx/transactors/account/SignerListSet.h @@ -42,10 +42,7 @@ public: preCompute() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/bridge/XChainBridge.h b/include/xrpl/tx/transactors/bridge/XChainBridge.h index 1033dee188..58a546de2f 100644 --- a/include/xrpl/tx/transactors/bridge/XChainBridge.h +++ b/include/xrpl/tx/transactors/bridge/XChainBridge.h @@ -28,10 +28,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( @@ -64,10 +61,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( @@ -111,10 +105,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( @@ -152,10 +143,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( @@ -195,10 +183,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( @@ -238,10 +223,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( @@ -272,10 +254,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( @@ -330,10 +309,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/check/CheckCancel.h b/include/xrpl/tx/transactors/check/CheckCancel.h index 5fdaa7e527..b8e8b6c52d 100644 --- a/include/xrpl/tx/transactors/check/CheckCancel.h +++ b/include/xrpl/tx/transactors/check/CheckCancel.h @@ -23,10 +23,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/check/CheckCash.h b/include/xrpl/tx/transactors/check/CheckCash.h index ad9de1e4c3..7d4e615cfd 100644 --- a/include/xrpl/tx/transactors/check/CheckCash.h +++ b/include/xrpl/tx/transactors/check/CheckCash.h @@ -26,10 +26,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/check/CheckCreate.h b/include/xrpl/tx/transactors/check/CheckCreate.h index e03677e5f5..178fe4707c 100644 --- a/include/xrpl/tx/transactors/check/CheckCreate.h +++ b/include/xrpl/tx/transactors/check/CheckCreate.h @@ -26,10 +26,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/credentials/CredentialAccept.h b/include/xrpl/tx/transactors/credentials/CredentialAccept.h index 97838f8a0a..8630ac3f7f 100644 --- a/include/xrpl/tx/transactors/credentials/CredentialAccept.h +++ b/include/xrpl/tx/transactors/credentials/CredentialAccept.h @@ -26,10 +26,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/credentials/CredentialCreate.h b/include/xrpl/tx/transactors/credentials/CredentialCreate.h index 7493aa4dc5..91b5e829d3 100644 --- a/include/xrpl/tx/transactors/credentials/CredentialCreate.h +++ b/include/xrpl/tx/transactors/credentials/CredentialCreate.h @@ -26,10 +26,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/credentials/CredentialDelete.h b/include/xrpl/tx/transactors/credentials/CredentialDelete.h index 4d9b4ddd18..70fe5cb3d0 100644 --- a/include/xrpl/tx/transactors/credentials/CredentialDelete.h +++ b/include/xrpl/tx/transactors/credentials/CredentialDelete.h @@ -26,10 +26,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/delegate/DelegateSet.h b/include/xrpl/tx/transactors/delegate/DelegateSet.h index c93c48e970..a55524ecff 100644 --- a/include/xrpl/tx/transactors/delegate/DelegateSet.h +++ b/include/xrpl/tx/transactors/delegate/DelegateSet.h @@ -23,10 +23,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( @@ -38,7 +35,7 @@ public: // Interface used by AccountDelete static TER - deleteDelegate(ApplyView& view, std::shared_ptr const& sle, beast::Journal j); + deleteDelegate(ApplyView& view, SLE::ref sle, beast::Journal j); }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/dex/AMMBid.h b/include/xrpl/tx/transactors/dex/AMMBid.h index 9328c48e79..dfa50d06ba 100644 --- a/include/xrpl/tx/transactors/dex/AMMBid.h +++ b/include/xrpl/tx/transactors/dex/AMMBid.h @@ -64,10 +64,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/dex/AMMClawback.h b/include/xrpl/tx/transactors/dex/AMMClawback.h index 7ac03ebb28..6f31480490 100644 --- a/include/xrpl/tx/transactors/dex/AMMClawback.h +++ b/include/xrpl/tx/transactors/dex/AMMClawback.h @@ -29,10 +29,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/dex/AMMCreate.h b/include/xrpl/tx/transactors/dex/AMMCreate.h index 04d6fe6f60..64d2a1e3b1 100644 --- a/include/xrpl/tx/transactors/dex/AMMCreate.h +++ b/include/xrpl/tx/transactors/dex/AMMCreate.h @@ -60,10 +60,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/dex/AMMDelete.h b/include/xrpl/tx/transactors/dex/AMMDelete.h index ff5776e3b7..d3e8cfeeb4 100644 --- a/include/xrpl/tx/transactors/dex/AMMDelete.h +++ b/include/xrpl/tx/transactors/dex/AMMDelete.h @@ -32,10 +32,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/dex/AMMDeposit.h b/include/xrpl/tx/transactors/dex/AMMDeposit.h index 9be53167f2..453046ddad 100644 --- a/include/xrpl/tx/transactors/dex/AMMDeposit.h +++ b/include/xrpl/tx/transactors/dex/AMMDeposit.h @@ -64,10 +64,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/dex/AMMVote.h b/include/xrpl/tx/transactors/dex/AMMVote.h index 1b5946aae1..8defc1369e 100644 --- a/include/xrpl/tx/transactors/dex/AMMVote.h +++ b/include/xrpl/tx/transactors/dex/AMMVote.h @@ -49,10 +49,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/dex/AMMWithdraw.h b/include/xrpl/tx/transactors/dex/AMMWithdraw.h index 9e6eb62d51..6e88320eae 100644 --- a/include/xrpl/tx/transactors/dex/AMMWithdraw.h +++ b/include/xrpl/tx/transactors/dex/AMMWithdraw.h @@ -72,10 +72,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( @@ -155,7 +152,7 @@ public: static std::pair deleteAMMAccountIfEmpty( Sandbox& sb, - std::shared_ptr const ammSle, + SLE::pointer const ammSle, STAmount const& lpTokenBalance, Asset const& asset1, Asset const& asset2, diff --git a/include/xrpl/tx/transactors/dex/OfferCancel.h b/include/xrpl/tx/transactors/dex/OfferCancel.h index b2641049e6..2806b6942f 100644 --- a/include/xrpl/tx/transactors/dex/OfferCancel.h +++ b/include/xrpl/tx/transactors/dex/OfferCancel.h @@ -24,10 +24,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/dex/OfferCreate.h b/include/xrpl/tx/transactors/dex/OfferCreate.h index efae5312e2..7faf613d3a 100644 --- a/include/xrpl/tx/transactors/dex/OfferCreate.h +++ b/include/xrpl/tx/transactors/dex/OfferCreate.h @@ -41,10 +41,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( @@ -81,7 +78,7 @@ private: TER applyHybrid( Sandbox& sb, - std::shared_ptr sleOffer, + STLedgerEntry::pointer sleOffer, Keylet const& offerIndex, STAmount const& saTakerPays, STAmount const& saTakerGets, diff --git a/include/xrpl/tx/transactors/did/DIDDelete.h b/include/xrpl/tx/transactors/did/DIDDelete.h index c750d4c95e..94615f51b6 100644 --- a/include/xrpl/tx/transactors/did/DIDDelete.h +++ b/include/xrpl/tx/transactors/did/DIDDelete.h @@ -20,16 +20,13 @@ public: deleteSLE(ApplyContext& ctx, Keylet sleKeylet, AccountID const owner); static TER - deleteSLE(ApplyView& view, std::shared_ptr sle, AccountID const owner, beast::Journal j); + deleteSLE(ApplyView& view, SLE::pointer sle, AccountID const owner, beast::Journal j); TER doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/did/DIDSet.h b/include/xrpl/tx/transactors/did/DIDSet.h index b2c3d97c81..8c84ea6c9b 100644 --- a/include/xrpl/tx/transactors/did/DIDSet.h +++ b/include/xrpl/tx/transactors/did/DIDSet.h @@ -20,10 +20,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/escrow/EscrowCancel.h b/include/xrpl/tx/transactors/escrow/EscrowCancel.h index 92b55374d5..af09f70202 100644 --- a/include/xrpl/tx/transactors/escrow/EscrowCancel.h +++ b/include/xrpl/tx/transactors/escrow/EscrowCancel.h @@ -23,10 +23,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/escrow/EscrowCreate.h b/include/xrpl/tx/transactors/escrow/EscrowCreate.h index 2e9da89896..8800e97b80 100644 --- a/include/xrpl/tx/transactors/escrow/EscrowCreate.h +++ b/include/xrpl/tx/transactors/escrow/EscrowCreate.h @@ -29,10 +29,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/escrow/EscrowFinish.h b/include/xrpl/tx/transactors/escrow/EscrowFinish.h index 806f947b8b..061fa0527c 100644 --- a/include/xrpl/tx/transactors/escrow/EscrowFinish.h +++ b/include/xrpl/tx/transactors/escrow/EscrowFinish.h @@ -32,10 +32,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/lending/LoanBrokerCoverClawback.h b/include/xrpl/tx/transactors/lending/LoanBrokerCoverClawback.h index 1b86ac41e7..81ea97ce7e 100644 --- a/include/xrpl/tx/transactors/lending/LoanBrokerCoverClawback.h +++ b/include/xrpl/tx/transactors/lending/LoanBrokerCoverClawback.h @@ -26,10 +26,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/lending/LoanBrokerCoverDeposit.h b/include/xrpl/tx/transactors/lending/LoanBrokerCoverDeposit.h index 63e96457dc..43b932726b 100644 --- a/include/xrpl/tx/transactors/lending/LoanBrokerCoverDeposit.h +++ b/include/xrpl/tx/transactors/lending/LoanBrokerCoverDeposit.h @@ -26,10 +26,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.h b/include/xrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.h index e3182c0851..a757ac51bf 100644 --- a/include/xrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.h +++ b/include/xrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.h @@ -26,10 +26,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/lending/LoanBrokerDelete.h b/include/xrpl/tx/transactors/lending/LoanBrokerDelete.h index 464a43e398..0ce5f29387 100644 --- a/include/xrpl/tx/transactors/lending/LoanBrokerDelete.h +++ b/include/xrpl/tx/transactors/lending/LoanBrokerDelete.h @@ -26,10 +26,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/lending/LoanBrokerSet.h b/include/xrpl/tx/transactors/lending/LoanBrokerSet.h index 72a339951b..75175e2dd6 100644 --- a/include/xrpl/tx/transactors/lending/LoanBrokerSet.h +++ b/include/xrpl/tx/transactors/lending/LoanBrokerSet.h @@ -29,10 +29,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/lending/LoanDelete.h b/include/xrpl/tx/transactors/lending/LoanDelete.h index 8dd9c4601b..9e8c3c172a 100644 --- a/include/xrpl/tx/transactors/lending/LoanDelete.h +++ b/include/xrpl/tx/transactors/lending/LoanDelete.h @@ -26,10 +26,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/lending/LoanManage.h b/include/xrpl/tx/transactors/lending/LoanManage.h index 08c873a5e7..d2344c0ec0 100644 --- a/include/xrpl/tx/transactors/lending/LoanManage.h +++ b/include/xrpl/tx/transactors/lending/LoanManage.h @@ -60,10 +60,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/lending/LoanPay.h b/include/xrpl/tx/transactors/lending/LoanPay.h index 561550e889..9be9695c64 100644 --- a/include/xrpl/tx/transactors/lending/LoanPay.h +++ b/include/xrpl/tx/transactors/lending/LoanPay.h @@ -32,10 +32,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/lending/LoanSet.h b/include/xrpl/tx/transactors/lending/LoanSet.h index 304c077f3d..d277629e44 100644 --- a/include/xrpl/tx/transactors/lending/LoanSet.h +++ b/include/xrpl/tx/transactors/lending/LoanSet.h @@ -39,10 +39,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/nft/NFTokenAcceptOffer.h b/include/xrpl/tx/transactors/nft/NFTokenAcceptOffer.h index 3c98d55141..c5cd10fa6a 100644 --- a/include/xrpl/tx/transactors/nft/NFTokenAcceptOffer.h +++ b/include/xrpl/tx/transactors/nft/NFTokenAcceptOffer.h @@ -11,10 +11,10 @@ private: pay(AccountID const& from, AccountID const& to, STAmount const& amount); TER - acceptOffer(std::shared_ptr const& offer); + acceptOffer(SLE::ref offer); TER - bridgeOffers(std::shared_ptr const& buy, std::shared_ptr const& sell); + bridgeOffers(SLE::ref buy, SLE::ref sell); TER transferNFToken(AccountID const& buyer, AccountID const& seller, uint256 const& nfTokenID); @@ -36,10 +36,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/nft/NFTokenBurn.h b/include/xrpl/tx/transactors/nft/NFTokenBurn.h index 4665a17aa7..849d09cb7e 100644 --- a/include/xrpl/tx/transactors/nft/NFTokenBurn.h +++ b/include/xrpl/tx/transactors/nft/NFTokenBurn.h @@ -23,10 +23,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/nft/NFTokenCancelOffer.h b/include/xrpl/tx/transactors/nft/NFTokenCancelOffer.h index 3eae44b389..a74a1c3e59 100644 --- a/include/xrpl/tx/transactors/nft/NFTokenCancelOffer.h +++ b/include/xrpl/tx/transactors/nft/NFTokenCancelOffer.h @@ -23,10 +23,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/nft/NFTokenCreateOffer.h b/include/xrpl/tx/transactors/nft/NFTokenCreateOffer.h index 20ccefc2cb..c874381dd0 100644 --- a/include/xrpl/tx/transactors/nft/NFTokenCreateOffer.h +++ b/include/xrpl/tx/transactors/nft/NFTokenCreateOffer.h @@ -26,10 +26,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/nft/NFTokenMint.h b/include/xrpl/tx/transactors/nft/NFTokenMint.h index 1a0deae29d..9267e8e801 100644 --- a/include/xrpl/tx/transactors/nft/NFTokenMint.h +++ b/include/xrpl/tx/transactors/nft/NFTokenMint.h @@ -31,10 +31,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/nft/NFTokenModify.h b/include/xrpl/tx/transactors/nft/NFTokenModify.h index 5b197d72b4..0d18e4a6d4 100644 --- a/include/xrpl/tx/transactors/nft/NFTokenModify.h +++ b/include/xrpl/tx/transactors/nft/NFTokenModify.h @@ -23,10 +23,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/oracle/OracleDelete.h b/include/xrpl/tx/transactors/oracle/OracleDelete.h index e83a334b87..c16d5fb2a9 100644 --- a/include/xrpl/tx/transactors/oracle/OracleDelete.h +++ b/include/xrpl/tx/transactors/oracle/OracleDelete.h @@ -32,10 +32,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( @@ -46,11 +43,7 @@ public: beast::Journal const& j) override; static TER - deleteOracle( - ApplyView& view, - std::shared_ptr const& sle, - AccountID const& account, - beast::Journal j); + deleteOracle(ApplyView& view, SLE::ref sle, AccountID const& account, beast::Journal j); }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/oracle/OracleSet.h b/include/xrpl/tx/transactors/oracle/OracleSet.h index 12c022470b..831c11b8c4 100644 --- a/include/xrpl/tx/transactors/oracle/OracleSet.h +++ b/include/xrpl/tx/transactors/oracle/OracleSet.h @@ -32,10 +32,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/payment/DepositPreauth.h b/include/xrpl/tx/transactors/payment/DepositPreauth.h index fbf22f8ca8..742b1ef3f7 100644 --- a/include/xrpl/tx/transactors/payment/DepositPreauth.h +++ b/include/xrpl/tx/transactors/payment/DepositPreauth.h @@ -26,10 +26,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/payment/Payment.h b/include/xrpl/tx/transactors/payment/Payment.h index ef42b67fa4..14897b4efe 100644 --- a/include/xrpl/tx/transactors/payment/Payment.h +++ b/include/xrpl/tx/transactors/payment/Payment.h @@ -41,10 +41,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/payment_channel/PaymentChannelClaim.h b/include/xrpl/tx/transactors/payment_channel/PaymentChannelClaim.h index 98d4638e51..e13fea6d6c 100644 --- a/include/xrpl/tx/transactors/payment_channel/PaymentChannelClaim.h +++ b/include/xrpl/tx/transactors/payment_channel/PaymentChannelClaim.h @@ -29,10 +29,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/payment_channel/PaymentChannelCreate.h b/include/xrpl/tx/transactors/payment_channel/PaymentChannelCreate.h index 73059a7e46..56e984cd57 100644 --- a/include/xrpl/tx/transactors/payment_channel/PaymentChannelCreate.h +++ b/include/xrpl/tx/transactors/payment_channel/PaymentChannelCreate.h @@ -26,10 +26,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/payment_channel/PaymentChannelFund.h b/include/xrpl/tx/transactors/payment_channel/PaymentChannelFund.h index 587ee9f778..272076ff20 100644 --- a/include/xrpl/tx/transactors/payment_channel/PaymentChannelFund.h +++ b/include/xrpl/tx/transactors/payment_channel/PaymentChannelFund.h @@ -23,10 +23,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h index 77834f7683..88883fb86f 100644 --- a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h +++ b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h @@ -24,10 +24,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h index 47c35800dd..4afa8cef5a 100644 --- a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h +++ b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h @@ -27,10 +27,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/system/Batch.h b/include/xrpl/tx/transactors/system/Batch.h index e190714725..43f0103319 100644 --- a/include/xrpl/tx/transactors/system/Batch.h +++ b/include/xrpl/tx/transactors/system/Batch.h @@ -34,10 +34,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/system/Change.h b/include/xrpl/tx/transactors/system/Change.h index 33df426593..339723ae8e 100644 --- a/include/xrpl/tx/transactors/system/Change.h +++ b/include/xrpl/tx/transactors/system/Change.h @@ -19,10 +19,7 @@ public: preCompute() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/system/LedgerStateFix.h b/include/xrpl/tx/transactors/system/LedgerStateFix.h index 6fbae6fc6a..973f89faa9 100644 --- a/include/xrpl/tx/transactors/system/LedgerStateFix.h +++ b/include/xrpl/tx/transactors/system/LedgerStateFix.h @@ -31,10 +31,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/system/TicketCreate.h b/include/xrpl/tx/transactors/system/TicketCreate.h index 4991d1d08b..5783faa6d1 100644 --- a/include/xrpl/tx/transactors/system/TicketCreate.h +++ b/include/xrpl/tx/transactors/system/TicketCreate.h @@ -61,10 +61,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/token/Clawback.h b/include/xrpl/tx/transactors/token/Clawback.h index 7c99cef0d2..ed90776e59 100644 --- a/include/xrpl/tx/transactors/token/Clawback.h +++ b/include/xrpl/tx/transactors/token/Clawback.h @@ -23,10 +23,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/token/MPTokenAuthorize.h b/include/xrpl/tx/transactors/token/MPTokenAuthorize.h index b2cfa74f6e..e30d123e52 100644 --- a/include/xrpl/tx/transactors/token/MPTokenAuthorize.h +++ b/include/xrpl/tx/transactors/token/MPTokenAuthorize.h @@ -35,10 +35,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h index 6c59d85548..a706c71e18 100644 --- a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h +++ b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h @@ -51,10 +51,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/token/MPTokenIssuanceDestroy.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceDestroy.h index 65682c8f5e..21032e7337 100644 --- a/include/xrpl/tx/transactors/token/MPTokenIssuanceDestroy.h +++ b/include/xrpl/tx/transactors/token/MPTokenIssuanceDestroy.h @@ -23,10 +23,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/token/MPTokenIssuanceSet.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceSet.h index 7397183bbf..6a6d1fc445 100644 --- a/include/xrpl/tx/transactors/token/MPTokenIssuanceSet.h +++ b/include/xrpl/tx/transactors/token/MPTokenIssuanceSet.h @@ -32,10 +32,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/token/TrustSet.h b/include/xrpl/tx/transactors/token/TrustSet.h index d439b676b5..dcf454bea1 100644 --- a/include/xrpl/tx/transactors/token/TrustSet.h +++ b/include/xrpl/tx/transactors/token/TrustSet.h @@ -30,10 +30,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/vault/VaultClawback.h b/include/xrpl/tx/transactors/vault/VaultClawback.h index 4b9e283571..b8032809ee 100644 --- a/include/xrpl/tx/transactors/vault/VaultClawback.h +++ b/include/xrpl/tx/transactors/vault/VaultClawback.h @@ -23,10 +23,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( @@ -39,8 +36,8 @@ public: private: Expected, TER> assetsToClawback( - std::shared_ptr const& vault, - std::shared_ptr const& sleShareIssuance, + SLE::ref vault, + SLE::const_ref sleShareIssuance, AccountID const& holder, STAmount const& clawbackAmount); }; diff --git a/include/xrpl/tx/transactors/vault/VaultCreate.h b/include/xrpl/tx/transactors/vault/VaultCreate.h index bbe80b49c1..9b11f97957 100644 --- a/include/xrpl/tx/transactors/vault/VaultCreate.h +++ b/include/xrpl/tx/transactors/vault/VaultCreate.h @@ -29,10 +29,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/vault/VaultDelete.h b/include/xrpl/tx/transactors/vault/VaultDelete.h index d8fccd6024..b8bb3c4096 100644 --- a/include/xrpl/tx/transactors/vault/VaultDelete.h +++ b/include/xrpl/tx/transactors/vault/VaultDelete.h @@ -23,10 +23,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/vault/VaultDeposit.h b/include/xrpl/tx/transactors/vault/VaultDeposit.h index f1a81c928e..523b3f2e53 100644 --- a/include/xrpl/tx/transactors/vault/VaultDeposit.h +++ b/include/xrpl/tx/transactors/vault/VaultDeposit.h @@ -23,10 +23,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/vault/VaultSet.h b/include/xrpl/tx/transactors/vault/VaultSet.h index 48178f5048..5c362d5db4 100644 --- a/include/xrpl/tx/transactors/vault/VaultSet.h +++ b/include/xrpl/tx/transactors/vault/VaultSet.h @@ -26,10 +26,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/include/xrpl/tx/transactors/vault/VaultWithdraw.h b/include/xrpl/tx/transactors/vault/VaultWithdraw.h index 7461752ff2..7bbe06187d 100644 --- a/include/xrpl/tx/transactors/vault/VaultWithdraw.h +++ b/include/xrpl/tx/transactors/vault/VaultWithdraw.h @@ -23,10 +23,7 @@ public: doApply() override; void - visitInvariantEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) override; + visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override; [[nodiscard]] bool finalizeInvariants( diff --git a/src/libxrpl/ledger/ApplyStateTable.cpp b/src/libxrpl/ledger/ApplyStateTable.cpp index 70fa0aef5d..abcbaac0aa 100644 --- a/src/libxrpl/ledger/ApplyStateTable.cpp +++ b/src/libxrpl/ledger/ApplyStateTable.cpp @@ -80,11 +80,9 @@ ApplyStateTable::size() const void ApplyStateTable::visit( ReadView const& to, - std::function const& before, - std::shared_ptr const& after)> const& func) const + std::function< + void(uint256 const& key, bool isDelete, SLE::const_ref before, SLE::const_ref after)> const& + func) const { for (auto& item : items_) { @@ -339,7 +337,7 @@ ApplyStateTable::succ( return next; } -std::shared_ptr +SLE::const_pointer ApplyStateTable::read(ReadView const& base, Keylet const& k) const { auto const iter = items_.find(k.key); @@ -361,7 +359,7 @@ ApplyStateTable::read(ReadView const& base, Keylet const& k) const return sle; } -std::shared_ptr +SLE::pointer ApplyStateTable::peek(ReadView const& base, Keylet const& k) { auto iter = items_.lower_bound(k.key); @@ -396,7 +394,7 @@ ApplyStateTable::peek(ReadView const& base, Keylet const& k) } void -ApplyStateTable::erase(ReadView const& base, std::shared_ptr const& sle) +ApplyStateTable::erase(ReadView const& base, SLE::ref sle) { auto const iter = items_.find(sle->key()); if (iter == items_.end()) @@ -420,7 +418,7 @@ ApplyStateTable::erase(ReadView const& base, std::shared_ptr const& sle) } void -ApplyStateTable::rawErase(ReadView const& base, std::shared_ptr const& sle) +ApplyStateTable::rawErase(ReadView const& base, SLE::ref sle) { using namespace std; auto const result = items_.emplace( @@ -445,7 +443,7 @@ ApplyStateTable::rawErase(ReadView const& base, std::shared_ptr const& sle) } void -ApplyStateTable::insert(ReadView const& base, std::shared_ptr const& sle) +ApplyStateTable::insert(ReadView const& base, SLE::ref sle) { auto const iter = items_.lower_bound(sle->key()); if (iter == items_.end() || iter->first != sle->key()) @@ -475,7 +473,7 @@ ApplyStateTable::insert(ReadView const& base, std::shared_ptr const& sle) } void -ApplyStateTable::replace(ReadView const& base, std::shared_ptr const& sle) +ApplyStateTable::replace(ReadView const& base, SLE::ref sle) { auto const iter = items_.lower_bound(sle->key()); if (iter == items_.end() || iter->first != sle->key()) @@ -504,7 +502,7 @@ ApplyStateTable::replace(ReadView const& base, std::shared_ptr const& sle) } void -ApplyStateTable::update(ReadView const& base, std::shared_ptr const& sle) +ApplyStateTable::update(ReadView const& base, SLE::ref sle) { auto const iter = items_.find(sle->key()); if (iter == items_.end()) @@ -536,7 +534,7 @@ ApplyStateTable::destroyXRP(XRPAmount const& fee) // Insert this transaction to the SLE's threading list void -ApplyStateTable::threadItem(TxMeta& meta, std::shared_ptr const& sle) +ApplyStateTable::threadItem(TxMeta& meta, SLE::ref sle) { key_type prevTxID; LedgerIndex prevLgrID = 0; @@ -568,7 +566,7 @@ ApplyStateTable::threadItem(TxMeta& meta, std::shared_ptr const& sle) } } -std::shared_ptr +SLE::pointer ApplyStateTable::getForMod(ReadView const& base, key_type const& key, Mods& mods, beast::Journal j) { { @@ -640,7 +638,7 @@ void ApplyStateTable::threadOwners( ReadView const& base, TxMeta& meta, - std::shared_ptr const& sle, + SLE::const_ref sle, Mods& mods, beast::Journal j) { diff --git a/src/libxrpl/ledger/ApplyView.cpp b/src/libxrpl/ledger/ApplyView.cpp index 2200afecac..8575bc2f52 100644 --- a/src/libxrpl/ledger/ApplyView.cpp +++ b/src/libxrpl/ledger/ApplyView.cpp @@ -31,7 +31,7 @@ createRoot( ApplyView& view, Keylet const& directory, uint256 const& key, - std::function const&)> const& describe) + std::function const& describe) { auto newRoot = std::make_shared(directory); newRoot->setFieldH256(sfRootIndex, directory.key); @@ -110,7 +110,7 @@ insertPage( SLE::ref next, uint256 const& key, Keylet const& directory, - std::function const&)> const& describe) + std::function const& describe) { // We rely on modulo arithmetic of unsigned integers (guaranteed in // [basic.fundamental] paragraph 2) to detect page representation overflow. @@ -166,7 +166,7 @@ ApplyView::dirAdd( bool preserveOrder, Keylet const& directory, uint256 const& key, - std::function const&)> const& describe) + std::function const& describe) { auto root = peek(directory); diff --git a/src/libxrpl/ledger/ApplyViewBase.cpp b/src/libxrpl/ledger/ApplyViewBase.cpp index e5a8e11b4c..1b6ba78046 100644 --- a/src/libxrpl/ledger/ApplyViewBase.cpp +++ b/src/libxrpl/ledger/ApplyViewBase.cpp @@ -58,7 +58,7 @@ ApplyViewBase::succ(key_type const& key, std::optional const& last) co return items_.succ(*base_, key, last); } -std::shared_ptr +SLE::const_pointer ApplyViewBase::read(Keylet const& k) const { return items_.read(*base_, k); @@ -114,26 +114,26 @@ ApplyViewBase::flags() const return flags_; } -std::shared_ptr +SLE::pointer ApplyViewBase::peek(Keylet const& k) { return items_.peek(*base_, k); } void -ApplyViewBase::erase(std::shared_ptr const& sle) +ApplyViewBase::erase(SLE::ref sle) { items_.erase(*base_, sle); } void -ApplyViewBase::insert(std::shared_ptr const& sle) +ApplyViewBase::insert(SLE::ref sle) { items_.insert(*base_, sle); } void -ApplyViewBase::update(std::shared_ptr const& sle) +ApplyViewBase::update(SLE::ref sle) { items_.update(*base_, sle); } @@ -141,19 +141,19 @@ ApplyViewBase::update(std::shared_ptr const& sle) //--- void -ApplyViewBase::rawErase(std::shared_ptr const& sle) +ApplyViewBase::rawErase(SLE::ref sle) { items_.rawErase(*base_, sle); } void -ApplyViewBase::rawInsert(std::shared_ptr const& sle) +ApplyViewBase::rawInsert(SLE::ref sle) { items_.insert(*base_, sle); } void -ApplyViewBase::rawReplace(std::shared_ptr const& sle) +ApplyViewBase::rawReplace(SLE::ref sle) { items_.replace(*base_, sle); } diff --git a/src/libxrpl/ledger/ApplyViewImpl.cpp b/src/libxrpl/ledger/ApplyViewImpl.cpp index 9650190a3e..66c009cd88 100644 --- a/src/libxrpl/ledger/ApplyViewImpl.cpp +++ b/src/libxrpl/ledger/ApplyViewImpl.cpp @@ -13,7 +13,6 @@ #include #include -#include #include namespace xrpl { @@ -43,11 +42,9 @@ ApplyViewImpl::size() void ApplyViewImpl::visit( OpenView& to, - std::function const& before, - std::shared_ptr const& after)> const& func) + std::function< + void(uint256 const& key, bool isDelete, SLE::const_ref before, SLE::const_ref after)> const& + func) { items_.visit(to, func); } diff --git a/src/libxrpl/ledger/CachedView.cpp b/src/libxrpl/ledger/CachedView.cpp index 8a6c266b8f..1853cc0ac8 100644 --- a/src/libxrpl/ledger/CachedView.cpp +++ b/src/libxrpl/ledger/CachedView.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include @@ -19,7 +18,7 @@ CachedViewImpl::exists(Keylet const& k) const return read(k) != nullptr; } -std::shared_ptr +SLE::const_pointer CachedViewImpl::read(Keylet const& k) const { static CountedObjects::Counter kHits{"CachedView::hit"}; diff --git a/src/libxrpl/ledger/Ledger.cpp b/src/libxrpl/ledger/Ledger.cpp index fe7db9a158..82956650e2 100644 --- a/src/libxrpl/ledger/Ledger.cpp +++ b/src/libxrpl/ledger/Ledger.cpp @@ -401,7 +401,7 @@ Ledger::succ(uint256 const& key, std::optional const& last) const return item->key(); } -std::shared_ptr +SLE::const_pointer Ledger::read(Keylet const& k) const { if (k.key == beast::kZero) @@ -486,7 +486,7 @@ Ledger::digest(key_type const& key) const -> std::optional //------------------------------------------------------------------------------ void -Ledger::rawErase(std::shared_ptr const& sle) +Ledger::rawErase(SLE::ref sle) { if (!stateMap_.delItem(sle->key())) logicError("Ledger::rawErase: key not found"); @@ -500,7 +500,7 @@ Ledger::rawErase(uint256 const& key) } void -Ledger::rawInsert(std::shared_ptr const& sle) +Ledger::rawInsert(SLE::ref sle) { Serializer ss; sle->add(ss); @@ -512,7 +512,7 @@ Ledger::rawInsert(std::shared_ptr const& sle) } void -Ledger::rawReplace(std::shared_ptr const& sle) +Ledger::rawReplace(SLE::ref sle) { Serializer ss; sle->add(ss); @@ -623,7 +623,7 @@ Ledger::setup() return ret; } -std::shared_ptr +SLE::pointer Ledger::peek(Keylet const& k) const { auto const& value = stateMap_.peekItem(k.key); diff --git a/src/libxrpl/ledger/OpenView.cpp b/src/libxrpl/ledger/OpenView.cpp index 40b411fcc0..26cb109f85 100644 --- a/src/libxrpl/ledger/OpenView.cpp +++ b/src/libxrpl/ledger/OpenView.cpp @@ -163,7 +163,7 @@ OpenView::succ(key_type const& key, std::optional const& last) const return items_.succ(*base_, key, last); } -std::shared_ptr +SLE::const_pointer OpenView::read(Keylet const& k) const { return items_.read(*base_, k); @@ -228,19 +228,19 @@ OpenView::txRead(key_type const& key) const -> tx_type //--- void -OpenView::rawErase(std::shared_ptr const& sle) +OpenView::rawErase(SLE::ref sle) { items_.erase(sle); } void -OpenView::rawInsert(std::shared_ptr const& sle) +OpenView::rawInsert(SLE::ref sle) { items_.insert(sle); } void -OpenView::rawReplace(std::shared_ptr const& sle) +OpenView::rawReplace(SLE::ref sle) { items_.replace(sle); } diff --git a/src/libxrpl/ledger/RawStateTable.cpp b/src/libxrpl/ledger/RawStateTable.cpp index 69e2f5c0fe..fbe7bf1d82 100644 --- a/src/libxrpl/ledger/RawStateTable.cpp +++ b/src/libxrpl/ledger/RawStateTable.cpp @@ -20,10 +20,10 @@ namespace xrpl::detail { class RawStateTable::SlesIterImpl : public ReadView::SlesType::iter_base { private: - std::shared_ptr sle0_; + SLE::const_pointer sle0_; ReadView::SlesType::Iterator iter0_; ReadView::SlesType::Iterator end0_; - std::shared_ptr sle1_; + SLE::const_pointer sle1_; items_t::const_iterator iter1_; items_t::const_iterator end1_; @@ -241,7 +241,7 @@ RawStateTable::succ(ReadView const& base, key_type const& key, std::optional const& sle) +RawStateTable::erase(SLE::ref sle) { // The base invariant is checked during apply auto const result = items_.emplace( @@ -267,7 +267,7 @@ RawStateTable::erase(std::shared_ptr const& sle) } void -RawStateTable::insert(std::shared_ptr const& sle) +RawStateTable::insert(SLE::ref sle) { auto const result = items_.emplace( std::piecewise_construct, @@ -292,7 +292,7 @@ RawStateTable::insert(std::shared_ptr const& sle) } void -RawStateTable::replace(std::shared_ptr const& sle) +RawStateTable::replace(SLE::ref sle) { auto const result = items_.emplace( std::piecewise_construct, @@ -313,7 +313,7 @@ RawStateTable::replace(std::shared_ptr const& sle) } } -std::shared_ptr +SLE::const_pointer RawStateTable::read(ReadView const& base, Keylet const& k) const { auto const iter = items_.find(k.key); @@ -323,7 +323,7 @@ RawStateTable::read(ReadView const& base, Keylet const& k) const if (item.action == Action::Erase) return nullptr; // Convert to SLE const - std::shared_ptr sle = item.sle; + SLE::const_pointer sle = item.sle; if (!k.check(*sle)) return nullptr; return sle; diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index c62d79dcac..fdd7998609 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -32,7 +32,6 @@ #include #include -#include #include #include @@ -332,11 +331,7 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal) //------------------------------------------------------------------------------ TER -dirLink( - ApplyView& view, - AccountID const& owner, - std::shared_ptr& object, - SF_UINT64 const& node) +dirLink(ApplyView& view, AccountID const& owner, SLE::pointer& object, SF_UINT64 const& node) { auto const page = view.dirInsert(keylet::ownerDir(owner), object->key(), describeOwnerDir(owner)); @@ -488,7 +483,7 @@ cleanupOnAccountDelete( std::optional maxNodesToDelete) { // Delete all the entries in the account directory. - std::shared_ptr sleDirNode{}; + SLE::pointer sleDirNode{}; unsigned int uDirEntry{0}; uint256 dirEntry{beast::kZero}; std::uint32_t deleted = 0; diff --git a/src/libxrpl/ledger/helpers/AMMHelpers.cpp b/src/libxrpl/ledger/helpers/AMMHelpers.cpp index f7aabc8ea5..fe6d022490 100644 --- a/src/libxrpl/ledger/helpers/AMMHelpers.cpp +++ b/src/libxrpl/ledger/helpers/AMMHelpers.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -636,7 +635,7 @@ deleteAMMTrustLines( keylet::ownerDir(ammAccountID), [&](LedgerEntryType nodeType, uint256 const&, - std::shared_ptr& sleItem) -> std::pair { + SLE::pointer& sleItem) -> std::pair { // Skip AMM and MPToken if (nodeType == ltAMM || nodeType == ltMPTOKEN) return {tesSUCCESS, SkipEntry::Yes}; @@ -672,7 +671,7 @@ deleteAMMMPTokens(Sandbox& sb, AccountID const& ammAccountID, beast::Journal j) keylet::ownerDir(ammAccountID), [&](LedgerEntryType nodeType, uint256 const&, - std::shared_ptr& sleItem) -> std::pair { + SLE::pointer& sleItem) -> std::pair { // Skip AMM if (nodeType == ltAMM) return {tesSUCCESS, SkipEntry::Yes}; @@ -768,7 +767,7 @@ deleteAMMAccount(Sandbox& sb, Asset const& asset, Asset const& asset2, beast::Jo void initializeFeeAuctionVote( ApplyView& view, - std::shared_ptr& ammSle, + SLE::pointer& ammSle, AccountID const& account, Asset const& lptAsset, std::uint16_t tfee) @@ -926,7 +925,7 @@ Expected verifyAndAdjustLPTokenBalance( Sandbox& sb, STAmount const& lpTokens, - std::shared_ptr& ammSle, + SLE::pointer& ammSle, AccountID const& account) { auto const res = isOnlyLiquidityProvider(sb, lpTokens.get(), account); diff --git a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp index 029cb5cd92..1634de93c9 100644 --- a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp +++ b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp @@ -125,11 +125,7 @@ transferRate(ReadView const& view, AccountID const& issuer) } void -adjustOwnerCount( - ApplyView& view, - std::shared_ptr const& sle, - std::int32_t amount, - beast::Journal j) +adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Journal j) { if (!sle) return; @@ -192,9 +188,7 @@ getPseudoAccountFields() } [[nodiscard]] bool -isPseudoAccount( - std::shared_ptr sleAcct, - std::set const& pseudoFieldFilter) +isPseudoAccount(SLE::const_pointer sleAcct, std::set const& pseudoFieldFilter) { auto const& fields = getPseudoAccountFields(); @@ -208,7 +202,7 @@ isPseudoAccount( }) > 0; } -Expected, TER> +Expected createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField) { [[maybe_unused]] diff --git a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp index 838e72d364..28b50b51d6 100644 --- a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp +++ b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp @@ -25,7 +25,6 @@ #include #include -#include #include #include #include @@ -71,7 +70,7 @@ removeExpired(ApplyView& view, STVector256 const& arr, beast::Journal const j) } TER -deleteSLE(ApplyView& view, std::shared_ptr const& sleCredential, beast::Journal j) +deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j) { if (!sleCredential) return tecNO_ENTRY; @@ -231,7 +230,7 @@ TER authorizedDepositPreauth(ReadView const& view, STVector256 const& credIDs, AccountID const& dst) { std::set> sorted; - std::vector> lifeExtender; + std::vector lifeExtender; lifeExtender.reserve(credIDs.size()); for (auto const& h : credIDs) { @@ -352,7 +351,7 @@ verifyDepositPreauth( ApplyView& view, AccountID const& src, AccountID const& dst, - std::shared_ptr const& sleDst, + SLE::const_ref sleDst, beast::Journal j) { // If depositPreauth is enabled, then an account that requires diff --git a/src/libxrpl/ledger/helpers/DirectoryHelpers.cpp b/src/libxrpl/ledger/helpers/DirectoryHelpers.cpp index 8b4eeae7b7..43f8219b6a 100644 --- a/src/libxrpl/ledger/helpers/DirectoryHelpers.cpp +++ b/src/libxrpl/ledger/helpers/DirectoryHelpers.cpp @@ -13,15 +13,13 @@ #include #include -#include - namespace xrpl { bool dirFirst( ApplyView& view, uint256 const& root, - std::shared_ptr& page, + SLE::pointer& page, unsigned int& index, uint256& entry) { @@ -32,7 +30,7 @@ bool dirNext( ApplyView& view, uint256 const& root, - std::shared_ptr& page, + SLE::pointer& page, unsigned int& index, uint256& entry) { @@ -43,7 +41,7 @@ bool cdirFirst( ReadView const& view, uint256 const& root, - std::shared_ptr& page, + SLE::const_pointer& page, unsigned int& index, uint256& entry) { @@ -54,7 +52,7 @@ bool cdirNext( ReadView const& view, uint256 const& root, - std::shared_ptr& page, + SLE::const_pointer& page, unsigned int& index, uint256& entry) { @@ -62,10 +60,7 @@ cdirNext( } void -forEachItem( - ReadView const& view, - Keylet const& root, - std::function const&)> const& f) +forEachItem(ReadView const& view, Keylet const& root, std::function const& f) { XRPL_ASSERT(root.type == ltDIR_NODE, "xrpl::forEachItem : valid root type"); @@ -95,7 +90,7 @@ forEachItemAfter( uint256 const& after, std::uint64_t const hint, unsigned int limit, - std::function const&)> const& f) + std::function const& f) { XRPL_ASSERT(root.type == ltDIR_NODE, "xrpl::forEachItemAfter : valid root type"); @@ -184,7 +179,7 @@ dirIsEmpty(ReadView const& view, Keylet const& k) std::function describeOwnerDir(AccountID const& account) { - return [account](std::shared_ptr const& sle) { (*sle)[sfOwner] = account; }; + return [account](SLE::ref sle) { (*sle)[sfOwner] = account; }; } } // namespace xrpl diff --git a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp index bb784278ba..5b467db796 100644 --- a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp @@ -41,7 +41,7 @@ namespace xrpl::nft { -static std::shared_ptr +static SLE::const_pointer locatePage(ReadView const& view, AccountID const& owner, uint256 const& id) { auto const first = keylet::nftpage(keylet::nftpageMin(owner), id); @@ -54,7 +54,7 @@ locatePage(ReadView const& view, AccountID const& owner, uint256 const& id) Keylet(ltNFTOKEN_PAGE, view.succ(first.key, last.key.next()).value_or(last.key))); } -static std::shared_ptr +static SLE::pointer locatePage(ApplyView& view, AccountID const& owner, uint256 const& id) { auto const first = keylet::nftpage(keylet::nftpageMin(owner), id); @@ -67,7 +67,7 @@ locatePage(ApplyView& view, AccountID const& owner, uint256 const& id) Keylet(ltNFTOKEN_PAGE, view.succ(first.key, last.key.next()).value_or(last.key))); } -static std::shared_ptr +static SLE::pointer getPageForToken( ApplyView& view, AccountID const& owner, @@ -230,7 +230,7 @@ changeTokenURI( uint256 const& nftokenID, std::optional const& uri) { - std::shared_ptr const page = locatePage(view, owner, nftokenID); + SLE::pointer const page = locatePage(view, owner, nftokenID); // If the page couldn't be found, the given NFT isn't owned by this account if (!page) @@ -267,7 +267,7 @@ insertToken(ApplyView& view, AccountID owner, STObject&& nft) // First, we need to locate the page the NFT belongs to, creating it // if necessary. This operation may fail if it is impossible to insert // the NFT. - std::shared_ptr const page = + SLE::pointer const page = getPageForToken(view, owner, nft[sfNFTokenID], [](ApplyView& view, AccountID const& owner) { adjustOwnerCount( view, @@ -296,7 +296,7 @@ insertToken(ApplyView& view, AccountID owner, STObject&& nft) } static bool -mergePages(ApplyView& view, std::shared_ptr const& p1, std::shared_ptr const& p2) +mergePages(ApplyView& view, SLE::ref p1, SLE::ref p2) { if (p1->key() >= p2->key()) Throw("mergePages: pages passed in out of order!"); @@ -355,7 +355,7 @@ mergePages(ApplyView& view, std::shared_ptr const& p1, std::shared_ptr TER removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID) { - std::shared_ptr const page = locatePage(view, owner, nftokenID); + SLE::pointer const page = locatePage(view, owner, nftokenID); // If the page couldn't be found, the given NFT isn't owned by this account if (!page) @@ -366,11 +366,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID) /** Remove the token from the owner's token directory. */ TER -removeToken( - ApplyView& view, - AccountID const& owner, - uint256 const& nftokenID, - std::shared_ptr const& curr) +removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, SLE::ref curr) { // We found a page, but the given NFT may not be in it. auto arr = curr->getFieldArray(sfNFTokens); @@ -386,8 +382,8 @@ removeToken( } // Page management: - auto const loadPage = [&view](std::shared_ptr const& page1, SF_UINT256 const& field) { - std::shared_ptr page2; + auto const loadPage = [&view](SLE::ref page1, SF_UINT256 const& field) { + SLE::pointer page2; if (auto const id = (*page1)[~field]) { @@ -535,7 +531,7 @@ removeToken( std::optional findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID) { - std::shared_ptr const page = locatePage(view, owner, nftokenID); + SLE::const_pointer const page = locatePage(view, owner, nftokenID); // If the page couldn't be found, the given NFT isn't owned by this account if (!page) @@ -554,7 +550,7 @@ findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID std::optional findTokenAndPage(ApplyView& view, AccountID const& owner, uint256 const& nftokenID) { - std::shared_ptr page = locatePage(view, owner, nftokenID); + SLE::pointer page = locatePage(view, owner, nftokenID); // If the page couldn't be found, the given NFT isn't owned by this account if (!page) @@ -623,7 +619,7 @@ removeTokenOffersWithLimit(ApplyView& view, Keylet const& directory, std::size_t } bool -deleteTokenOffer(ApplyView& view, std::shared_ptr const& offer) +deleteTokenOffer(ApplyView& view, SLE::ref offer) { if (offer->getType() != ltNFTOKEN_OFFER) return false; @@ -657,7 +653,7 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner) auto const last = keylet::nftpageMax(owner); - std::shared_ptr page = view.peek(Keylet( + SLE::pointer page = view.peek(Keylet( ltNFTOKEN_PAGE, view.succ(keylet::nftpageMin(owner).key, last.key.next()).value_or(last.key))); @@ -691,7 +687,7 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner) view.update(page); } - std::shared_ptr nextPage; + SLE::pointer nextPage; while ( (nextPage = view.peek(Keylet( ltNFTOKEN_PAGE, view.succ(page->key().next(), last.key.next()).value_or(last.key))))) @@ -956,7 +952,7 @@ tokenOfferCreateApply( auto const offerNode = view.dirInsert( isSellOffer ? keylet::nftSells(nftokenID) : keylet::nftBuys(nftokenID), offerID, - [&nftokenID, isSellOffer](std::shared_ptr const& sle) { + [&nftokenID, isSellOffer](SLE::ref sle) { (*sle)[sfFlags] = isSellOffer ? lsfNFTokenSellOffers : lsfNFTokenBuyOffers; (*sle)[sfNFTokenID] = nftokenID; }); diff --git a/src/libxrpl/ledger/helpers/OfferHelpers.cpp b/src/libxrpl/ledger/helpers/OfferHelpers.cpp index 03a1170aad..5249870143 100644 --- a/src/libxrpl/ledger/helpers/OfferHelpers.cpp +++ b/src/libxrpl/ledger/helpers/OfferHelpers.cpp @@ -12,12 +12,10 @@ #include #include -#include - namespace xrpl { TER -offerDelete(ApplyView& view, std::shared_ptr const& sle, beast::Journal j) +offerDelete(ApplyView& view, SLE::ref sle, beast::Journal j) { if (!sle) return tesSUCCESS; diff --git a/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp b/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp index 31c206d85b..e755dbaca3 100644 --- a/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp +++ b/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp @@ -12,16 +12,10 @@ #include #include -#include - namespace xrpl { TER -closeChannel( - std::shared_ptr const& slep, - ApplyView& view, - uint256 const& key, - beast::Journal j) +closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal j) { AccountID const src = (*slep)[sfAccount]; // Remove PayChan from owner directory diff --git a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp index 5aaa417ad9..7ed7ab8fc4 100644 --- a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp +++ b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp @@ -294,7 +294,7 @@ trustCreate( TER trustDelete( ApplyView& view, - std::shared_ptr const& sleRippleState, + SLE::ref sleRippleState, AccountID const& uLowAccountID, AccountID const& uHighAccountID, beast::Journal j) @@ -738,7 +738,7 @@ removeEmptyHolding( TER deleteAMMTrustLine( ApplyView& view, - std::shared_ptr sleState, + SLE::pointer sleState, std::optional const& ammAccountID, beast::Journal j) { @@ -786,7 +786,7 @@ deleteAMMTrustLine( TER deleteAMMMPToken( ApplyView& view, - std::shared_ptr sleMpt, + SLE::pointer sleMpt, AccountID const& ammAccountID, beast::Journal j) { diff --git a/src/libxrpl/ledger/helpers/VaultHelpers.cpp b/src/libxrpl/ledger/helpers/VaultHelpers.cpp index 587923953d..3a3a756499 100644 --- a/src/libxrpl/ledger/helpers/VaultHelpers.cpp +++ b/src/libxrpl/ledger/helpers/VaultHelpers.cpp @@ -12,16 +12,12 @@ #include // IWYU pragma: keep #include -#include #include namespace xrpl { [[nodiscard]] std::optional -assetsToSharesDeposit( - std::shared_ptr const& vault, - std::shared_ptr const& issuance, - STAmount const& assets) +assetsToSharesDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount const& assets) { XRPL_ASSERT(!assets.negative(), "xrpl::assetsToSharesDeposit : non-negative assets"); XRPL_ASSERT( @@ -45,10 +41,7 @@ assetsToSharesDeposit( } [[nodiscard]] std::optional -sharesToAssetsDeposit( - std::shared_ptr const& vault, - std::shared_ptr const& issuance, - STAmount const& shares) +sharesToAssetsDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount const& shares) { XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsDeposit : non-negative shares"); XRPL_ASSERT( @@ -72,8 +65,8 @@ sharesToAssetsDeposit( [[nodiscard]] std::optional assetsToSharesWithdraw( - std::shared_ptr const& vault, - std::shared_ptr const& issuance, + SLE::const_ref vault, + SLE::const_ref issuance, STAmount const& assets, TruncateShares truncate, WaiveUnrealizedLoss waive) @@ -101,8 +94,8 @@ assetsToSharesWithdraw( [[nodiscard]] std::optional sharesToAssetsWithdraw( - std::shared_ptr const& vault, - std::shared_ptr const& issuance, + SLE::const_ref vault, + SLE::const_ref issuance, STAmount const& shares, WaiveUnrealizedLoss waive) { diff --git a/src/libxrpl/tx/ApplyContext.cpp b/src/libxrpl/tx/ApplyContext.cpp index 88e37baf00..93b0d101af 100644 --- a/src/libxrpl/tx/ApplyContext.cpp +++ b/src/libxrpl/tx/ApplyContext.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -70,11 +69,7 @@ ApplyContext::size() void ApplyContext::visit( - std::function const&, - std::shared_ptr const&)> const& func) + std::function const& func) { view_->visit(base_, func); // NOLINT(bugprone-unchecked-optional-access) } @@ -104,13 +99,11 @@ ApplyContext::checkInvariantsHelper( auto checkers = getInvariantChecks(); // call each check's per-entry method - visit([&checkers]( - uint256 const& index, - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) { - (..., std::get(checkers).visitEntry(isDelete, before, after)); - }); + visit( + [&checkers]( + uint256 const& index, bool isDelete, SLE::const_ref before, SLE::const_ref after) { + (..., std::get(checkers).visitEntry(isDelete, before, after)); + }); // Note: do not replace this logic with a `...&&` fold expression. // The fold expression will only run until the first check fails (it diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index 28fa059902..51541cc2e3 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -45,7 +45,6 @@ #include #include #include -#include #include #include #include @@ -789,7 +788,7 @@ Transactor::checkSingleSign( ReadView const& view, AccountID const& idSigner, AccountID const& idAccount, - std::shared_ptr sleAccount, + SLE::const_pointer sleAccount, beast::Journal const j) { bool const isMasterDisabled = sleAccount->isFlag(lsfDisableMaster); @@ -825,7 +824,7 @@ Transactor::checkMultiSign( beast::Journal const j) { // Get id's SignerList and Quorum. - std::shared_ptr const sleAccountSigners = view.read(keylet::signers(id)); + STLedgerEntry::const_pointer const sleAccountSigners = view.read(keylet::signers(id)); // If the signer list doesn't exist the account is not multi-signing. if (!sleAccountSigners) { @@ -1286,8 +1285,8 @@ Transactor::operator()() &expiredCredentials]( uint256 const& index, bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) { + SLE::const_ref before, + SLE::const_ref after) { if (isDelete) { XRPL_ASSERT( diff --git a/src/libxrpl/tx/invariants/AMMInvariant.cpp b/src/libxrpl/tx/invariants/AMMInvariant.cpp index be2a803e93..ecd7bedf89 100644 --- a/src/libxrpl/tx/invariants/AMMInvariant.cpp +++ b/src/libxrpl/tx/invariants/AMMInvariant.cpp @@ -19,16 +19,12 @@ #include #include -#include #include namespace xrpl { void -ValidAMM::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) +ValidAMM::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { if (isDelete) return; diff --git a/src/libxrpl/tx/invariants/FreezeInvariant.cpp b/src/libxrpl/tx/invariants/FreezeInvariant.cpp index eaaeb7fc6f..0293d42e97 100644 --- a/src/libxrpl/tx/invariants/FreezeInvariant.cpp +++ b/src/libxrpl/tx/invariants/FreezeInvariant.cpp @@ -16,16 +16,12 @@ #include #include -#include #include namespace xrpl { void -TransfersNotFrozen::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) +TransfersNotFrozen::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { /* * A trust line freeze state alone doesn't determine if a transfer is @@ -107,9 +103,7 @@ TransfersNotFrozen::finalize( } bool -TransfersNotFrozen::isValidEntry( - std::shared_ptr const& before, - std::shared_ptr const& after) +TransfersNotFrozen::isValidEntry(SLE::const_ref before, SLE::const_ref after) { // `after` can never be null, even if the trust line is deleted. XRPL_ASSERT(after, "xrpl::TransfersNotFrozen::isValidEntry : valid after."); @@ -135,8 +129,8 @@ TransfersNotFrozen::isValidEntry( STAmount TransfersNotFrozen::calculateBalanceChange( - std::shared_ptr const& before, - std::shared_ptr const& after, + SLE::const_ref before, + SLE::const_ref after, bool isDelete) { auto const getBalance = [](auto const& line, auto const& other, bool zero) { @@ -180,9 +174,7 @@ TransfersNotFrozen::recordBalance(Issue const& issue, BalanceChange change) } void -TransfersNotFrozen::recordBalanceChanges( - std::shared_ptr const& after, - STAmount const& balanceChange) +TransfersNotFrozen::recordBalanceChanges(SLE::const_ref after, STAmount const& balanceChange) { auto const balanceChangeSign = balanceChange.signum(); auto const currency = after->at(sfBalance).get().currency; @@ -198,7 +190,7 @@ TransfersNotFrozen::recordBalanceChanges( {.line = after, .balanceChangeSign = -balanceChangeSign}); } -std::shared_ptr +SLE::const_pointer TransfersNotFrozen::findIssuer(AccountID const& issuerID, ReadView const& view) { if (auto it = possibleIssuers_.find(issuerID); it != possibleIssuers_.end()) @@ -211,7 +203,7 @@ TransfersNotFrozen::findIssuer(AccountID const& issuerID, ReadView const& view) bool TransfersNotFrozen::validateIssuerChanges( - std::shared_ptr const& issuer, + SLE::const_ref issuer, IssuerChanges const& changes, STTx const& tx, beast::Journal const& j, diff --git a/src/libxrpl/tx/invariants/InvariantCheck.cpp b/src/libxrpl/tx/invariants/InvariantCheck.cpp index 0154dca747..b4a533905c 100644 --- a/src/libxrpl/tx/invariants/InvariantCheck.cpp +++ b/src/libxrpl/tx/invariants/InvariantCheck.cpp @@ -64,10 +64,7 @@ hasPrivilege(STTx const& tx, Privilege priv) #pragma pop_macro("TRANSACTION") void -TransactionFeeCheck::visitEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +TransactionFeeCheck::visitEntry(bool, SLE::const_ref, SLE::const_ref) { // nothing to do } @@ -110,10 +107,7 @@ TransactionFeeCheck::finalize( //------------------------------------------------------------------------------ void -XRPNotCreated::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) +XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { /* We go through all modified ledger entries, looking only at account roots, * escrow payments, and payment channels. We remove from the total any @@ -192,10 +186,7 @@ XRPNotCreated::finalize( //------------------------------------------------------------------------------ void -XRPBalanceChecks::visitEntry( - bool, - std::shared_ptr const& before, - std::shared_ptr const& after) +XRPBalanceChecks::visitEntry(bool, SLE::const_ref before, SLE::const_ref after) { auto isBad = [](STAmount const& balance) { if (!balance.native()) @@ -242,10 +233,7 @@ XRPBalanceChecks::finalize( //------------------------------------------------------------------------------ void -NoBadOffers::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) +NoBadOffers::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { auto isBad = [](STAmount const& pays, STAmount const& gets) { // An offer should never be negative @@ -286,10 +274,7 @@ NoBadOffers::finalize( //------------------------------------------------------------------------------ void -NoZeroEscrow::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) +NoZeroEscrow::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { auto isBad = [](STAmount const& amount) { // XRP case @@ -393,10 +378,7 @@ NoZeroEscrow::finalize( //------------------------------------------------------------------------------ void -AccountRootsNotDeleted::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const&) +AccountRootsNotDeleted::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref) { if (isDelete && before && before->getType() == ltACCOUNT_ROOT) accountsDeleted_++; @@ -446,10 +428,7 @@ AccountRootsNotDeleted::finalize( //------------------------------------------------------------------------------ void -AccountRootsDeletedClean::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) +AccountRootsDeletedClean::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { if (isDelete && before && before->getType() == ltACCOUNT_ROOT) accountsDeleted_.emplace_back(before, after); @@ -566,10 +545,7 @@ AccountRootsDeletedClean::finalize( //------------------------------------------------------------------------------ void -LedgerEntryTypesMatch::visitEntry( - bool, - std::shared_ptr const& before, - std::shared_ptr const& after) +LedgerEntryTypesMatch::visitEntry(bool, SLE::const_ref before, SLE::const_ref after) { if (before && after && before->getType() != after->getType()) typeMismatch_ = true; @@ -623,10 +599,7 @@ LedgerEntryTypesMatch::finalize( //------------------------------------------------------------------------------ void -NoXRPTrustLines::visitEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const& after) +NoXRPTrustLines::visitEntry(bool, SLE::const_ref, SLE::const_ref after) { bool const overwriteFixEnabled = isFeatureEnabled(fixCleanup3_1_3, true); @@ -666,10 +639,7 @@ NoXRPTrustLines::finalize( //------------------------------------------------------------------------------ void -NoDeepFreezeTrustLinesWithoutFreeze::visitEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const& after) +NoDeepFreezeTrustLinesWithoutFreeze::visitEntry(bool, SLE::const_ref, SLE::const_ref after) { if (after && after->getType() == ltRIPPLE_STATE) { @@ -712,10 +682,7 @@ NoDeepFreezeTrustLinesWithoutFreeze::finalize( //------------------------------------------------------------------------------ void -ValidNewAccountRoot::visitEntry( - bool, - std::shared_ptr const& before, - std::shared_ptr const& after) +ValidNewAccountRoot::visitEntry(bool, SLE::const_ref before, SLE::const_ref after) { if (!before && after->getType() == ltACCOUNT_ROOT) { @@ -789,10 +756,7 @@ ValidNewAccountRoot::finalize( //------------------------------------------------------------------------------ void -ValidClawback::visitEntry( - bool, - std::shared_ptr const& before, - std::shared_ptr const&) +ValidClawback::visitEntry(bool, SLE::const_ref before, SLE::const_ref) { if (before && before->getType() == ltRIPPLE_STATE) trustlinesChanged_++; @@ -877,10 +841,7 @@ ValidClawback::finalize( //------------------------------------------------------------------------------ void -ValidPseudoAccounts::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) +ValidPseudoAccounts::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { if (isDelete) { @@ -968,10 +929,7 @@ ValidPseudoAccounts::finalize( //------------------------------------------------------------------------------ void -NoModifiedUnmodifiableFields::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) +NoModifiedUnmodifiableFields::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { if (isDelete || !before) { diff --git a/src/libxrpl/tx/invariants/LoanBrokerInvariant.cpp b/src/libxrpl/tx/invariants/LoanBrokerInvariant.cpp index 56995ef94c..8586a27be3 100644 --- a/src/libxrpl/tx/invariants/LoanBrokerInvariant.cpp +++ b/src/libxrpl/tx/invariants/LoanBrokerInvariant.cpp @@ -15,15 +15,10 @@ #include #include -#include - namespace xrpl { void -ValidLoanBroker::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) +ValidLoanBroker::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { if (after) { diff --git a/src/libxrpl/tx/invariants/LoanInvariant.cpp b/src/libxrpl/tx/invariants/LoanInvariant.cpp index 3eef0957e5..ce9a7c6e03 100644 --- a/src/libxrpl/tx/invariants/LoanInvariant.cpp +++ b/src/libxrpl/tx/invariants/LoanInvariant.cpp @@ -12,15 +12,10 @@ #include #include -#include - namespace xrpl { void -ValidLoan::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) +ValidLoan::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { if (after && after->getType() == ltLOAN) { diff --git a/src/libxrpl/tx/invariants/MPTInvariant.cpp b/src/libxrpl/tx/invariants/MPTInvariant.cpp index 635af25b61..a2369cc1b9 100644 --- a/src/libxrpl/tx/invariants/MPTInvariant.cpp +++ b/src/libxrpl/tx/invariants/MPTInvariant.cpp @@ -24,14 +24,10 @@ #include #include #include - namespace xrpl { void -ValidMPTIssuance::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) +ValidMPTIssuance::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { // The sfReferenceHolding tracking and the deleted-holding capture are // only meaningful post-fixCleanup3_2_0 (the field is never set @@ -369,10 +365,7 @@ ValidMPTIssuance::finalize( } void -ValidMPTPayment::visitEntry( - bool, - std::shared_ptr const& before, - std::shared_ptr const& after) +ValidMPTPayment::visitEntry(bool, SLE::const_ref before, SLE::const_ref after) { if (overflow_) return; diff --git a/src/libxrpl/tx/invariants/NFTInvariant.cpp b/src/libxrpl/tx/invariants/NFTInvariant.cpp index 09ae9db925..52ecbcd9d1 100644 --- a/src/libxrpl/tx/invariants/NFTInvariant.cpp +++ b/src/libxrpl/tx/invariants/NFTInvariant.cpp @@ -20,16 +20,12 @@ #include #include -#include #include namespace xrpl { void -ValidNFTokenPage::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) +ValidNFTokenPage::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { static constexpr uint256 const& kPageBits = nft::kPageMask; static constexpr uint256 kAccountBits = ~kPageBits; @@ -38,7 +34,7 @@ ValidNFTokenPage::visitEntry( (after && after->getType() != ltNFTOKEN_PAGE)) return; - auto check = [this, isDelete](std::shared_ptr const& sle) { + auto check = [this, isDelete](SLE::const_ref sle) { uint256 const account = sle->key() & kAccountBits; uint256 const hiLimit = sle->key() & kPageBits; std::optional const prev = (*sle)[~sfPreviousPageMin]; @@ -187,10 +183,7 @@ ValidNFTokenPage::finalize( //------------------------------------------------------------------------------ void -NFTokenCountTracking::visitEntry( - bool, - std::shared_ptr const& before, - std::shared_ptr const& after) +NFTokenCountTracking::visitEntry(bool, SLE::const_ref before, SLE::const_ref after) { if (before && before->getType() == ltACCOUNT_ROOT) { diff --git a/src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp b/src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp index 282df85302..1014642b36 100644 --- a/src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp +++ b/src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp @@ -14,15 +14,10 @@ #include #include -#include - namespace xrpl { void -ValidPermissionedDEX::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) +ValidPermissionedDEX::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { if (after && after->getType() == ltDIR_NODE) { diff --git a/src/libxrpl/tx/invariants/PermissionedDomainInvariant.cpp b/src/libxrpl/tx/invariants/PermissionedDomainInvariant.cpp index 93c1b2058e..544a3af2dc 100644 --- a/src/libxrpl/tx/invariants/PermissionedDomainInvariant.cpp +++ b/src/libxrpl/tx/invariants/PermissionedDomainInvariant.cpp @@ -15,23 +15,19 @@ #include #include -#include #include namespace xrpl { void -ValidPermissionedDomain::visitEntry( - bool isDel, - std::shared_ptr const& before, - std::shared_ptr const& after) +ValidPermissionedDomain::visitEntry(bool isDel, SLE::const_ref before, SLE::const_ref after) { if (before && before->getType() != ltPERMISSIONED_DOMAIN) return; if (after && after->getType() != ltPERMISSIONED_DOMAIN) return; - auto check = [isDel](std::vector& sleStatus, std::shared_ptr const& sle) { + auto check = [isDel](std::vector& sleStatus, SLE::const_ref sle) { auto const& credentials = sle->getFieldArray(sfAcceptedCredentials); auto const sorted = credentials::makeSorted(credentials); diff --git a/src/libxrpl/tx/invariants/VaultInvariant.cpp b/src/libxrpl/tx/invariants/VaultInvariant.cpp index 4aa79279a1..80b8f36bd9 100644 --- a/src/libxrpl/tx/invariants/VaultInvariant.cpp +++ b/src/libxrpl/tx/invariants/VaultInvariant.cpp @@ -23,7 +23,6 @@ #include #include -#include #include #include #include @@ -63,10 +62,7 @@ ValidVault::Shares::make(SLE const& from) } void -ValidVault::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) +ValidVault::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { // If `before` is empty, this means an object is being created, in which // case `isDelete` must be false. Otherwise `before` and `after` are set and diff --git a/src/libxrpl/tx/paths/BookTip.cpp b/src/libxrpl/tx/paths/BookTip.cpp index 5be512aa84..eda11e5d72 100644 --- a/src/libxrpl/tx/paths/BookTip.cpp +++ b/src/libxrpl/tx/paths/BookTip.cpp @@ -8,8 +8,6 @@ #include #include -#include - namespace xrpl { BookTip::BookTip(ApplyView& view, Book const& book) @@ -40,7 +38,7 @@ BookTip::step(beast::Journal j) return false; unsigned int di = 0; - std::shared_ptr dir; + SLE::pointer dir; if (dirFirst(view_, *firstPage, dir, di, index_)) { diff --git a/src/libxrpl/tx/paths/DirectStep.cpp b/src/libxrpl/tx/paths/DirectStep.cpp index a16c3afd6a..cc6e75ea3d 100644 --- a/src/libxrpl/tx/paths/DirectStep.cpp +++ b/src/libxrpl/tx/paths/DirectStep.cpp @@ -269,7 +269,7 @@ public: // Verify the consistency of the step. These checks are specific to // payments and assume that general checks were already performed. [[nodiscard]] TER - check(StrandContext const& ctx, std::shared_ptr const& sleSrc) const; + check(StrandContext const& ctx, SLE::const_ref sleSrc) const; [[nodiscard]] std::string logString() const override @@ -327,7 +327,7 @@ public: // Verify the consistency of the step. These checks are specific to // offer crossing and assume that general checks were already performed. static TER - check(StrandContext const& ctx, std::shared_ptr const& sleSrc); + check(StrandContext const& ctx, SLE::const_ref sleSrc); [[nodiscard]] std::string logString() const override @@ -415,7 +415,7 @@ DirectIOfferCrossingStep::maxFlow(ReadView const& sb, IOUAmount const& desired) } TER -DirectIPaymentStep::check(StrandContext const& ctx, std::shared_ptr const& sleSrc) const +DirectIPaymentStep::check(StrandContext const& ctx, SLE::const_ref sleSrc) const { // Since this is a payment a trust line must be present. Perform all // trust line related checks. @@ -463,7 +463,7 @@ DirectIPaymentStep::check(StrandContext const& ctx, std::shared_ptr c } TER -DirectIOfferCrossingStep::check(StrandContext const&, std::shared_ptr const&) +DirectIOfferCrossingStep::check(StrandContext const&, SLE::const_ref) { // The standard checks are all we can do because any remaining checks // require the existence of a trust line. Offer crossing does not diff --git a/src/libxrpl/tx/paths/MPTEndpointStep.cpp b/src/libxrpl/tx/paths/MPTEndpointStep.cpp index 5595f8ea65..7dcd6d9241 100644 --- a/src/libxrpl/tx/paths/MPTEndpointStep.cpp +++ b/src/libxrpl/tx/paths/MPTEndpointStep.cpp @@ -264,7 +264,7 @@ public: // Verify the consistency of the step. These checks are specific to // payments and assume that general checks were already performed. [[nodiscard]] TER - check(StrandContext const& ctx, std::shared_ptr const& sleSrc) const; + check(StrandContext const& ctx, SLE::const_ref sleSrc) const; [[nodiscard]] std::string logString() const override @@ -312,7 +312,7 @@ public: // Verify the consistency of the step. These checks are specific to // offer crossing and assume that general checks were already performed. static TER - check(StrandContext const& ctx, std::shared_ptr const& sleSrc); + check(StrandContext const& ctx, SLE::const_ref sleSrc); [[nodiscard]] std::string logString() const override @@ -328,8 +328,7 @@ public: //------------------------------------------------------------------------------ TER -MPTEndpointPaymentStep::check(StrandContext const& ctx, std::shared_ptr const& sleSrc) - const +MPTEndpointPaymentStep::check(StrandContext const& ctx, SLE::const_ref sleSrc) const { // Since this is a payment, MPToken must be present. Perform all // MPToken related checks. @@ -393,7 +392,7 @@ MPTEndpointPaymentStep::check(StrandContext const& ctx, std::shared_ptr const&) +MPTEndpointOfferCrossingStep::check(StrandContext const& ctx, SLE::const_ref) { // The standard checks are all we can do because any remaining checks // require the existence of a MPToken. Offer crossing does not diff --git a/src/libxrpl/tx/paths/OfferStream.cpp b/src/libxrpl/tx/paths/OfferStream.cpp index bfe2bbd1bb..b7defb4df8 100644 --- a/src/libxrpl/tx/paths/OfferStream.cpp +++ b/src/libxrpl/tx/paths/OfferStream.cpp @@ -27,7 +27,6 @@ #include #include -#include #include namespace xrpl { @@ -205,7 +204,7 @@ TOfferStreamBase::step() if (!tip_.step(j_)) return false; - std::shared_ptr const entry = tip_.entry(); + SLE::pointer const entry = tip_.entry(); // If we exceed the maximum number of allowed steps, we're done. if (!counter_.step()) diff --git a/src/libxrpl/tx/transactors/account/AccountDelete.cpp b/src/libxrpl/tx/transactors/account/AccountDelete.cpp index c0e8fe05c6..833f1c8b25 100644 --- a/src/libxrpl/tx/transactors/account/AccountDelete.cpp +++ b/src/libxrpl/tx/transactors/account/AccountDelete.cpp @@ -32,7 +32,6 @@ #include #include -#include #include namespace xrpl { @@ -72,7 +71,7 @@ using DeleterFuncPtr = TER (*)( ApplyView& view, AccountID const& account, uint256 const& delIndex, - std::shared_ptr const& sleDel, + SLE::ref sleDel, beast::Journal j); // Local function definitions that provides signature compatibility. @@ -82,7 +81,7 @@ offerDelete( ApplyView& view, AccountID const& account, uint256 const& delIndex, - std::shared_ptr const& sleDel, + SLE::ref sleDel, beast::Journal j) { return offerDelete(view, sleDel, j); @@ -94,7 +93,7 @@ removeSignersFromLedger( ApplyView& view, AccountID const& account, uint256 const& delIndex, - std::shared_ptr const& sleDel, + SLE::ref sleDel, beast::Journal j) { return SignerListSet::removeFromLedger(registry, view, account, j); @@ -106,7 +105,7 @@ removeTicketFromLedger( ApplyView& view, AccountID const& account, uint256 const& delIndex, - std::shared_ptr const&, + SLE::ref, beast::Journal j) { return Transactor::ticketDelete(view, account, delIndex, j); @@ -118,7 +117,7 @@ removeDepositPreauthFromLedger( ApplyView& view, AccountID const&, uint256 const& delIndex, - std::shared_ptr const&, + SLE::ref, beast::Journal j) { return DepositPreauth::removeFromLedger(view, delIndex, j); @@ -130,7 +129,7 @@ removeNFTokenOfferFromLedger( ApplyView& view, AccountID const& account, uint256 const& delIndex, - std::shared_ptr const& sleDel, + SLE::ref sleDel, beast::Journal) { if (!nft::deleteTokenOffer(view, sleDel)) @@ -145,7 +144,7 @@ removeDIDFromLedger( ApplyView& view, AccountID const& account, uint256 const& delIndex, - std::shared_ptr const& sleDel, + SLE::ref sleDel, beast::Journal j) { return DIDDelete::deleteSLE(view, sleDel, account, j); @@ -157,7 +156,7 @@ removeOracleFromLedger( ApplyView& view, AccountID const& account, uint256 const&, - std::shared_ptr const& sleDel, + SLE::ref sleDel, beast::Journal j) { return OracleDelete::deleteOracle(view, sleDel, account, j); @@ -169,7 +168,7 @@ removeCredentialFromLedger( ApplyView& view, AccountID const&, uint256 const&, - std::shared_ptr const& sleDel, + SLE::ref sleDel, beast::Journal j) { return credentials::deleteSLE(view, sleDel, j); @@ -181,7 +180,7 @@ removeDelegateFromLedger( ApplyView& view, AccountID const&, uint256 const&, - std::shared_ptr const& sleDel, + SLE::ref sleDel, beast::Journal j) { return DelegateSet::deleteDelegate(view, sleDel, j); @@ -301,7 +300,7 @@ AccountDelete::preclaim(PreclaimContext const& ctx) if (dirIsEmpty(ctx.view, ownerDirKeylet)) return tesSUCCESS; - std::shared_ptr sleDirNode{}; + SLE::const_pointer sleDirNode{}; unsigned int uDirEntry{0}; uint256 dirEntry{beast::kZero}; @@ -368,7 +367,7 @@ AccountDelete::doApply() ownerDirKeylet, [&](LedgerEntryType nodeType, uint256 const& dirEntry, - std::shared_ptr& sleItem) -> std::pair { + SLE::pointer& sleItem) -> std::pair { if (auto deleter = nonObligationDeleter(nodeType)) { TER const result{deleter(ctx_.registry, view(), accountID_, dirEntry, sleItem, j_)}; @@ -417,10 +416,7 @@ AccountDelete::doApply() } void -AccountDelete::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +AccountDelete::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/account/AccountSet.cpp b/src/libxrpl/tx/transactors/account/AccountSet.cpp index b52db14720..bc207b39dc 100644 --- a/src/libxrpl/tx/transactors/account/AccountSet.cpp +++ b/src/libxrpl/tx/transactors/account/AccountSet.cpp @@ -26,7 +26,6 @@ #include #include -#include #include namespace xrpl { @@ -643,10 +642,7 @@ AccountSet::doApply() } void -AccountSet::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +AccountSet::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/account/SetRegularKey.cpp b/src/libxrpl/tx/transactors/account/SetRegularKey.cpp index 66a0bef336..08ccf5e7ab 100644 --- a/src/libxrpl/tx/transactors/account/SetRegularKey.cpp +++ b/src/libxrpl/tx/transactors/account/SetRegularKey.cpp @@ -13,8 +13,6 @@ #include #include -#include - namespace xrpl { XRPAmount @@ -81,10 +79,7 @@ SetRegularKey::doApply() } void -SetRegularKey::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +SetRegularKey::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/account/SignerListSet.cpp b/src/libxrpl/tx/transactors/account/SignerListSet.cpp index 7a5d2c60b0..cedb9ace78 100644 --- a/src/libxrpl/tx/transactors/account/SignerListSet.cpp +++ b/src/libxrpl/tx/transactors/account/SignerListSet.cpp @@ -406,10 +406,7 @@ SignerListSet::writeSignersToSLE(SLE::pointer const& ledgerEntry, std::uint32_t } void -SignerListSet::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +SignerListSet::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp index 76b274a609..7c9c9d95dc 100644 --- a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp +++ b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp @@ -798,21 +798,21 @@ readOrpeekBridge(F&& getter, STXChainBridge const& bridgeSpec) return tryGet(STXChainBridge::ChainType::Issuing); } -std::shared_ptr +SLE::pointer peekBridge(ApplyView& v, STXChainBridge const& bridgeSpec) { return readOrpeekBridge( - [&v](STXChainBridge const& b, STXChainBridge::ChainType ct) -> std::shared_ptr { + [&v](STXChainBridge const& b, STXChainBridge::ChainType ct) -> SLE::pointer { return v.peek(keylet::bridge(b, ct)); }, bridgeSpec); } -std::shared_ptr +SLE::const_pointer readBridge(ReadView const& v, STXChainBridge const& bridgeSpec) { return readOrpeekBridge( - [&v](STXChainBridge const& b, STXChainBridge::ChainType ct) -> std::shared_ptr { + [&v](STXChainBridge const& b, STXChainBridge::ChainType ct) -> SLE::const_pointer { return v.read(keylet::bridge(b, ct)); }, bridgeSpec); @@ -2225,10 +2225,7 @@ XChainCreateAccountCommit::doApply() } void -XChainCreateBridge::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +XChainCreateBridge::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } @@ -2246,10 +2243,7 @@ XChainCreateBridge::finalizeInvariants( } void -BridgeModify::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +BridgeModify::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } @@ -2267,10 +2261,7 @@ BridgeModify::finalizeInvariants( } void -XChainClaim::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +XChainClaim::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } @@ -2283,10 +2274,7 @@ XChainClaim::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, be } void -XChainCommit::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +XChainCommit::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } @@ -2304,10 +2292,7 @@ XChainCommit::finalizeInvariants( } void -XChainCreateClaimID::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +XChainCreateClaimID::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } @@ -2325,10 +2310,7 @@ XChainCreateClaimID::finalizeInvariants( } void -XChainAddClaimAttestation::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +XChainAddClaimAttestation::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } @@ -2346,10 +2328,7 @@ XChainAddClaimAttestation::finalizeInvariants( } void -XChainAddAccountCreateAttestation::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +XChainAddAccountCreateAttestation::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } @@ -2367,10 +2346,7 @@ XChainAddAccountCreateAttestation::finalizeInvariants( } void -XChainCreateAccountCommit::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +XChainCreateAccountCommit::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/check/CheckCancel.cpp b/src/libxrpl/tx/transactors/check/CheckCancel.cpp index 7f1708fd81..d966772191 100644 --- a/src/libxrpl/tx/transactors/check/CheckCancel.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCancel.cpp @@ -14,8 +14,6 @@ #include #include -#include - namespace xrpl { NotTEC @@ -102,10 +100,7 @@ CheckCancel::doApply() } void -CheckCancel::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +CheckCancel::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/check/CheckCash.cpp b/src/libxrpl/tx/transactors/check/CheckCash.cpp index af903ff177..c5813ae42d 100644 --- a/src/libxrpl/tx/transactors/check/CheckCash.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCash.cpp @@ -31,7 +31,6 @@ #include #include -#include #include namespace xrpl { @@ -388,7 +387,7 @@ CheckCash::doApply() // Check reserve. Return destination account SLE if enough reserve, // otherwise return nullptr. - auto checkReserve = [&]() -> std::shared_ptr { + auto checkReserve = [&]() -> SLE::pointer { auto sleDst = psb.peek(keylet::account(accountID_)); // Can the account cover the trust line's or MPT reserve? @@ -592,10 +591,7 @@ CheckCash::doApply() } void -CheckCash::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +CheckCash::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/check/CheckCreate.cpp b/src/libxrpl/tx/transactors/check/CheckCreate.cpp index d2d84536de..ff82912a1f 100644 --- a/src/libxrpl/tx/transactors/check/CheckCreate.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCreate.cpp @@ -258,10 +258,7 @@ CheckCreate::doApply() } void -CheckCreate::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +CheckCreate::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp b/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp index 4e9857d782..e0ebdd893a 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp @@ -20,8 +20,6 @@ #include #include -#include - namespace xrpl { using namespace credentials; @@ -126,10 +124,7 @@ CredentialAccept::doApply() } void -CredentialAccept::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +CredentialAccept::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp b/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp index bcb0a6fefa..acca408a95 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp @@ -179,10 +179,7 @@ CredentialCreate::doApply() } void -CredentialCreate::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +CredentialCreate::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp b/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp index fcd1848cbe..6bd4ad54c5 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp @@ -16,8 +16,6 @@ #include #include -#include - namespace xrpl { using namespace credentials; @@ -97,10 +95,7 @@ CredentialDelete::doApply() } void -CredentialDelete::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +CredentialDelete::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp index 30d703686c..82fe88aa9f 100644 --- a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp +++ b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp @@ -132,7 +132,7 @@ DelegateSet::doApply() } TER -DelegateSet::deleteDelegate(ApplyView& view, std::shared_ptr const& sle, beast::Journal j) +DelegateSet::deleteDelegate(ApplyView& view, SLE::ref sle, beast::Journal j) { if (!sle) return tecINTERNAL; // LCOV_EXCL_LINE @@ -174,10 +174,7 @@ DelegateSet::deleteDelegate(ApplyView& view, std::shared_ptr const& sle, be } void -DelegateSet::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +DelegateSet::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/delegate/DelegateUtils.cpp b/src/libxrpl/tx/transactors/delegate/DelegateUtils.cpp index 4d3d97e443..dc6c98f95e 100644 --- a/src/libxrpl/tx/transactors/delegate/DelegateUtils.cpp +++ b/src/libxrpl/tx/transactors/delegate/DelegateUtils.cpp @@ -7,12 +7,11 @@ #include #include -#include #include namespace xrpl { NotTEC -checkTxPermission(std::shared_ptr const& delegate, STTx const& tx) +checkTxPermission(SLE::const_ref delegate, STTx const& tx) { if (!delegate) return terNO_DELEGATE_PERMISSION; @@ -32,7 +31,7 @@ checkTxPermission(std::shared_ptr const& delegate, STTx const& tx) void loadGranularPermission( - std::shared_ptr const& delegate, + SLE::const_ref delegate, TxType const& txType, std::unordered_set& granularPermissions) { diff --git a/src/libxrpl/tx/transactors/dex/AMMBid.cpp b/src/libxrpl/tx/transactors/dex/AMMBid.cpp index b3b41fbfa2..a98f439d0a 100644 --- a/src/libxrpl/tx/transactors/dex/AMMBid.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMBid.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -379,10 +378,7 @@ AMMBid::doApply() } void -AMMBid::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +AMMBid::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/dex/AMMClawback.cpp b/src/libxrpl/tx/transactors/dex/AMMClawback.cpp index 43abc6ae29..b94e97e931 100644 --- a/src/libxrpl/tx/transactors/dex/AMMClawback.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMClawback.cpp @@ -27,7 +27,6 @@ #include #include -#include #include #include @@ -383,10 +382,7 @@ AMMClawback::equalWithdrawMatchingOneAmount( } void -AMMClawback::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +AMMClawback::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/dex/AMMCreate.cpp b/src/libxrpl/tx/transactors/dex/AMMCreate.cpp index 60508eab85..9c1a5cfacb 100644 --- a/src/libxrpl/tx/transactors/dex/AMMCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMCreate.cpp @@ -389,10 +389,7 @@ AMMCreate::doApply() } void -AMMCreate::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +AMMCreate::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/dex/AMMDelete.cpp b/src/libxrpl/tx/transactors/dex/AMMDelete.cpp index 8a8cab1c03..e2ecec8242 100644 --- a/src/libxrpl/tx/transactors/dex/AMMDelete.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMDelete.cpp @@ -15,8 +15,6 @@ #include #include -#include - namespace xrpl { bool @@ -67,10 +65,7 @@ AMMDelete::doApply() } void -AMMDelete::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +AMMDelete::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp index f45529f617..91858e3cd7 100644 --- a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include @@ -1014,10 +1013,7 @@ AMMDeposit::equalDepositInEmptyState( } void -AMMDeposit::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +AMMDeposit::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/dex/AMMVote.cpp b/src/libxrpl/tx/transactors/dex/AMMVote.cpp index 391f7e1ecc..23604d46cc 100644 --- a/src/libxrpl/tx/transactors/dex/AMMVote.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMVote.cpp @@ -22,7 +22,6 @@ #include #include -#include #include #include @@ -249,10 +248,7 @@ AMMVote::doApply() } void -AMMVote::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +AMMVote::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp index 352f637f2f..e57f8558ff 100644 --- a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp @@ -32,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -758,7 +757,7 @@ AMMWithdraw::equalWithdrawTokens( std::pair AMMWithdraw::deleteAMMAccountIfEmpty( Sandbox& sb, - std::shared_ptr const ammSle, + SLE::pointer const ammSle, STAmount const& lpTokenBalance, Asset const& asset1, Asset const& asset2, @@ -1137,10 +1136,7 @@ AMMWithdraw::isWithdrawAll(STTx const& tx) return WithdrawAll::No; } void -AMMWithdraw::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +AMMWithdraw::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/dex/OfferCancel.cpp b/src/libxrpl/tx/transactors/dex/OfferCancel.cpp index 42692b59cc..0dea5fa967 100644 --- a/src/libxrpl/tx/transactors/dex/OfferCancel.cpp +++ b/src/libxrpl/tx/transactors/dex/OfferCancel.cpp @@ -10,8 +10,6 @@ #include #include -#include - namespace xrpl { NotTEC @@ -70,10 +68,7 @@ OfferCancel::doApply() } void -OfferCancel::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +OfferCancel::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp index f982837c5e..547d40e7b8 100644 --- a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp @@ -559,7 +559,7 @@ OfferCreate::formatAmount(STAmount const& amount) TER OfferCreate::applyHybrid( Sandbox& sb, - std::shared_ptr sleOffer, + STLedgerEntry::pointer sleOffer, Keylet const& offerKey, STAmount const& saTakerPays, STAmount const& saTakerGets, @@ -990,10 +990,7 @@ OfferCreate::doApply() } void -OfferCreate::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +OfferCreate::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/did/DIDDelete.cpp b/src/libxrpl/tx/transactors/did/DIDDelete.cpp index 4b4fb47169..90aa21d8a1 100644 --- a/src/libxrpl/tx/transactors/did/DIDDelete.cpp +++ b/src/libxrpl/tx/transactors/did/DIDDelete.cpp @@ -16,8 +16,6 @@ #include #include -#include - namespace xrpl { NotTEC @@ -37,11 +35,7 @@ DIDDelete::deleteSLE(ApplyContext& ctx, Keylet sleKeylet, AccountID const owner) } TER -DIDDelete::deleteSLE( - ApplyView& view, - std::shared_ptr sle, - AccountID const owner, - beast::Journal j) +DIDDelete::deleteSLE(ApplyView& view, SLE::pointer sle, AccountID const owner, beast::Journal j) { // Remove object from owner directory if (!view.dirRemove(keylet::ownerDir(owner), (*sle)[sfOwnerNode], sle->key(), true)) @@ -71,10 +65,7 @@ DIDDelete::doApply() } void -DIDDelete::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +DIDDelete::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/did/DIDSet.cpp b/src/libxrpl/tx/transactors/did/DIDSet.cpp index a930c3c754..1392581bb0 100644 --- a/src/libxrpl/tx/transactors/did/DIDSet.cpp +++ b/src/libxrpl/tx/transactors/did/DIDSet.cpp @@ -63,7 +63,7 @@ DIDSet::preflight(PreflightContext const& ctx) } static TER -addSLE(ApplyContext& ctx, std::shared_ptr const& sle, AccountID const& owner) +addSLE(ApplyContext& ctx, SLE::ref sle, AccountID const& owner) { auto const sleAccount = ctx.view().peek(keylet::account(owner)); if (!sleAccount) @@ -150,10 +150,7 @@ DIDSet::doApply() } void -DIDSet::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +DIDSet::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp index 123f83a1a6..b8f4604d73 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp @@ -23,7 +23,6 @@ #include #include -#include #include namespace xrpl { @@ -220,10 +219,7 @@ EscrowCancel::doApply() } void -EscrowCancel::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +EscrowCancel::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index 4de302db3e..0a12e2d1bc 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -541,10 +541,7 @@ EscrowCreate::doApply() } void -EscrowCreate::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +EscrowCreate::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp index 13bd4b1682..4cda867b48 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp @@ -30,7 +30,6 @@ #include #include -#include #include #include @@ -401,10 +400,7 @@ EscrowFinish::doApply() } void -EscrowFinish::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +EscrowFinish::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp index 11095fdebe..0e1a4b3a3d 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp @@ -28,7 +28,6 @@ #include #include -#include #include #include @@ -377,10 +376,7 @@ LoanBrokerCoverClawback::doApply() } void -LoanBrokerCoverClawback::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +LoanBrokerCoverClawback::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp index e84f277f5b..537996ba57 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp @@ -16,8 +16,6 @@ #include #include -#include - namespace xrpl { bool @@ -181,10 +179,7 @@ LoanBrokerCoverDeposit::doApply() } void -LoanBrokerCoverDeposit::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +LoanBrokerCoverDeposit::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp index f4c95541f6..fea6f3b9cb 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp @@ -20,8 +20,6 @@ #include #include -#include - namespace xrpl { bool @@ -204,10 +202,7 @@ LoanBrokerCoverWithdraw::doApply() } void -LoanBrokerCoverWithdraw::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +LoanBrokerCoverWithdraw::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp index 6b77914370..f3c000bf0b 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp @@ -18,8 +18,6 @@ #include #include -#include - namespace xrpl { bool @@ -206,10 +204,7 @@ LoanBrokerDelete::doApply() } void -LoanBrokerDelete::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +LoanBrokerDelete::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp index 6e3d2ad0a8..2dc003eb7f 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp @@ -279,10 +279,7 @@ LoanBrokerSet::doApply() } void -LoanBrokerSet::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +LoanBrokerSet::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/lending/LoanDelete.cpp b/src/libxrpl/tx/transactors/lending/LoanDelete.cpp index 813700bd7a..d4ec92a9fb 100644 --- a/src/libxrpl/tx/transactors/lending/LoanDelete.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanDelete.cpp @@ -16,8 +16,6 @@ #include #include -#include - namespace xrpl { bool @@ -142,10 +140,7 @@ LoanDelete::doApply() } void -LoanDelete::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +LoanDelete::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/lending/LoanManage.cpp b/src/libxrpl/tx/transactors/lending/LoanManage.cpp index d3165006b5..2b5c9d25f6 100644 --- a/src/libxrpl/tx/transactors/lending/LoanManage.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanManage.cpp @@ -26,8 +26,6 @@ #include #include -#include - namespace xrpl { bool @@ -435,10 +433,7 @@ LoanManage::doApply() } void -LoanManage::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +LoanManage::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/lending/LoanPay.cpp b/src/libxrpl/tx/transactors/lending/LoanPay.cpp index 65220573de..a0a1479bdb 100644 --- a/src/libxrpl/tx/transactors/lending/LoanPay.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanPay.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include namespace xrpl { @@ -831,10 +830,7 @@ LoanPay::doApply() } void -LoanPay::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +LoanPay::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/lending/LoanSet.cpp b/src/libxrpl/tx/transactors/lending/LoanSet.cpp index 561ab6b2aa..573f700f51 100644 --- a/src/libxrpl/tx/transactors/lending/LoanSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanSet.cpp @@ -656,10 +656,7 @@ LoanSet::doApply() } void -LoanSet::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +LoanSet::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp index b80d282d70..6a82a15044 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp @@ -21,7 +21,6 @@ #include #include -#include #include #include @@ -55,7 +54,7 @@ TER NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx) { auto const checkOffer = - [&ctx](std::optional id) -> std::pair, TER> { + [&ctx](std::optional id) -> std::pair { if (id) { if (id->isZero()) @@ -403,7 +402,7 @@ NFTokenAcceptOffer::transferNFToken( } TER -NFTokenAcceptOffer::acceptOffer(std::shared_ptr const& offer) +NFTokenAcceptOffer::acceptOffer(SLE::ref offer) { bool const isSell = offer->isFlag(lsfSellNFToken); AccountID const owner = (*offer)[sfOwner]; @@ -441,7 +440,7 @@ TER NFTokenAcceptOffer::doApply() { auto const loadToken = [this](std::optional const& id) { - std::shared_ptr sle; + SLE::pointer sle; if (id) sle = view().peek(keylet::nftoffer(*id)); return sle; @@ -456,8 +455,7 @@ NFTokenAcceptOffer::doApply() { bool foundExpired = false; - auto const deleteOfferIfExpired = - [this, &foundExpired](std::shared_ptr const& offer) -> TER { + auto const deleteOfferIfExpired = [this, &foundExpired](SLE::ref offer) -> TER { if (offer && hasExpired(view(), (*offer)[~sfExpiration])) { JLOG(j_.trace()) << "Offer is expired, deleting: " << offer->key(); @@ -569,10 +567,7 @@ NFTokenAcceptOffer::doApply() } void -NFTokenAcceptOffer::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +NFTokenAcceptOffer::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp b/src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp index 871a8e706a..d33b1af8c2 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp @@ -13,8 +13,6 @@ #include #include -#include - namespace xrpl { NotTEC @@ -95,10 +93,7 @@ NFTokenBurn::doApply() } void -NFTokenBurn::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +NFTokenBurn::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp index 924dc49269..1614f90202 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp @@ -16,8 +16,6 @@ #include #include -#include - namespace xrpl { NotTEC @@ -98,10 +96,7 @@ NFTokenCancelOffer::doApply() } void -NFTokenCancelOffer::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +NFTokenCancelOffer::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp index 9c7fe7d5ef..1948f3803d 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp @@ -14,8 +14,6 @@ #include #include -#include - namespace xrpl { std::uint32_t @@ -91,10 +89,7 @@ NFTokenCreateOffer::doApply() } void -NFTokenCreateOffer::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +NFTokenCreateOffer::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp index e7faa30df8..bd7413d50b 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp @@ -27,7 +27,6 @@ #include #include #include // IWYU pragma: keep -#include #include namespace xrpl { @@ -344,10 +343,7 @@ NFTokenMint::doApply() } void -NFTokenMint::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +NFTokenMint::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenModify.cpp b/src/libxrpl/tx/transactors/nft/NFTokenModify.cpp index 7e3eeeefee..32ba362ded 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenModify.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenModify.cpp @@ -14,8 +14,6 @@ #include #include -#include - namespace xrpl { NotTEC @@ -69,10 +67,7 @@ NFTokenModify::doApply() } void -NFTokenModify::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +NFTokenModify::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp b/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp index 839b2b4e24..9a26816155 100644 --- a/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp +++ b/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp @@ -13,8 +13,6 @@ #include #include -#include - namespace xrpl { NotTEC @@ -51,7 +49,7 @@ OracleDelete::preclaim(PreclaimContext const& ctx) TER OracleDelete::deleteOracle( ApplyView& view, - std::shared_ptr const& sle, + SLE::ref sle, AccountID const& account, beast::Journal j) { @@ -89,10 +87,7 @@ OracleDelete::doApply() } void -OracleDelete::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +OracleDelete::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/oracle/OracleSet.cpp b/src/libxrpl/tx/transactors/oracle/OracleSet.cpp index 66ce95f46c..12d826e54d 100644 --- a/src/libxrpl/tx/transactors/oracle/OracleSet.cpp +++ b/src/libxrpl/tx/transactors/oracle/OracleSet.cpp @@ -328,10 +328,7 @@ OracleSet::doApply() } void -OracleSet::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +OracleSet::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp index 8b4ad2f025..7e950dc743 100644 --- a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp +++ b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp @@ -298,10 +298,7 @@ DepositPreauth::removeFromLedger(ApplyView& view, uint256 const& preauthIndex, b } void -DepositPreauth::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +DepositPreauth::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/payment/Payment.cpp b/src/libxrpl/tx/transactors/payment/Payment.cpp index d7ad5d3b3c..805ebe3684 100644 --- a/src/libxrpl/tx/transactors/payment/Payment.cpp +++ b/src/libxrpl/tx/transactors/payment/Payment.cpp @@ -679,10 +679,7 @@ Payment::doApply() } void -Payment::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +Payment::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp index cc99b8f62d..b1fe5e24bc 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp @@ -23,7 +23,6 @@ #include #include -#include #include namespace xrpl { @@ -200,10 +199,7 @@ PaymentChannelClaim::doApply() } void -PaymentChannelClaim::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +PaymentChannelClaim::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp index 7ce25b6d15..63dbe01944 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp @@ -185,10 +185,7 @@ PaymentChannelCreate::doApply() } void -PaymentChannelCreate::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +PaymentChannelCreate::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp index 41906aa3da..bcb8a91c96 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp @@ -18,8 +18,6 @@ #include #include -#include - namespace xrpl { TxConsequences @@ -107,10 +105,7 @@ PaymentChannelFund::doApply() } void -PaymentChannelFund::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +PaymentChannelFund::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp index e36a0eb584..7eb3f282b9 100644 --- a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp +++ b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp @@ -12,8 +12,6 @@ #include #include -#include - namespace xrpl { NotTEC @@ -74,10 +72,7 @@ PermissionedDomainDelete::doApply() } void -PermissionedDomainDelete::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +PermissionedDomainDelete::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp index ff4019ec59..0e71ceada1 100644 --- a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp +++ b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp @@ -133,10 +133,7 @@ PermissionedDomainSet::doApply() } void -PermissionedDomainSet::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +PermissionedDomainSet::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/system/Batch.cpp b/src/libxrpl/tx/transactors/system/Batch.cpp index b9440e3273..64a62ac273 100644 --- a/src/libxrpl/tx/transactors/system/Batch.cpp +++ b/src/libxrpl/tx/transactors/system/Batch.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -518,10 +517,7 @@ Batch::doApply() } void -Batch::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +Batch::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/system/Change.cpp b/src/libxrpl/tx/transactors/system/Change.cpp index 92a06fd807..38fee5bf92 100644 --- a/src/libxrpl/tx/transactors/system/Change.cpp +++ b/src/libxrpl/tx/transactors/system/Change.cpp @@ -409,10 +409,7 @@ Change::applyUNLModify() } void -Change::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +Change::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp b/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp index 222e5dce7a..65c32b788c 100644 --- a/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp +++ b/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp @@ -16,7 +16,6 @@ #include #include -#include #include namespace xrpl { @@ -153,10 +152,7 @@ LedgerStateFix::doApply() } void -LedgerStateFix::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +LedgerStateFix::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/system/TicketCreate.cpp b/src/libxrpl/tx/transactors/system/TicketCreate.cpp index f442ac223b..5be00fe76c 100644 --- a/src/libxrpl/tx/transactors/system/TicketCreate.cpp +++ b/src/libxrpl/tx/transactors/system/TicketCreate.cpp @@ -135,10 +135,7 @@ TicketCreate::doApply() } void -TicketCreate::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +TicketCreate::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/token/Clawback.cpp b/src/libxrpl/tx/transactors/token/Clawback.cpp index 8532f97995..06132c1c97 100644 --- a/src/libxrpl/tx/transactors/token/Clawback.cpp +++ b/src/libxrpl/tx/transactors/token/Clawback.cpp @@ -23,7 +23,6 @@ #include #include -#include #include namespace xrpl { @@ -275,10 +274,7 @@ Clawback::doApply() } void -Clawback::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +Clawback::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp index 9b0c8d2b56..332d160cac 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp @@ -15,8 +15,6 @@ #include #include -#include - namespace xrpl { std::uint32_t @@ -48,7 +46,7 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx) // `holderID` is NOT used if (!holderID) { - std::shared_ptr const sleMpt = + SLE::const_pointer const sleMpt = ctx.view.read(keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], accountID)); // There is an edge case where all holders have zero balance, issuance @@ -156,10 +154,7 @@ MPTokenAuthorize::doApply() } void -MPTokenAuthorize::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +MPTokenAuthorize::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp index 64d0b01f5e..94a5ca848d 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp @@ -195,10 +195,7 @@ MPTokenIssuanceCreate::doApply() } void -MPTokenIssuanceCreate::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +MPTokenIssuanceCreate::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp index 46811508b7..1029c25813 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp @@ -9,8 +9,6 @@ #include #include -#include - namespace xrpl { NotTEC @@ -59,10 +57,7 @@ MPTokenIssuanceDestroy::doApply() } void -MPTokenIssuanceDestroy::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +MPTokenIssuanceDestroy::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp index d8b62acf3f..9b25531161 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include namespace xrpl { @@ -270,7 +269,7 @@ MPTokenIssuanceSet::doApply() auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID]; auto const holderID = ctx_.tx[~sfHolder]; auto const domainID = ctx_.tx[~sfDomainID]; - std::shared_ptr sle; + SLE::pointer sle; if (holderID) { @@ -373,10 +372,7 @@ MPTokenIssuanceSet::doApply() } void -MPTokenIssuanceSet::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +MPTokenIssuanceSet::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/token/TrustSet.cpp b/src/libxrpl/tx/transactors/token/TrustSet.cpp index a8ea786347..1d2bc96693 100644 --- a/src/libxrpl/tx/transactors/token/TrustSet.cpp +++ b/src/libxrpl/tx/transactors/token/TrustSet.cpp @@ -27,7 +27,6 @@ #include #include -#include #include namespace { @@ -669,10 +668,7 @@ TrustSet::doApply() } void -TrustSet::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +TrustSet::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp index b5b2c1f384..eb12905467 100644 --- a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp @@ -26,7 +26,6 @@ #include #include -#include #include #include #include @@ -61,7 +60,7 @@ VaultClawback::preflight(PreflightContext const& ctx) [[nodiscard]] STAmount clawbackAmount( - std::shared_ptr const& vault, + SLE::const_ref vault, std::optional const& maybeAmount, AccountID const& account) { @@ -221,8 +220,8 @@ VaultClawback::preclaim(PreclaimContext const& ctx) Expected, TER> VaultClawback::assetsToClawback( - std::shared_ptr const& vault, - std::shared_ptr const& sleShareIssuance, + SLE::ref vault, + SLE::const_ref sleShareIssuance, AccountID const& holder, STAmount const& clawbackAmount) { @@ -452,10 +451,7 @@ VaultClawback::doApply() } void -VaultClawback::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +VaultClawback::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp index 7490f44619..4163753014 100644 --- a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp @@ -263,10 +263,7 @@ VaultCreate::doApply() } void -VaultCreate::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +VaultCreate::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/vault/VaultDelete.cpp b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp index 438896e7ed..030a7e971c 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDelete.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp @@ -17,8 +17,6 @@ #include #include -#include - namespace xrpl { NotTEC @@ -213,10 +211,7 @@ VaultDelete::doApply() } void -VaultDelete::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +VaultDelete::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp index 50d165a2ba..c08d1e957c 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp @@ -23,7 +23,6 @@ #include #include -#include #include namespace xrpl { @@ -348,10 +347,7 @@ VaultDeposit::doApply() } void -VaultDeposit::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +VaultDeposit::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/vault/VaultSet.cpp b/src/libxrpl/tx/transactors/vault/VaultSet.cpp index f384fd3fb1..6d0ade6e52 100644 --- a/src/libxrpl/tx/transactors/vault/VaultSet.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultSet.cpp @@ -15,8 +15,6 @@ #include #include -#include - namespace xrpl { bool @@ -180,10 +178,7 @@ VaultSet::doApply() } void -VaultSet::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +VaultSet::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp index c52d94ac0b..cfcf79fdba 100644 --- a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp @@ -23,7 +23,6 @@ #include #include -#include #include namespace xrpl { @@ -368,10 +367,7 @@ VaultWithdraw::doApply() } void -VaultWithdraw::visitInvariantEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) +VaultWithdraw::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) { // No transaction-specific invariants yet (future work). } diff --git a/src/test/app/CheckMPT_test.cpp b/src/test/app/CheckMPT_test.cpp index 1ca24051dd..861a115fc9 100644 --- a/src/test/app/CheckMPT_test.cpp +++ b/src/test/app/CheckMPT_test.cpp @@ -42,7 +42,6 @@ #include #include #include -#include #include #include #include @@ -54,11 +53,11 @@ namespace xrpl { class CheckMPT_test : public beast::unit_test::Suite { // Helper function that returns the Checks on an account. - static std::vector> + static std::vector checksOnAccount(test::jtx::Env& env, test::jtx::Account account) { - std::vector> result; - forEachItem(*env.current(), account, [&result](std::shared_ptr const& sle) { + std::vector result; + forEachItem(*env.current(), account, [&result](SLE::const_ref sle) { if (sle && sle->getType() == ltCHECK) result.push_back(sle); }); diff --git a/src/test/app/Check_test.cpp b/src/test/app/Check_test.cpp index 1d5861136c..9b814a24b1 100644 --- a/src/test/app/Check_test.cpp +++ b/src/test/app/Check_test.cpp @@ -43,7 +43,6 @@ #include #include -#include #include #include @@ -58,11 +57,11 @@ class Check_test : public beast::unit_test::Suite } // Helper function that returns the Checks on an account. - static std::vector> + static std::vector checksOnAccount(test::jtx::Env& env, test::jtx::Account account) { - std::vector> result; - forEachItem(*env.current(), account, [&result](std::shared_ptr const& sle) { + std::vector result; + forEachItem(*env.current(), account, [&result](SLE::const_ref sle) { if (sle && sle->getType() == ltCHECK) result.push_back(sle); }); diff --git a/src/test/app/FlowMPT_test.cpp b/src/test/app/FlowMPT_test.cpp index e1d6f512d2..2e224a7eb1 100644 --- a/src/test/app/FlowMPT_test.cpp +++ b/src/test/app/FlowMPT_test.cpp @@ -37,7 +37,6 @@ #include #include -#include #include #include #include @@ -433,7 +432,7 @@ struct FlowMPT_test : public beast::unit_test::Suite env.require(Balance(bob, eur(999))); // Show that bob's USD offer is now a blocker. - std::shared_ptr const usdOffer = env.le(bobUsdOffer); + SLE::const_pointer const usdOffer = env.le(bobUsdOffer); if (BEAST_EXPECT(usdOffer)) { std::uint64_t const bookRate = [&usdOffer]() { @@ -730,11 +729,11 @@ struct FlowMPT_test : public beast::unit_test::Suite } // Helper function that returns the Offers on an account. - static std::vector> + static std::vector offersOnAccount(jtx::Env& env, jtx::Account account) { - std::vector> result; - forEachItem(*env.current(), account, [&result](std::shared_ptr const& sle) { + std::vector result; + forEachItem(*env.current(), account, [&result](SLE::const_ref sle) { if (sle->getType() == ltOFFER) result.push_back(sle); }); @@ -1792,7 +1791,7 @@ struct FlowMPT_test : public beast::unit_test::Suite // but OutstandingAmount is 300USD because gw's sell offer is balanced out by // gw's buy offer. //*maxAmt sendMax limitEUR expectEUR outstandingUSD edBuy danBuy bobSell gwXRP offersGw lastGw - { 400, 400, 400, 400, 300, 100, 100, 100, 1100, 0, false}, + { .maxAmt=400, .sendMax=400, .dstTrustLimit=400, .dstExpectEUR=400, .outstandingUSD=300, .expEdBuyUSD=100, .expDanBuyUSD=100, .expBobSellUSD=100, .expGwXRP=1100, .expOffersGw=0, .lastGwBuyUSD=false}, // Sell USD: alice, carol, bob, gw are consumed. // Buy USD: john, gw, dan, ed (partially) are consumed. // gw's sell USD is partially consumed because there is available balance (50USD). @@ -1801,32 +1800,32 @@ struct FlowMPT_test : public beast::unit_test::Suite // gw's offer is removed from the order book because it's partially consumed and // the remaining offer is unfunded. //*maxAmt sendMax limitEUR expectEUR outstandingUSD edBuy danBuy bobSell gwXRP offersGw lastGw - { 350, 400, 400, 350, 250, 50, 100, 100, 1050, 0, false}, + { .maxAmt=350, .sendMax=400, .dstTrustLimit=400, .dstExpectEUR=350, .outstandingUSD=250, .expEdBuyUSD=50, .expDanBuyUSD=100, .expBobSellUSD=100, .expGwXRP=1050, .expOffersGw=0, .lastGwBuyUSD=false}, // Sell USD: alice, carol, bob are consumed; gw's is unfunded // since OutstandingAmount is initially at MaximumAmount. // Buy USD: john, gw, dan are consumed; ed's remains on the order // book since 300USD is the sell limit. //*maxAmt sendMax limitEUR expectEUR outstandingUSD edBuy danBuy bobSell gwXRP offersGw lastGw - { 300, 400, 400, 300, 200, 0, 100, 100, 1000, 0, false}, + { .maxAmt=300, .sendMax=400, .dstTrustLimit=400, .dstExpectEUR=300, .outstandingUSD=200, .expEdBuyUSD=0, .expDanBuyUSD=100, .expBobSellUSD=100, .expGwXRP=1000, .expOffersGw=0, .lastGwBuyUSD=false}, // Same as above. bill's trustline limit sets the output to 300USD. //*maxAmt sendMax limitEUR expectEUR outstandingUSD edBuy danBuy bobSell gwXRP offersGw lastGw - { 300, 400, 300, 300, 200, 0, 100, 100, 1000, 0, false}, + { .maxAmt=300, .sendMax=400, .dstTrustLimit=300, .dstExpectEUR=300, .outstandingUSD=200, .expEdBuyUSD=0, .expDanBuyUSD=100, .expBobSellUSD=100, .expGwXRP=1000, .expOffersGw=0, .lastGwBuyUSD=false}, // Sell USD: alice, carol, bob are consumed; gw's removed from // the order book since it's unfunded. // Buy USD: john, gw, dan are consumed; ed's remains on the order // book since 300USD is the limit. //*maxAmt sendMax limitEUR expectEUR outstandingUSD edBuy danBuy bobSell gwXRP offersGw lastGw - { 300, 400, 300, 300, 200, 0, 100, 100, 1000, 0, true}, + { .maxAmt=300, .sendMax=400, .dstTrustLimit=300, .dstExpectEUR=300, .outstandingUSD=200, .expEdBuyUSD=0, .expDanBuyUSD=100, .expBobSellUSD=100, .expGwXRP=1000, .expOffersGw=0, .lastGwBuyUSD=true}, // Sell USD: alice, carol are consumed; gw's removed from // the order book in rev pass since it's unfunded; bob's // remains on the order book. // Buy USD: john, gw; ed's, dan's remains on the order // book since 300USD is the limit. //*maxAmt sendMax limitEUR expectEUR outstandingUSD edBuy danBuy bobSell gwXRP offersGw lastGw - { 300, 200, 300, 200, 200, 0, 0, 0, 1000, 0, false}, + { .maxAmt=300, .sendMax=200, .dstTrustLimit=300, .dstExpectEUR=200, .outstandingUSD=200, .expEdBuyUSD=0, .expDanBuyUSD=0, .expBobSellUSD=0, .expGwXRP=1000, .expOffersGw=0, .lastGwBuyUSD=false}, // Same as three tests above since limited by buy 300USD (gw offer is unfunded) //*maxAmt sendMax limitEUR expectEUR outstandingUSD edBuy danBuy bobSell gwXRP offersGw lastGw - { 300, 380, 400, 300, 200, 0, 100, 100, 1000, 0, false}, + { .maxAmt=300, .sendMax=380, .dstTrustLimit=400, .dstExpectEUR=300, .outstandingUSD=200, .expEdBuyUSD=0, .expDanBuyUSD=100, .expBobSellUSD=100, .expGwXRP=1000, .expOffersGw=0, .lastGwBuyUSD=false}, }; // clang-format on for (auto const& t : tests) @@ -1912,26 +1911,26 @@ struct FlowMPT_test : public beast::unit_test::Suite // Gw gets 300USD from alice; carol and bob buy 200USD, // therefore OutstandingAmount is 200. //*maxAmt sendMax gwOffer dstXRP outstandingUSD bobBuy gwXRP offersGw lastGw - { 300, 300, 100, 1300, 200, 100, 900, 0, false}, + { .maxAmt=300, .sendMax=300, .gwOffer=100, .dstExpectXRP=1300, .outstandingUSD=200, .expBobBuyUSD=100, .expGwXRP=900, .expOffersGw=0, .lastGwBuyUSD=false}, // Same as above. Gw offer location in the order book doesn't matter //*maxAmt sendMax gwOffer dstXRP outstandingUSD bobBuy gwXRP offersGw lastGw - { 300, 300, 100, 1300, 200, 100, 900, 0, true}, + { .maxAmt=300, .sendMax=300, .gwOffer=100, .dstExpectXRP=1300, .outstandingUSD=200, .expBobBuyUSD=100, .expGwXRP=900, .expOffersGw=0, .lastGwBuyUSD=true}, // Buy USD: carol, gw are consumed. bob's offer remains on the order book. // Gw gets 300USD from alice; carol buys 100USD, // therefore OutstandingAmount is 100. //*maxAmt sendMax gwOffer dstXRP outstandingUSD bobBuy gwXRP offersGw lastGw - { 300, 300, 200, 1300, 100, 0, 800, 0, false}, + { .maxAmt=300, .sendMax=300, .gwOffer=200, .dstExpectXRP=1300, .outstandingUSD=100, .expBobBuyUSD=0, .expGwXRP=800, .expOffersGw=0, .lastGwBuyUSD=false}, // Buy USD: carol, bob are consumed; gw's is partially consumed (100/100) since it's last. // Gw gets 300USD from alice; carol and bob buy 200USD, // therefore OutstandingAmount is 200. //*maxAmt sendMax gwOffer dstXRP outstandingUSD bobBuy gwXRP offersGw lastGw - { 300, 300, 200, 1300, 200, 100, 900, 1, true}, + { .maxAmt=300, .sendMax=300, .gwOffer=200, .dstExpectXRP=1300, .outstandingUSD=200, .expBobBuyUSD=100, .expGwXRP=900, .expOffersGw=1, .lastGwBuyUSD=true}, // Buy USD: carol, bob are consumed; gw's is partially consumed (50/50) since it's last // and sendMax limits the output. // Gw gets 250USD from alice; carol and bob buy 200USD, alice has 50USD left, // therefore OutstandingAmount is 200. //*maxAmt sendMax gwOffer dstXRP outstandingUSD bobBuy gwXRP offersGw lastGw - { 300, 250, 200, 1250, 250, 100, 950, 1, true}, + { .maxAmt=300, .sendMax=250, .gwOffer=200, .dstExpectXRP=1250, .outstandingUSD=250, .expBobBuyUSD=100, .expGwXRP=950, .expOffersGw=1, .lastGwBuyUSD=true}, }; // clang-format on for (auto const& t : tests) @@ -2024,10 +2023,10 @@ struct FlowMPT_test : public beast::unit_test::Suite // Sell USD: carol, gw, bob are consumed. // ed buys 300USD from carol, gw, bob therefore OutstandingAmount is 300. //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw - { 300, 300, 0, 100, 300, 300, 700, 100, 1100, 0, false}, + { .maxAmt=300, .sendMax=300, .initDst=0, .gwOffer=100, .dstExpectUSD=300, .outstandingUSD=300, .expAliceXRP=700, .expBobSellUSD=100, .expGwXRP=1100, .expOffersGw=0, .lastGwBuyUSD=false}, // Same as above. Gw offer location in the order book doesn't matter //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw - { 300, 300, 0, 100, 300, 300, 700, 100, 1100, 0, true}, + { .maxAmt=300, .sendMax=300, .initDst=0, .gwOffer=100, .dstExpectUSD=300, .outstandingUSD=300, .expAliceXRP=700, .expBobSellUSD=100, .expGwXRP=1100, .expOffersGw=0, .lastGwBuyUSD=true}, // Sell USD: carol, bob are consumed, gw is partially consumed. // ed buys 200 from carol and bob and 50 from gw because gw can only issue 50 // (300(max) - 200(carol+bob) - 50(ed)). ed buys 250 from carol, gw, bob and has 50 initially, @@ -2035,33 +2034,33 @@ struct FlowMPT_test : public beast::unit_test::Suite // gw's offer is removed from the order book because it's partially consumed and the remaining // offer is unfunded. //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw - { 300, 300, 50, 100, 300, 300, 750, 100, 1050, 0, false}, + { .maxAmt=300, .sendMax=300, .initDst=50, .gwOffer=100, .dstExpectUSD=300, .outstandingUSD=300, .expAliceXRP=750, .expBobSellUSD=100, .expGwXRP=1050, .expOffersGw=0, .lastGwBuyUSD=false}, // Same as above. Gw offer location in the order book doesn't matter. //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw - { 300, 300, 50, 100, 300, 300, 750, 100, 1050, 0, true}, + { .maxAmt=300, .sendMax=300, .initDst=50, .gwOffer=100, .dstExpectUSD=300, .outstandingUSD=300, .expAliceXRP=750, .expBobSellUSD=100, .expGwXRP=1050, .expOffersGw=0, .lastGwBuyUSD=true}, // Same as above. Gw offer size doesn't matter. //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw - { 300, 300, 50, 200, 300, 300, 750, 100, 1050, 0, true}, + { .maxAmt=300, .sendMax=300, .initDst=50, .gwOffer=200, .dstExpectUSD=300, .outstandingUSD=300, .expAliceXRP=750, .expBobSellUSD=100, .expGwXRP=1050, .expOffersGw=0, .lastGwBuyUSD=true}, // Sell USD: carol, gw are consumed, bob is partially consumed. // ed buys 200 from carol and gw and 50 form bob because of sendMax limit. bob keeps 50, // therefore OutstandingAmount is 300. //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw - { 300, 250, 0, 100, 250, 300, 750, 50, 1100, 0, false}, + { .maxAmt=300, .sendMax=250, .initDst=0, .gwOffer=100, .dstExpectUSD=250, .outstandingUSD=300, .expAliceXRP=750, .expBobSellUSD=50, .expGwXRP=1100, .expOffersGw=0, .lastGwBuyUSD=false}, // Sell USD: carol, bob are consumed, gw is partially consumed because of sendMax limit. // ed buys 200 from carol and bob and 50 from gw. Therefore, OutstandingAmount is 250. // gw's offer remains on the order book because it's partially consumed and has more funds. //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw - { 300, 250, 0, 100, 250, 250, 750, 100, 1050, 1, true}, + { .maxAmt=300, .sendMax=250, .initDst=0, .gwOffer=100, .dstExpectUSD=250, .outstandingUSD=250, .expAliceXRP=750, .expBobSellUSD=100, .expGwXRP=1050, .expOffersGw=1, .lastGwBuyUSD=true}, // Sell USD: carol, bob are consumed, gw is partially consumed because of sendMax limit, also // there is only 50 available to issue. ed buys 200 from carol and bob and 50 from gw, plus // he has initially 50, therefore OutstandingAmount is 300. //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw - { 300, 250, 50, 100, 300, 300, 750, 100, 1050, 0, true}, + { .maxAmt=300, .sendMax=250, .initDst=50, .gwOffer=100, .dstExpectUSD=300, .outstandingUSD=300, .expAliceXRP=750, .expBobSellUSD=100, .expGwXRP=1050, .expOffersGw=0, .lastGwBuyUSD=true}, // Sell USD: carol, bob are consumed, gw is not consumed because there is not available funds // to issue. ed buys 200 from carol and bob and, plus he has initially 100, // therefore OutstandingAmount is 300. gw offer is removed because it's unfunded. //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw - { 300, 250, 100, 100, 300, 300, 800, 100, 1000, 0, true}, + { .maxAmt=300, .sendMax=250, .initDst=100, .gwOffer=100, .dstExpectUSD=300, .outstandingUSD=300, .expAliceXRP=800, .expBobSellUSD=100, .expGwXRP=1000, .expOffersGw=0, .lastGwBuyUSD=true}, }; // clang-format on for (auto const& t : tests) diff --git a/src/test/app/Flow_test.cpp b/src/test/app/Flow_test.cpp index c6f4cf59d1..5f56a0ceb1 100644 --- a/src/test/app/Flow_test.cpp +++ b/src/test/app/Flow_test.cpp @@ -43,7 +43,6 @@ #include #include -#include #include #include #include @@ -572,7 +571,7 @@ struct Flow_test : public beast::unit_test::Suite env.require(Balance(bob, eur(999))); // Show that bob's USD offer is now a blocker. - std::shared_ptr const usdOffer = env.le(bobUsdOffer); + SLE::const_pointer const usdOffer = env.le(bobUsdOffer); if (BEAST_EXPECT(usdOffer)) { std::uint64_t const bookRate = [&usdOffer]() { @@ -711,11 +710,11 @@ struct Flow_test : public beast::unit_test::Suite } // Helper function that returns the Offers on an account. - static std::vector> + static std::vector offersOnAccount(jtx::Env& env, jtx::Account account) { - std::vector> result; - forEachItem(*env.current(), account, [&result](std::shared_ptr const& sle) { + std::vector result; + forEachItem(*env.current(), account, [&result](SLE::const_ref sle) { if (sle->getType() == ltOFFER) result.push_back(sle); }); diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 432dccce61..3654036869 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -1277,7 +1277,7 @@ class Invariants_test : public beast::unit_test::Suite }); } - static std::shared_ptr + static SLE::pointer createPermissionedDomain( ApplyContext& ac, test::jtx::Account const& a1, diff --git a/src/test/app/OfferMPT_test.cpp b/src/test/app/OfferMPT_test.cpp index e9366f7c32..e0f2f4eab0 100644 --- a/src/test/app/OfferMPT_test.cpp +++ b/src/test/app/OfferMPT_test.cpp @@ -44,7 +44,6 @@ #include #include #include -#include #include #include #include @@ -721,11 +720,11 @@ public: } // Helper function that returns the Offers on an account. - static std::vector> + static std::vector offersOnAccount(jtx::Env& env, jtx::Account account) { - std::vector> result; - forEachItem(*env.current(), account, [&result](std::shared_ptr const& sle) { + std::vector result; + forEachItem(*env.current(), account, [&result](SLE::const_ref sle) { if (sle->getType() == ltOFFER) result.push_back(sle); }); @@ -3731,9 +3730,7 @@ public: auto const offerCount = std::distance( actorOffers.begin(), std::remove_if( - actorOffers.begin(), - actorOffers.end(), - [](std::shared_ptr& offer) { + actorOffers.begin(), actorOffers.end(), [](SLE::const_pointer& offer) { return (*offer)[sfTakerGets].signum() == 0; })); BEAST_EXPECT(offerCount == actor.offers); @@ -3903,9 +3900,7 @@ public: auto const offerCount = std::distance( actorOffers.begin(), std::remove_if( - actorOffers.begin(), - actorOffers.end(), - [](std::shared_ptr& offer) { + actorOffers.begin(), actorOffers.end(), [](SLE::const_pointer& offer) { return (*offer)[sfTakerGets].signum() == 0; })); BEAST_EXPECT(offerCount == actor.offers); @@ -4239,15 +4234,13 @@ public: } // Helper function that returns offers on an account sorted by sequence. - static std::vector> + static std::vector sortedOffersOnAccount(jtx::Env& env, jtx::Account const& acct) { - std::vector> offers{offersOnAccount(env, acct)}; - std::ranges::sort( - offers, - [](std::shared_ptr const& rhs, std::shared_ptr const& lhs) { - return (*rhs)[sfSequence] < (*lhs)[sfSequence]; - }); + std::vector offers{offersOnAccount(env, acct)}; + std::ranges::sort(offers, [](SLE::const_ref rhs, SLE::const_ref lhs) { + return (*rhs)[sfSequence] < (*lhs)[sfSequence]; + }); return offers; } @@ -4692,14 +4685,14 @@ public: // IOU/IOU, XRP/IOU, IOU/XRP offers have TickSize logic unchanged // IOU/MPT, MPT/IOU have TickSize logic applied to adjust IOU only std::vector const tests = { - {getIOU, getIOU, 10, 30}, - {getIOU, getXRP, 10, 30'000'000}, - {getXRP, getIOU, 10'000'000, 30}, - {getMPT, getXRP, 10'000'000, 30'000'000}, - {getXRP, getMPT, 10'000'000, 30'000'000}, - {getIOU, getMPT, 10, 30'000'000}, - {getMPT, getIOU, 10'000'000, 30}, - {getMPT, getMPT, 10'000'000, 30'000'000}}; + {.toAsset1 = getIOU, .toAsset2 = getIOU, .val1 = 10, .val2 = 30}, + {.toAsset1 = getIOU, .toAsset2 = getXRP, .val1 = 10, .val2 = 30'000'000}, + {.toAsset1 = getXRP, .toAsset2 = getIOU, .val1 = 10'000'000, .val2 = 30}, + {.toAsset1 = getMPT, .toAsset2 = getXRP, .val1 = 10'000'000, .val2 = 30'000'000}, + {.toAsset1 = getXRP, .toAsset2 = getMPT, .val1 = 10'000'000, .val2 = 30'000'000}, + {.toAsset1 = getIOU, .toAsset2 = getMPT, .val1 = 10, .val2 = 30'000'000}, + {.toAsset1 = getMPT, .toAsset2 = getIOU, .val1 = 10'000'000, .val2 = 30}, + {.toAsset1 = getMPT, .toAsset2 = getMPT, .val1 = 10'000'000, .val2 = 30'000'000}}; for (TestInfo const& t : tests) { Env env{*this, features}; @@ -4731,7 +4724,7 @@ public: env(offer(alice, xts(t.val2), xxx(t.val1)), Json(jss::Flags, tfSell)); std::map> offers; - forEachItem(*env.current(), alice, [&](std::shared_ptr const& sle) { + forEachItem(*env.current(), alice, [&](SLE::const_ref sle) { if (sle->getType() == ltOFFER) { offers.emplace( diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index 811a18dda5..83c58884e0 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -49,7 +49,6 @@ #include #include #include -#include #include #include #include @@ -755,11 +754,11 @@ public: } // Helper function that returns the Offers on an account. - static std::vector> + static std::vector offersOnAccount(jtx::Env& env, jtx::Account const& account) { - std::vector> result; - forEachItem(*env.current(), account, [&result](std::shared_ptr const& sle) { + std::vector result; + forEachItem(*env.current(), account, [&result](SLE::const_ref sle) { if (sle->getType() == ltOFFER) result.push_back(sle); }); @@ -3928,10 +3927,10 @@ public: // clang-format off TestData const tests[]{ // btcStart --------------------- actor[0] --------------------- -------------------- actor[1] ------------------- - {.self=0, .leg0=0, .leg1=1, .btcStart=btc(20), .actors={{"ann", 0, drops(3900000'000000 - (4 * baseFee)), btc(20.0), usd(3000)}, {"abe", 0, drops(4100000'000000 - (3 * baseFee)), btc( 0), usd(750)}}}, // no BTC xfer fee - {.self=0, .leg0=1, .leg1=0, .btcStart=btc(20), .actors={{"bev", 0, drops(4100000'000000 - (4 * baseFee)), btc( 7.5), usd(2000)}, {"bob", 0, drops(3900000'000000 - (3 * baseFee)), btc(10), usd( 0)}}}, // no USD xfer fee - {.self=0, .leg0=0, .leg1=0, .btcStart=btc(20), .actors={{"cam", 0, drops(4000000'000000 - (5 * baseFee)), btc(20.0), usd(2000)} }}, // no xfer fee - {.self=0, .leg0=1, .leg1=0, .btcStart=btc( 5), .actors={{"deb", 1, drops(4040000'000000 - (4 * baseFee)), btc( 0.0), usd(2000)}, {"dan", 1, drops(3960000'000000 - (3 * baseFee)), btc( 4), usd( 0)}}}, // no USD xfer fee + {.self=0, .leg0=0, .leg1=1, .btcStart=btc(20), .actors={{.acct="ann", .offers=0, .xrp=drops(3900000'000000 - (4 * baseFee)), .btc=btc(20.0), .usd=usd(3000)}, {.acct="abe", .offers=0, .xrp=drops(4100000'000000 - (3 * baseFee)), .btc=btc( 0), .usd=usd(750)}}}, // no BTC xfer fee + {.self=0, .leg0=1, .leg1=0, .btcStart=btc(20), .actors={{.acct="bev", .offers=0, .xrp=drops(4100000'000000 - (4 * baseFee)), .btc=btc( 7.5), .usd=usd(2000)}, {.acct="bob", .offers=0, .xrp=drops(3900000'000000 - (3 * baseFee)), .btc=btc(10), .usd=usd( 0)}}}, // no USD xfer fee + {.self=0, .leg0=0, .leg1=0, .btcStart=btc(20), .actors={{.acct="cam", .offers=0, .xrp=drops(4000000'000000 - (5 * baseFee)), .btc=btc(20.0), .usd=usd(2000)} }}, // no xfer fee + {.self=0, .leg0=1, .leg1=0, .btcStart=btc( 5), .actors={{.acct="deb", .offers=1, .xrp=drops(4040000'000000 - (4 * baseFee)), .btc=btc( 0.0), .usd=usd(2000)}, {.acct="dan", .offers=1, .xrp=drops(3960000'000000 - (3 * baseFee)), .btc=btc( 4), .usd=usd( 0)}}}, // no USD xfer fee }; // clang-format on @@ -3980,7 +3979,7 @@ public: auto actorOffers = offersOnAccount(env, actor.acct); auto const offerCount = std::distance( actorOffers.begin(), - std::ranges::remove_if(actorOffers, [](std::shared_ptr& offer) { + std::ranges::remove_if(actorOffers, [](SLE::const_pointer& offer) { return (*offer)[sfTakerGets].signum() == 0; }).begin()); BEAST_EXPECT(offerCount == actor.offers); @@ -4076,8 +4075,8 @@ public: // clang-format off TestData const tests[]{ // btcStart ------------------- actor[0] -------------------- ------------------- actor[1] -------------------- - {.self=0, .leg0=0, .leg1=1, .btcStart=btc(5), .actors={{"gay", 1, drops(3950000'000000 - (4 * baseFee)), btc(5), usd(2500)}, {"gar", 1, drops(4050000'000000 - (3 * baseFee)), btc(0), usd(1375)}}}, // no BTC xfer fee - {.self=0, .leg0=0, .leg1=0, .btcStart=btc(5), .actors={{"hye", 2, drops(4000000'000000 - (5 * baseFee)), btc(5), usd(2000)} }} // no xfer fee + {.self=0, .leg0=0, .leg1=1, .btcStart=btc(5), .actors={{.acct="gay", .offers=1, .xrp=drops(3950000'000000 - (4 * baseFee)), .btc=btc(5), .usd=usd(2500)}, {.acct="gar", .offers=1, .xrp=drops(4050000'000000 - (3 * baseFee)), .btc=btc(0), .usd=usd(1375)}}}, // no BTC xfer fee + {.self=0, .leg0=0, .leg1=0, .btcStart=btc(5), .actors={{.acct="hye", .offers=2, .xrp=drops(4000000'000000 - (5 * baseFee)), .btc=btc(5), .usd=usd(2000)} }} // no xfer fee }; // clang-format on @@ -4126,7 +4125,7 @@ public: auto actorOffers = offersOnAccount(env, actor.acct); auto const offerCount = std::distance( actorOffers.begin(), - std::ranges::remove_if(actorOffers, [](std::shared_ptr& offer) { + std::ranges::remove_if(actorOffers, [](SLE::const_pointer& offer) { return (*offer)[sfTakerGets].signum() == 0; }).begin()); BEAST_EXPECT(offerCount == actor.offers); @@ -4641,7 +4640,7 @@ public: env(offer(alice, xts(30), xxx(10)), Json(jss::Flags, tfSell)); std::map> offers; - forEachItem(*env.current(), alice, [&](std::shared_ptr const& sle) { + forEachItem(*env.current(), alice, [&](SLE::const_ref sle) { if (sle->getType() == ltOFFER) { offers.emplace( @@ -4676,15 +4675,13 @@ public: } // Helper function that returns offers on an account sorted by sequence. - static std::vector> + static std::vector sortedOffersOnAccount(jtx::Env& env, jtx::Account const& acct) { - std::vector> offers{offersOnAccount(env, acct)}; - std::ranges::sort( - offers, - [](std::shared_ptr const& rhs, std::shared_ptr const& lhs) { - return (*rhs)[sfSequence] < (*lhs)[sfSequence]; - }); + std::vector offers{offersOnAccount(env, acct)}; + std::ranges::sort(offers, [](SLE::const_ref rhs, SLE::const_ref lhs) { + return (*rhs)[sfSequence] < (*lhs)[sfSequence]; + }); return offers; } diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index b81afa830e..0b4222ca48 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -45,7 +45,6 @@ #include #include #include -#include #include #include #include @@ -57,7 +56,7 @@ using namespace jtx::paychan; struct PayChan_test : public beast::unit_test::Suite { - static std::pair> + static std::pair channelKeyAndSle(ReadView const& view, jtx::Account const& account, jtx::Account const& dst) { auto const sle = view.read(keylet::account(account)); @@ -869,7 +868,7 @@ struct PayChan_test : public beast::unit_test::Suite env.close(); // Setup deposit authorization - env(deposit::authCredentials(bob, {{carol, credType}})); + env(deposit::authCredentials(bob, {{.issuer = carol, .credType = credType}})); env.close(); // Fail, credentials doesn’t belong to root account @@ -1665,9 +1664,8 @@ struct PayChan_test : public beast::unit_test::Suite auto const settleDelay = 100s; auto const pk = alice.pk(); - auto inOwnerDir = [](ReadView const& view, - Account const& acc, - std::shared_ptr const& chan) -> bool { + auto inOwnerDir = + [](ReadView const& view, Account const& acc, SLE::const_ref chan) -> bool { xrpl::Dir const ownerDir(view, keylet::ownerDir(acc.id())); // NOLINTNEXTLINE(modernize-use-ranges) return std::find(ownerDir.begin(), ownerDir.end(), chan) != ownerDir.end(); diff --git a/src/test/app/XChain_test.cpp b/src/test/app/XChain_test.cpp index 0198a36e96..de80444f2e 100644 --- a/src/test/app/XChain_test.cpp +++ b/src/test/app/XChain_test.cpp @@ -150,18 +150,18 @@ struct SEnv return env.current()->fees().base; } - std::shared_ptr + SLE::const_pointer account(jtx::Account const& account) { return env.le(account); } - std::shared_ptr + SLE::const_pointer bridge(json::Value const& jvb) { STXChainBridge const b(jvb); - auto tryGet = [&](STXChainBridge::ChainType ct) -> std::shared_ptr { + auto tryGet = [&](STXChainBridge::ChainType ct) -> SLE::const_pointer { if (auto r = env.le(keylet::bridge(b, ct))) { if ((*r)[sfXChainBridge] == b) @@ -186,13 +186,13 @@ struct SEnv return (*bridge(jvb))[sfXChainClaimID]; } - std::shared_ptr + SLE::const_pointer claimID(json::Value const& jvb, std::uint64_t seq) { return env.le(keylet::xChainClaimID(STXChainBridge(jvb), seq)); } - std::shared_ptr + SLE::const_pointer caClaimID(json::Value const& jvb, std::uint64_t seq) { return env.le(keylet::xChainCreateAccountClaimID(STXChainBridge(jvb), seq)); diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index 4618b36fde..3d813d993c 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -531,13 +531,13 @@ public: /** Return an account root. @return empty if the account does not exist. */ - [[nodiscard]] std::shared_ptr + [[nodiscard]] SLE::const_pointer le(Account const& account) const; /** Return a ledger entry. @return empty if the ledger entry does not exist */ - [[nodiscard]] std::shared_ptr + [[nodiscard]] SLE::const_pointer le(Keylet const& k) const; /** Create a JTx from parameters. */ diff --git a/src/test/jtx/PathSet.h b/src/test/jtx/PathSet.h index cab31ea540..a391adcb1b 100644 --- a/src/test/jtx/PathSet.h +++ b/src/test/jtx/PathSet.h @@ -18,7 +18,7 @@ countOffers( Asset const& takerGets) { size_t count = 0; - forEachItem(*env.current(), account, [&](std::shared_ptr const& sle) { + forEachItem(*env.current(), account, [&](SLE::const_ref sle) { if (sle->getType() == ltOFFER && sle->getFieldAmount(sfTakerPays).asset() == takerPays && sle->getFieldAmount(sfTakerGets).asset() == takerGets) ++count; @@ -34,7 +34,7 @@ countOffers( STAmount const& takerGets) { size_t count = 0; - forEachItem(*env.current(), account, [&](std::shared_ptr const& sle) { + forEachItem(*env.current(), account, [&](SLE::const_ref sle) { if (sle->getType() == ltOFFER && sle->getFieldAmount(sfTakerPays) == takerPays && sle->getFieldAmount(sfTakerGets) == takerGets) ++count; diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index 011ac2e58d..27c54d830b 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -356,7 +356,7 @@ checkVL(Slice const& result, std::string const& expected) [[nodiscard]] inline bool -checkVL(std::shared_ptr const& sle, SField const& field, std::string const& expected) +checkVL(SLE::const_ref sle, SField const& field, std::string const& expected) { return strHex(expected) == strHex(sle->getFieldVL(field)); } diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index 4b6955bafb..707e1338a7 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -280,13 +280,13 @@ Env::seq(Account const& account) const return sle->getFieldU32(sfSequence); } -std::shared_ptr +SLE::const_pointer Env::le(Account const& account) const { return le(keylet::account(account.id())); } -std::shared_ptr +SLE::const_pointer Env::le(Keylet const& k) const { return current()->read(k); diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index c784c074de..a8ec899c8a 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -402,7 +402,7 @@ expectOffers( { std::uint16_t cnt = 0; std::uint16_t matched = 0; - forEachItem(*env.current(), account, [&](std::shared_ptr const& sle) { + forEachItem(*env.current(), account, [&](SLE::const_ref sle) { if (!sle) return false; if (sle->getType() == ltOFFER) diff --git a/src/test/jtx/impl/owners.cpp b/src/test/jtx/impl/owners.cpp index cb2cd5af29..2ff93757f0 100644 --- a/src/test/jtx/impl/owners.cpp +++ b/src/test/jtx/impl/owners.cpp @@ -10,8 +10,6 @@ #include #include -#include - namespace xrpl { namespace detail { @@ -19,7 +17,7 @@ std::uint32_t ownedCountOf(ReadView const& view, AccountID const& id, LedgerEntryType type) { std::uint32_t count = 0; - forEachItem(view, id, [&count, type](std::shared_ptr const& sle) { + forEachItem(view, id, [&count, type](SLE::const_ref sle) { if (sle->getType() == type) ++count; }); diff --git a/src/test/ledger/View_test.cpp b/src/test/ledger/View_test.cpp index d1c7316588..b62061d38d 100644 --- a/src/test/ledger/View_test.cpp +++ b/src/test/ledger/View_test.cpp @@ -61,7 +61,7 @@ class View_test : public beast::unit_test::Suite } // Create SLE with key and payload - static std::shared_ptr + static SLE::pointer sle(std::uint64_t id, std::uint32_t seq = 1) { auto const le = std::make_shared(k(id)); @@ -79,7 +79,7 @@ class View_test : public beast::unit_test::Suite // Set payload on SLE static void - seq(std::shared_ptr const& le, std::uint32_t seq) + seq(SLE::ref le, std::uint32_t seq) { le->setFieldU32(sfSequence, seq); } diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 12c79b821c..54cf85ba35 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -4316,7 +4316,7 @@ NetworkOPsImp::getBookPage( bool bDone = false; bool bDirectAdvance = true; - std::shared_ptr sleOfferDir; + SLE::const_pointer sleOfferDir; uint256 offerIndex; unsigned int uBookEntry = 0; STAmount saDirRate; diff --git a/src/xrpld/app/misc/TxQ.h b/src/xrpld/app/misc/TxQ.h index ad689abed4..d3caec55cf 100644 --- a/src/xrpld/app/misc/TxQ.h +++ b/src/xrpld/app/misc/TxQ.h @@ -288,7 +288,7 @@ public: /** Return the next sequence that would go in the TxQ for an account. */ SeqProxy - nextQueuableSeq(std::shared_ptr const& sleAccount) const; + nextQueuableSeq(SLE::const_ref sleAccount) const; /** Returns fee metrics in reference fee level units. */ @@ -342,9 +342,7 @@ public: private: // Implementation for nextQueuableSeq(). The passed lock must be held. SeqProxy - nextQueuableSeqImpl( - std::shared_ptr const& sleAccount, - std::scoped_lock const&) const; + nextQueuableSeqImpl(SLE::const_ref sleAccount, std::scoped_lock const&) const; /** Track and use the fee escalation metrics of the @@ -782,7 +780,7 @@ private: STTx const&, ApplyFlags const, OpenView const&, - std::shared_ptr const& sleAccount, + SLE::const_ref sleAccount, AccountMap::iterator const&, std::optional const&, std::scoped_lock const& lock); diff --git a/src/xrpld/app/misc/detail/TxQ.cpp b/src/xrpld/app/misc/detail/TxQ.cpp index 0f70f17046..c12632875d 100644 --- a/src/xrpld/app/misc/detail/TxQ.cpp +++ b/src/xrpld/app/misc/detail/TxQ.cpp @@ -150,7 +150,7 @@ TxQ::FeeMetrics::update( // current size limit, use a limit that is // 90% of the way from max_element to the // current size limit. - return (txnsExpected_ * 9 + *iter) / 10; + return ((txnsExpected_ * 9) + *iter) / 10; }(); // Ledgers are processing in a timely manner, // so keep the limit high, but don't let it @@ -218,7 +218,7 @@ sumOfFirstSquares(std::size_t xIn) // in a ledger, this is the least of our problems. if (x >= (1 << 21)) return {false, std::numeric_limits::max()}; - return {true, (x * (x + 1) * (2 * x + 1)) / 6}; + return {true, (x * (x + 1) * ((2 * x) + 1)) / 6}; } // Unit tests for sumOfSquares() @@ -387,7 +387,7 @@ TxQ::canBeHeld( STTx const& tx, ApplyFlags const flags, OpenView const& view, - std::shared_ptr const& sleAccount, + SLE::const_ref sleAccount, AccountMap::iterator const& accountIter, std::optional const& replacementIter, std::scoped_lock const& lock) @@ -1576,7 +1576,7 @@ TxQ::accept(Application& app, OpenView& view) // // Acquires a lock and calls the implementation. SeqProxy -TxQ::nextQueuableSeq(std::shared_ptr const& sleAccount) const +TxQ::nextQueuableSeq(SLE::const_ref sleAccount) const { std::scoped_lock const lock(mutex_); return nextQueuableSeqImpl(sleAccount, lock); @@ -1589,9 +1589,7 @@ TxQ::nextQueuableSeq(std::shared_ptr const& sleAccount) const // sequence number, that is not used by a transaction in the queue, must // be found and returned. SeqProxy -TxQ::nextQueuableSeqImpl( - std::shared_ptr const& sleAccount, - std::scoped_lock const&) const +TxQ::nextQueuableSeqImpl(SLE::const_ref sleAccount, std::scoped_lock const&) const { // If the account is not in the ledger or a non-account was passed // then return zero. We have no idea. diff --git a/src/xrpld/consensus/DisputedTx.h b/src/xrpld/consensus/DisputedTx.h index 1c85a3537d..1c0c069f54 100644 --- a/src/xrpld/consensus/DisputedTx.h +++ b/src/xrpld/consensus/DisputedTx.h @@ -286,7 +286,7 @@ DisputedTx::updateVote(int percentTime, bool proposing, ConsensusPar if (proposing) // give ourselves full weight { // This is basically the percentage of nodes voting 'yes' (including us) - weight = (yays_ * 100 + (ourVote_ ? 100 : 0)) / (nays_ + yays_ + 1); + weight = ((yays_ * 100) + (ourVote_ ? 100 : 0)) / (nays_ + yays_ + 1); newPosition = weight > requiredPct; } diff --git a/src/xrpld/rpc/detail/AssetCache.cpp b/src/xrpld/rpc/detail/AssetCache.cpp index a0743a2303..0976290d4c 100644 --- a/src/xrpld/rpc/detail/AssetCache.cpp +++ b/src/xrpld/rpc/detail/AssetCache.cpp @@ -124,7 +124,7 @@ AssetCache::getMPTs(xrpl::AccountID const& account) std::vector mpts; // Get issued/authorized tokens - forEachItem(*ledger_, account, [&](std::shared_ptr const& sle) { + forEachItem(*ledger_, account, [&](SLE::const_ref sle) { if (sle->getType() == ltMPTOKEN_ISSUANCE) { auto const mptID = makeMptID(sle->getFieldU32(sfSequence), account); diff --git a/src/xrpld/rpc/detail/RPCHelpers.cpp b/src/xrpld/rpc/detail/RPCHelpers.cpp index d48057a0a8..7ab2468b75 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCHelpers.cpp @@ -38,7 +38,6 @@ #include #include #include -#include #include #include #include @@ -46,7 +45,7 @@ namespace xrpl::RPC { std::uint64_t -getStartHint(std::shared_ptr const& sle, AccountID const& accountID) +getStartHint(SLE::const_ref sle, AccountID const& accountID) { if (sle->getType() == ltRIPPLE_STATE) { @@ -67,10 +66,7 @@ getStartHint(std::shared_ptr const& sle, AccountID const& accountID) } bool -isRelatedToAccount( - ReadView const& ledger, - std::shared_ptr const& sle, - AccountID const& accountID) +isRelatedToAccount(ReadView const& ledger, SLE::const_ref sle, AccountID const& accountID) { if (sle->getType() == ltRIPPLE_STATE) { diff --git a/src/xrpld/rpc/detail/RPCHelpers.h b/src/xrpld/rpc/detail/RPCHelpers.h index 781db1b8a5..bbc101a072 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.h +++ b/src/xrpld/rpc/detail/RPCHelpers.h @@ -33,7 +33,7 @@ struct JsonContext; * @return A 64-bit unsigned integer representing the start hint for traversal. */ std::uint64_t -getStartHint(std::shared_ptr const& sle, AccountID const& accountID); +getStartHint(SLE::const_ref sle, AccountID const& accountID); /** * @brief Tests if a ledger entry (SLE) is owned by the specified account. @@ -47,10 +47,7 @@ getStartHint(std::shared_ptr const& sle, AccountID const& accountID); * @return true if the SLE is owned by the account, false otherwise. */ bool -isRelatedToAccount( - ReadView const& ledger, - std::shared_ptr const& sle, - AccountID const& accountID); +isRelatedToAccount(ReadView const& ledger, SLE::const_ref sle, AccountID const& accountID); /** * @brief Parses an array of account IDs from a JSON value. diff --git a/src/xrpld/rpc/detail/TransactionSign.cpp b/src/xrpld/rpc/detail/TransactionSign.cpp index dd0e78c178..86d895fa1b 100644 --- a/src/xrpld/rpc/detail/TransactionSign.cpp +++ b/src/xrpld/rpc/detail/TransactionSign.cpp @@ -165,7 +165,7 @@ public: static ErrorCodeI acctMatchesPubKey( - std::shared_ptr accountState, + SLE::const_pointer accountState, AccountID const& accountID, PublicKey const& publicKey) { @@ -519,7 +519,7 @@ transactionPreProcessImpl( if (!verify && !txJson.isMember(jss::Sequence)) return RPC::missingFieldError("tx_json.Sequence"); - std::shared_ptr sle; + SLE::const_pointer sle; if (verify) sle = app.getOpenLedger().current()->read(keylet::account(srcAddressID)); @@ -1222,8 +1222,7 @@ transactionSignFor( signForParams.validMultiSign(), "xrpl::RPC::transactionSignFor : valid multi-signature"); { - std::shared_ptr const accountState = - ledger->read(keylet::account(*signerAccountID)); + SLE::const_pointer const accountState = ledger->read(keylet::account(*signerAccountID)); // Make sure the account and secret belong together. auto const err = acctMatchesPubKey(accountState, *signerAccountID, signForParams.getPublicKey()); @@ -1310,7 +1309,7 @@ transactionSubmitMultiSigned( if (RPC::containsError(txJsonResult)) return std::move(txJsonResult); - std::shared_ptr const sle = ledger->read(keylet::account(srcAddressID)); + SLE::const_pointer const sle = ledger->read(keylet::account(srcAddressID)); if (!sle) { diff --git a/src/xrpld/rpc/detail/TrustLine.cpp b/src/xrpld/rpc/detail/TrustLine.cpp index f7293d0816..77a2b36d56 100644 --- a/src/xrpld/rpc/detail/TrustLine.cpp +++ b/src/xrpld/rpc/detail/TrustLine.cpp @@ -9,13 +9,12 @@ #include #include -#include #include #include namespace xrpl { -TrustLineBase::TrustLineBase(std::shared_ptr const& sle, AccountID const& viewAccount) +TrustLineBase::TrustLineBase(SLE::const_ref sle, AccountID const& viewAccount) : key_(sle->key()) , lowLimit_(sle->getFieldAmount(sfLowLimit)) , highLimit_(sle->getFieldAmount(sfHighLimit)) @@ -37,7 +36,7 @@ TrustLineBase::getJson(int) } std::optional -PathFindTrustLine::makeItem(AccountID const& accountID, std::shared_ptr const& sle) +PathFindTrustLine::makeItem(AccountID const& accountID, SLE::const_ref sle) { if (!sle || sle->getType() != ltRIPPLE_STATE) return {}; @@ -53,14 +52,11 @@ getTrustLineItems( LineDirection direction = LineDirection::Outgoing) { std::vector items; - forEachItem( - view, - accountID, - [&items, &accountID, &direction](std::shared_ptr const& sleCur) { - auto ret = T::makeItem(accountID, sleCur); - if (ret && (direction == LineDirection::Outgoing || !ret->getNoRipple())) - items.push_back(std::move(*ret)); - }); + forEachItem(view, accountID, [&items, &accountID, &direction](SLE::const_ref sleCur) { + auto ret = T::makeItem(accountID, sleCur); + if (ret && (direction == LineDirection::Outgoing || !ret->getNoRipple())) + items.push_back(std::move(*ret)); + }); // This list may be around for a while, so free up any unneeded // capacity items.shrink_to_fit(); @@ -78,7 +74,7 @@ PathFindTrustLine::getItems( return detail::getTrustLineItems(accountID, view, direction); } -RPCTrustLine::RPCTrustLine(std::shared_ptr const& sle, AccountID const& viewAccount) +RPCTrustLine::RPCTrustLine(SLE::const_ref sle, AccountID const& viewAccount) : TrustLineBase(sle, viewAccount) , lowQualityIn_(sle->getFieldU32(sfLowQualityIn)) , lowQualityOut_(sle->getFieldU32(sfLowQualityOut)) @@ -88,7 +84,7 @@ RPCTrustLine::RPCTrustLine(std::shared_ptr const& sle, AccountID cons } std::optional -RPCTrustLine::makeItem(AccountID const& accountID, std::shared_ptr const& sle) +RPCTrustLine::makeItem(AccountID const& accountID, SLE::const_ref sle) { if (!sle || sle->getType() != ltRIPPLE_STATE) return {}; diff --git a/src/xrpld/rpc/detail/TrustLine.h b/src/xrpld/rpc/detail/TrustLine.h index 72d4d44ae3..7a0a01d744 100644 --- a/src/xrpld/rpc/detail/TrustLine.h +++ b/src/xrpld/rpc/detail/TrustLine.h @@ -39,7 +39,7 @@ public: protected: // This class should not be instantiated directly. Use one of the derived // classes. - TrustLineBase(std::shared_ptr const& sle, AccountID const& viewAccount); + TrustLineBase(SLE::const_ref sle, AccountID const& viewAccount); ~TrustLineBase() = default; TrustLineBase(TrustLineBase const&) = default; @@ -175,7 +175,7 @@ public: PathFindTrustLine() = delete; static std::optional - makeItem(AccountID const& accountID, std::shared_ptr const& sle); + makeItem(AccountID const& accountID, SLE::const_ref sle); static std::vector getItems(AccountID const& accountID, ReadView const& view, LineDirection direction); @@ -190,7 +190,7 @@ class RPCTrustLine final : public TrustLineBase, public CountedObject const& sle, AccountID const& viewAccount); + RPCTrustLine(SLE::const_ref sle, AccountID const& viewAccount); [[nodiscard]] Rate const& getQualityIn() const @@ -205,7 +205,7 @@ public: } static std::optional - makeItem(AccountID const& accountID, std::shared_ptr const& sle); + makeItem(AccountID const& accountID, SLE::const_ref sle); static std::vector getItems(AccountID const& accountID, ReadView const& view); diff --git a/src/xrpld/rpc/handlers/account/AccountChannels.cpp b/src/xrpld/rpc/handlers/account/AccountChannels.cpp index 6d5876322c..8a5c7dc6e3 100644 --- a/src/xrpld/rpc/handlers/account/AccountChannels.cpp +++ b/src/xrpld/rpc/handlers/account/AccountChannels.cpp @@ -114,7 +114,7 @@ doAccountChannels(RPC::JsonContext& context) json::Value jsonChannels{json::ValueType::Array}; struct VisitData { - std::vector> items; + std::vector items; AccountID const& accountID; std::optional const& raDstAccount; }; @@ -170,8 +170,7 @@ doAccountChannels(RPC::JsonContext& context) startAfter, startHint, limit + 1, - [&visitData, &accountID, &count, &limit, &marker, &nextHint]( - std::shared_ptr const& sleCur) { + [&visitData, &accountID, &count, &limit, &marker, &nextHint](SLE::const_ref sleCur) { if (!sleCur) { // LCOV_EXCL_START diff --git a/src/xrpld/rpc/handlers/account/AccountLines.cpp b/src/xrpld/rpc/handlers/account/AccountLines.cpp index c60ce90201..e69f70ca5a 100644 --- a/src/xrpld/rpc/handlers/account/AccountLines.cpp +++ b/src/xrpld/rpc/handlers/account/AccountLines.cpp @@ -195,8 +195,7 @@ doAccountLines(RPC::JsonContext& context) startAfter, startHint, limit + 1, - [&visitData, &count, &marker, &limit, &nextHint]( - std::shared_ptr const& sleCur) { + [&visitData, &count, &marker, &limit, &nextHint](SLE::const_ref sleCur) { if (!sleCur) { // LCOV_EXCL_START diff --git a/src/xrpld/rpc/handlers/account/AccountOffers.cpp b/src/xrpld/rpc/handlers/account/AccountOffers.cpp index 85d9470b75..4829ff56b1 100644 --- a/src/xrpld/rpc/handlers/account/AccountOffers.cpp +++ b/src/xrpld/rpc/handlers/account/AccountOffers.cpp @@ -33,7 +33,7 @@ namespace xrpl { void -appendOfferJson(std::shared_ptr const& offer, json::Value& offers) +appendOfferJson(SLE::const_ref offer, json::Value& offers) { STAmount const dirRate = amountFromQuality(getQuality(offer->getFieldH256(sfBookDirectory))); json::Value& obj(offers.append(json::ValueType::Object)); @@ -87,7 +87,7 @@ doAccountOffers(RPC::JsonContext& context) return *err; json::Value& jsonOffers(result[jss::offers] = json::ValueType::Array); - std::vector> offers; + std::vector offers; uint256 startAfter = beast::kZero; std::uint64_t startHint = 0; @@ -138,8 +138,7 @@ doAccountOffers(RPC::JsonContext& context) startAfter, startHint, limit + 1, - [&offers, &count, &marker, &limit, &nextHint, &accountID]( - std::shared_ptr const& sle) { + [&offers, &count, &marker, &limit, &nextHint, &accountID](SLE::const_ref sle) { if (!sle) { // LCOV_EXCL_START diff --git a/src/xrpld/rpc/handlers/account/GatewayBalances.cpp b/src/xrpld/rpc/handlers/account/GatewayBalances.cpp index 146b9ead5c..bd1681172c 100644 --- a/src/xrpld/rpc/handlers/account/GatewayBalances.cpp +++ b/src/xrpld/rpc/handlers/account/GatewayBalances.cpp @@ -144,7 +144,7 @@ doGatewayBalances(RPC::JsonContext& context) // Traverse the cold wallet's trust lines { - forEachItem(*ledger, accountID, [&](std::shared_ptr const& sle) { + forEachItem(*ledger, accountID, [&](SLE::const_ref sle) { if (sle->getType() == ltESCROW) { auto const& escrow = sle->getFieldAmount(sfAmount); diff --git a/src/xrpld/rpc/handlers/account/NoRippleCheck.cpp b/src/xrpld/rpc/handlers/account/NoRippleCheck.cpp index bb48d3ebd5..d8bb65aba9 100644 --- a/src/xrpld/rpc/handlers/account/NoRippleCheck.cpp +++ b/src/xrpld/rpc/handlers/account/NoRippleCheck.cpp @@ -137,52 +137,50 @@ doNoRippleCheck(RPC::JsonContext& context) } } - forEachItemAfter( - *ledger, accountID, uint256(), 0, limit, [&](std::shared_ptr const& ownedItem) { - if (ownedItem->getType() == ltRIPPLE_STATE) + forEachItemAfter(*ledger, accountID, uint256(), 0, limit, [&](SLE::const_ref ownedItem) { + if (ownedItem->getType() == ltRIPPLE_STATE) + { + bool const bLow = accountID == ownedItem->getFieldAmount(sfLowLimit).getIssuer(); + + bool const bNoRipple = ownedItem->isFlag(bLow ? lsfLowNoRipple : lsfHighNoRipple); + + std::string problem; + bool needFix = false; + if (bNoRipple && roleGateway) { - bool const bLow = accountID == ownedItem->getFieldAmount(sfLowLimit).getIssuer(); - - bool const bNoRipple = ownedItem->isFlag(bLow ? lsfLowNoRipple : lsfHighNoRipple); - - std::string problem; - bool needFix = false; - if (bNoRipple && roleGateway) - { - problem = "You should clear the no ripple flag on your "; - needFix = true; - } - else if (!roleGateway && !bNoRipple) - { - problem = "You should probably set the no ripple flag on your "; - needFix = true; - } - if (needFix) - { - AccountID const peer = - ownedItem->getFieldAmount(bLow ? sfHighLimit : sfLowLimit).getIssuer(); - STAmount const peerLimit = - ownedItem->getFieldAmount(bLow ? sfHighLimit : sfLowLimit); - problem += to_string(peerLimit.get().currency); - problem += " line to "; - problem += to_string(peerLimit.getIssuer()); - problems.append(problem); - - STAmount limitAmount( - ownedItem->getFieldAmount(bLow ? sfLowLimit : sfHighLimit)); - limitAmount.get().account = peer; - - json::Value& tx = jvTransactions.append(json::ValueType::Object); - tx["TransactionType"] = jss::TrustSet; - tx["LimitAmount"] = limitAmount.getJson(JsonOptions::Values::None); - tx["Flags"] = bNoRipple ? tfClearNoRipple : tfSetNoRipple; - fillTransaction(context, tx, accountID, seq, *ledger); - - return true; - } + problem = "You should clear the no ripple flag on your "; + needFix = true; } - return false; - }); + else if (!roleGateway && !bNoRipple) + { + problem = "You should probably set the no ripple flag on your "; + needFix = true; + } + if (needFix) + { + AccountID const peer = + ownedItem->getFieldAmount(bLow ? sfHighLimit : sfLowLimit).getIssuer(); + STAmount const peerLimit = + ownedItem->getFieldAmount(bLow ? sfHighLimit : sfLowLimit); + problem += to_string(peerLimit.get().currency); + problem += " line to "; + problem += to_string(peerLimit.getIssuer()); + problems.append(problem); + + STAmount limitAmount(ownedItem->getFieldAmount(bLow ? sfLowLimit : sfHighLimit)); + limitAmount.get().account = peer; + + json::Value& tx = jvTransactions.append(json::ValueType::Object); + tx["TransactionType"] = jss::TrustSet; + tx["LimitAmount"] = limitAmount.getJson(JsonOptions::Values::None); + tx["Flags"] = bNoRipple ? tfClearNoRipple : tfSetNoRipple; + fillTransaction(context, tx, accountID, seq, *ledger); + + return true; + } + } + return false; + }); return result; } diff --git a/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp b/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp index df6772e4c0..b9f4a42880 100644 --- a/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp +++ b/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp @@ -76,7 +76,7 @@ doAMMInfo(RPC::JsonContext& context) std::optional accountID; Asset asset1; Asset asset2; - std::shared_ptr amm; + SLE::const_pointer amm; }; auto getValuesFromContextParams = [&]() -> Expected { diff --git a/src/xrpld/rpc/handlers/orderbook/DepositAuthorized.cpp b/src/xrpld/rpc/handlers/orderbook/DepositAuthorized.cpp index cc176aaadb..343d539277 100644 --- a/src/xrpld/rpc/handlers/orderbook/DepositAuthorized.cpp +++ b/src/xrpld/rpc/handlers/orderbook/DepositAuthorized.cpp @@ -90,7 +90,7 @@ doDepositAuthorized(RPC::JsonContext& context) bool const credentialsPresent = params.isMember(jss::credentials); std::set> sorted; - std::vector> lifeExtender; + std::vector lifeExtender; if (credentialsPresent) { auto const& creds(params[jss::credentials]); @@ -128,7 +128,7 @@ doDepositAuthorized(RPC::JsonContext& context) jss::credentials, "an array of CredentialID(hash256)")); } - std::shared_ptr sleCred = ledger->read(keylet::credential(credH)); + SLE::const_pointer sleCred = ledger->read(keylet::credential(credH)); if (!sleCred) { RPC::injectError(RpcBadCredentials, "credentials don't exist", result); diff --git a/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp b/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp index ae551de1ab..6a75277b1b 100644 --- a/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp +++ b/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp @@ -48,7 +48,7 @@ using Prices = static void iteratePriceData( RPC::JsonContext& context, - std::shared_ptr const& sle, + SLE::const_ref sle, std::function const& f) { static constexpr std::uint8_t kMaxHistory = 3; diff --git a/src/xrpld/rpc/handlers/orderbook/NFTOffersHelpers.h b/src/xrpld/rpc/handlers/orderbook/NFTOffersHelpers.h index b94e431117..8529ec2d2c 100644 --- a/src/xrpld/rpc/handlers/orderbook/NFTOffersHelpers.h +++ b/src/xrpld/rpc/handlers/orderbook/NFTOffersHelpers.h @@ -17,10 +17,7 @@ namespace xrpl { inline void -appendNftOfferJson( - Application const& app, - std::shared_ptr const& offer, - json::Value& offers) +appendNftOfferJson(Application const& app, SLE::const_ref offer, json::Value& offers) { json::Value& obj(offers.append(json::ValueType::Object)); @@ -64,7 +61,7 @@ enumerateNFTOffers(RPC::JsonContext& context, uint256 const& nftId, Keylet const json::Value& jsonOffers(result[jss::offers] = json::ValueType::Array); - std::vector> offers; + std::vector offers; unsigned int reserve(limit); uint256 startAfter; std::uint64_t startHint = 0; @@ -97,12 +94,7 @@ enumerateNFTOffers(RPC::JsonContext& context, uint256 const& nftId, Keylet const } if (!forEachItemAfter( - *ledger, - directory, - startAfter, - startHint, - reserve, - [&offers](std::shared_ptr const& offer) { + *ledger, directory, startAfter, startHint, reserve, [&offers](SLE::const_ref offer) { if (offer->getType() == ltNFTOKEN_OFFER) { offers.emplace_back(offer); diff --git a/src/xrpld/rpc/handlers/transaction/Simulate.cpp b/src/xrpld/rpc/handlers/transaction/Simulate.cpp index 7a11b728ce..676f0318a2 100644 --- a/src/xrpld/rpc/handlers/transaction/Simulate.cpp +++ b/src/xrpld/rpc/handlers/transaction/Simulate.cpp @@ -61,7 +61,7 @@ getAutofillSequence(json::Value const& txJson, RPC::JsonContext& context) return Unexpected( RPC::makeError(RpcSrcActMalformed, RPC::invalidFieldMessage("tx.Account"))); } - std::shared_ptr const sle = + SLE::const_pointer const sle = context.app.getOpenLedger().current()->read(keylet::account(*srcAddressID)); if (!hasTicketSeq && !sle) { From e209ee537167ef23f2889eec66640ef21e7ab0ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:29:12 -0400 Subject: [PATCH 051/135] ci: [DEPENDABOT] bump eps1lon/actions-label-merge-conflict from 3.0.3 to 3.1.0 (#7375) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/conflicting-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/conflicting-pr.yml b/.github/workflows/conflicting-pr.yml index 6e667632a0..772d46fd7d 100644 --- a/.github/workflows/conflicting-pr.yml +++ b/.github/workflows/conflicting-pr.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check if PRs are dirty - uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3 + uses: eps1lon/actions-label-merge-conflict@0273be72a0bbd58fcd71d0d6c02c209b50d1e5e1 # v3.1.0 with: dirtyLabel: "PR: has conflicts" repoToken: "${{ secrets.GITHUB_TOKEN }}" From d4cb68d5a1904b19d220c7f681aaa31f7dd9ffbd Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 1 Jun 2026 17:47:01 +0100 Subject: [PATCH 052/135] ci: Check binaries separately from building them (#7355) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docker/check-sanitizers.sh | 48 -------------- docker/loader-path.sh | 12 ++++ docker/nix.Dockerfile | 47 ++++++++------ docker/test_files/compile-cpp-sources.sh | 50 +++++++++++++++ .../cpp_sources}/asan.cpp | 0 docker/test_files/cpp_sources/regular.cpp | 28 +++++++++ .../cpp_sources}/tsan.cpp | 0 .../cpp_sources}/ubsan.cpp | 0 docker/test_files/run-test-binaries.sh | 62 +++++++++++++++++++ 9 files changed, 179 insertions(+), 68 deletions(-) delete mode 100755 docker/check-sanitizers.sh create mode 100755 docker/loader-path.sh create mode 100755 docker/test_files/compile-cpp-sources.sh rename docker/{cpp_files => test_files/cpp_sources}/asan.cpp (100%) create mode 100644 docker/test_files/cpp_sources/regular.cpp rename docker/{cpp_files => test_files/cpp_sources}/tsan.cpp (100%) rename docker/{cpp_files => test_files/cpp_sources}/ubsan.cpp (100%) create mode 100755 docker/test_files/run-test-binaries.sh diff --git a/docker/check-sanitizers.sh b/docker/check-sanitizers.sh deleted file mode 100755 index 38ccaed560..0000000000 --- a/docker/check-sanitizers.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -# Sanity-check that the sanitizer runtimes shipped with g++/clang++ work -# end-to-end against the system loader: compile each example with both -# compilers, run it, and confirm the expected diagnostic is emitted. - -set -eo pipefail - -cpp_files_dir="${1:?usage: $0 }" - -case "$(uname -m)" in - x86_64) loader=/lib64/ld-linux-x86-64.so.2 ;; - aarch64) loader=/lib/ld-linux-aarch64.so.1 ;; - *) - echo "Unsupported arch: $(uname -m)" >&2 - exit 1 - ;; -esac - -declare -A sanitize=( - [asan]="-fsanitize=address" - [tsan]="-fsanitize=thread" - [ubsan]="-fsanitize=undefined" -) -declare -A expect=( - [asan]="heap-use-after-free" - [tsan]="data race" - [ubsan]="signed integer overflow" -) - -for compiler in g++ clang++; do - for name in asan tsan ubsan; do - bin="/tmp/${name}-${compiler}" - echo "=== Build ${name} with ${compiler} ===" - "$compiler" -std=c++20 -O1 -g ${sanitize[$name]} \ - -Wl,--dynamic-linker=$loader \ - "${cpp_files_dir}/${name}.cpp" -o "$bin" - echo "=== Run ${name}-${compiler} ===" - output=$("$bin" 2>&1) || true - echo "$output" - echo "$output" | grep -q "${expect[$name]}" || - { - echo "expected '${expect[$name]}' from $bin" - exit 1 - } - rm -f "$bin" - done -done diff --git a/docker/loader-path.sh b/docker/loader-path.sh new file mode 100755 index 0000000000..b8b9f0de51 --- /dev/null +++ b/docker/loader-path.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +case "$(uname -m)" in + x86_64) LOADER=/lib64/ld-linux-x86-64.so.2 ;; + aarch64) LOADER=/lib/ld-linux-aarch64.so.1 ;; + *) + echo "Unsupported arch: $(uname -m)" >&2 + exit 1 + ;; +esac + +echo "${LOADER}" diff --git a/docker/nix.Dockerfile b/docker/nix.Dockerfile index 690f0b76bd..3c5dbcb734 100644 --- a/docker/nix.Dockerfile +++ b/docker/nix.Dockerfile @@ -27,7 +27,9 @@ RUN mkdir /tmp/nix-store-closure && \ cp -R $(nix-store -qR result/) /tmp/nix-store-closure # Final image -FROM ${BASE_IMAGE} +FROM ${BASE_IMAGE} AS final + +ARG BASE_IMAGE # bash is not located at /bin/bash in nixos/nix, so we need to create a symlink to it. RUN if [ -d /nix ]; then \ @@ -43,25 +45,23 @@ ENTRYPOINT ["/bin/bash"] COPY --from=builder /tmp/nix-store-closure /nix/store COPY --from=builder /tmp/build/result /nix/ci-env -ENV PATH="/nix/ci-env/bin:$PATH" +ENV PATH="/nix/ci-env/bin:${PATH}" # Externally-built dynamically-linked ELF binaries hard-code the loader path -# (e.g. /lib64/ld-linux-x86-64.so.2) in their PT_INTERP header. Copy the -# loader from the Nix store to that path when the base image doesn't already -# provide one (i.e. on nixos/nix). +# (e.g. /lib64/ld-linux-x86-64.so.2) in their PT_INTERP header. Install it +# from the Nix store when the base image doesn't already provide one. +COPY docker/loader-path.sh /tmp/loader-path.sh + RUN <&2; exit 1 ;; -esac -if [ ! -e "$target" ]; then +target="$(/tmp/loader-path.sh)" + +if [ ! -e "${target}" ]; then # Use the loader from the same glibc that gcc links libc against, so # ld-linux and libc/libpthread share GLIBC_PRIVATE symbols at runtime. - src="$(dirname "$(gcc -print-file-name=libc.so.6)")/$(basename "$target")" - [ -e "$src" ] || { echo "ld-linux not found at $src" >&2; exit 1; } - mkdir -p "$(dirname "$target")" - cp "$src" "$target" + src="$(dirname "$(gcc -print-file-name=libc.so.6)")/$(basename "${target}")" + [ -e "${src}" ] || { echo "ld-linux not found at ${src}" >&2; exit 1; } + mkdir -p "$(dirname "${target}")" + cp "${src}" "${target}" fi EOF @@ -87,9 +87,16 @@ run-clang-tidy --help vim --version EOF -# Sanity-check that the sanitizer runtimes shipped with g++/clang++ work -# end-to-end against the system loader. -COPY docker/cpp_files/ /tmp/cpp_files/ -COPY docker/check-sanitizers.sh /tmp/check-sanitizers.sh +# Sanity-check that the sanitizer runtimes shipped with g++/clang++ are able to build binaries +COPY docker/test_files/cpp_sources/ /tmp/cpp_sources/ +COPY docker/test_files/compile-cpp-sources.sh /tmp/compile-cpp-sources.sh +RUN /tmp/compile-cpp-sources.sh /tmp/cpp_sources /tmp/bins -RUN grep -qi ubuntu /etc/os-release 2>/dev/null && /tmp/check-sanitizers.sh /tmp/cpp_files || true +# Sanity-check that the built binaries are able to run. +# We only support running the test binaries on Ubuntu and NixOS right now (will be fixed in the future) +# +# When build and test images will be separate, we will be to run on vanilla images. +COPY docker/test_files/run-test-binaries.sh /tmp/run-test-binaries.sh +RUN if echo "${BASE_IMAGE}" | grep -qiE '(ubuntu|nixos)'; then \ + /tmp/run-test-binaries.sh /tmp/bins; \ + fi diff --git a/docker/test_files/compile-cpp-sources.sh b/docker/test_files/compile-cpp-sources.sh new file mode 100755 index 0000000000..b4edaee2f6 --- /dev/null +++ b/docker/test_files/compile-cpp-sources.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# Compile all C++ test binaries during the Docker image build. +# Each binary has the target system's ELF PT_INTERP (dynamic-linker path) +# baked in so it can run on the (potentially minimal) final BASE_IMAGE. + +set -eo pipefail + +src_dir="${1:?usage: $0 }" +dst_dir="${2:?usage: $0 }" + +loader="$(/tmp/loader-path.sh)" + +mkdir -p "${dst_dir}" + +function compile() { + local compiler="${1}" + local name="${2}" + local san_flag="${3:-}" + + local src="${src_dir}/${name}.cpp" + local binary="${dst_dir}/${name}-${compiler}" + + echo "=== Compile ${name} with ${compiler} ===" + cmd="${compiler} -std=c++23 -O1 -g \ + -pthread \ + -Wl,--dynamic-linker=${loader} \ + ${san_flag} \ + ${src} -o ${binary}" + echo "Command: ${cmd}" + eval "${cmd}" +} + +declare -A sanitize=( + [regular]="" + + [asan]="-fsanitize=address" + [tsan]="-fsanitize=thread" + [ubsan]="-fsanitize=undefined -fno-sanitize-recover=all" +) + +for name in regular asan tsan ubsan; do + san_flag="${sanitize[${name}]}" + for compiler in g++ clang++; do + compile "${compiler}" "${name}" "${san_flag}" + done +done + +echo "=== All binaries compiled ===" + +ls -la "${dst_dir}" diff --git a/docker/cpp_files/asan.cpp b/docker/test_files/cpp_sources/asan.cpp similarity index 100% rename from docker/cpp_files/asan.cpp rename to docker/test_files/cpp_sources/asan.cpp diff --git a/docker/test_files/cpp_sources/regular.cpp b/docker/test_files/cpp_sources/regular.cpp new file mode 100644 index 0000000000..637dafa1fd --- /dev/null +++ b/docker/test_files/cpp_sources/regular.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +static std::mutex gMutex; + +void +worker(int id) +{ + std::lock_guard lock(gMutex); + std::cout << "Hello from thread " << id << "\n"; +} + +int +main() +{ + constexpr int kNumThreads = 10; + std::vector threads; + threads.reserve(kNumThreads); + for (int i = 0; i < kNumThreads; ++i) + threads.emplace_back(worker, i); + for (auto& t : threads) + t.join(); + + std::cout << "Hello from main thread\n"; + return 0; +} diff --git a/docker/cpp_files/tsan.cpp b/docker/test_files/cpp_sources/tsan.cpp similarity index 100% rename from docker/cpp_files/tsan.cpp rename to docker/test_files/cpp_sources/tsan.cpp diff --git a/docker/cpp_files/ubsan.cpp b/docker/test_files/cpp_sources/ubsan.cpp similarity index 100% rename from docker/cpp_files/ubsan.cpp rename to docker/test_files/cpp_sources/ubsan.cpp diff --git a/docker/test_files/run-test-binaries.sh b/docker/test_files/run-test-binaries.sh new file mode 100755 index 0000000000..6e8f0a931c --- /dev/null +++ b/docker/test_files/run-test-binaries.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# Run pre-compiled sanitizer binaries and confirm each emits its expected diagnostic. +# Binaries must already exist in with the layout: +# -g++ and -clang++ for name in {regular,asan,tsan,ubsan} + +set -eo pipefail + +bins_dir="${1:?usage: $0 }" + +# Run a binary and verify its exit code and output. +# Usage: run +function run() { + local binary="${1}" + local expected_output="${2}" + local expected_rc="${3}" + + local out_file + out_file="$(mktemp)" + + echo "=== Run ${binary} ===" + local rc=0 + "${binary}" >"${out_file}" 2>&1 || rc=$? + + cat "${out_file}" + + if [ "${expected_rc}" = "nonzero" ]; then + if [ "${rc}" -eq 0 ]; then + echo "ERROR: expected non-zero exit code from ${binary}, got ${rc}" >&2 + exit 1 + fi + elif [ "${rc}" -ne "${expected_rc}" ]; then + echo "ERROR: expected exit code ${expected_rc} from ${binary}, got ${rc}" >&2 + exit 1 + fi + + grep -q "${expected_output}" "${out_file}" || + { + echo "ERROR: expected '${expected_output}' from ${binary}" >&2 + exit 1 + } + echo "OK: '${expected_output}' detected" +} + +declare -A expect=( + [regular]="Hello from main thread" + + [asan]="heap-use-after-free" + [tsan]="data race" + [ubsan]="signed integer overflow" +) + +for compiler in g++ clang++; do + for name in regular asan tsan ubsan; do + binary="${bins_dir}/${name}-${compiler}" + if [ "${name}" = "regular" ]; then + expected_rc=0 + else + expected_rc=nonzero + fi + run "${binary}" "${expect[$name]}" "${expected_rc}" + done +done From ad111bcc22d439c4bc6d40333aa0db9f6c8e0d64 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 2 Jun 2026 14:51:20 +0100 Subject: [PATCH 053/135] ci: Patch binaries in nix-based images and test in every distro (#7376) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docker/check-tool-versions.sh | 24 +++++++ docker/install-sanitizer-libs.sh | 89 ++++++++++++++++++++++++ docker/nix.Dockerfile | 66 ++++++++++-------- docker/test_files/compile-cpp-sources.sh | 17 +++-- docker/test_files/run-test-binaries.sh | 44 +++++++++--- nix/packages.nix | 1 + 6 files changed, 196 insertions(+), 45 deletions(-) create mode 100755 docker/check-tool-versions.sh create mode 100755 docker/install-sanitizer-libs.sh diff --git a/docker/check-tool-versions.sh b/docker/check-tool-versions.sh new file mode 100755 index 0000000000..db20be45e4 --- /dev/null +++ b/docker/check-tool-versions.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Verify that every tool expected in the Nix CI env is present and runnable. +set -euo pipefail + +ccache --version +clang --version +clang++ --version +clang-format --version +cmake --version +conan --version +g++ --version +gcc --version +gcovr --version +git --version +less --version +make --version +mold --version +ninja --version +perl --version +pkg-config --version +pre-commit --version +python3 --version +run-clang-tidy --help +vim --version diff --git a/docker/install-sanitizer-libs.sh b/docker/install-sanitizer-libs.sh new file mode 100755 index 0000000000..a28efeab3f --- /dev/null +++ b/docker/install-sanitizer-libs.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# Install sanitizer runtime libraries required to run binaries compiled with: +# -fsanitize=address → libasan.so.8 +# -fsanitize=thread → libtsan.so.2 +# -fsanitize=undefined → libubsan.so.1 +# +# The exact SONAMEs required depend on the compiler toolchain used to build the +# test binaries (see nix/ci-env.nix). If the toolchain is bumped and SONAMEs +# change, update the list below (or detect them from the binaries). +# +# Supported base images: +# debian:bookworm +# ubuntu:20.04 +# rhel:9 +# nixos/nix — tests are skipped; this script is not called + +set -euo pipefail + +if [ ! -f /etc/os-release ]; then + echo "ERROR: /etc/os-release not found; cannot detect OS" >&2 + exit 1 +fi + +# shellcheck source=/dev/null +. /etc/os-release + +echo "Detected OS: ${ID} ${VERSION_ID:-}" + +case "${ID}" in + debian) + apt-get update -y + apt-get install -y --no-install-recommends \ + libasan8 \ + libtsan2 \ + libubsan1 + + apt-get clean + rm -rf /var/lib/apt/lists/* + ;; + + ubuntu) + apt-get update -y + apt-get install -y --no-install-recommends \ + gnupg \ + software-properties-common + add-apt-repository -y ppa:ubuntu-toolchain-r/test + apt-get update -y + apt-get install -y --no-install-recommends \ + libasan8 \ + libtsan2 \ + libubsan1 + + apt-get clean + rm -rf /var/lib/apt/lists/* + ;; + + rhel | centos | rocky | almalinux) + dnf install -y \ + libasan8 \ + libtsan2 \ + libubsan + + dnf clean -y all + rm -rf /var/cache/dnf/* + ;; + + *) + echo "ERROR: unsupported OS '${ID}'. Supported: debian, ubuntu, rhel-family" >&2 + exit 1 + ;; +esac + +# Verify that every expected library is now resolvable by the dynamic linker. +missing=0 +for lib in libasan.so.8 libtsan.so.2 libubsan.so.1; do + if ldconfig -p | grep -q "${lib}"; then + echo "OK: ${lib} found" + else + echo "ERROR: ${lib} not found after installation" >&2 + missing=$((missing + 1)) + fi +done + +if [ "${missing}" -ne 0 ]; then + echo "ERROR: ${missing} library/libraries missing" >&2 + exit 1 +fi + +echo "All sanitizer runtime libraries installed successfully." diff --git a/docker/nix.Dockerfile b/docker/nix.Dockerfile index 3c5dbcb734..a0eab31769 100644 --- a/docker/nix.Dockerfile +++ b/docker/nix.Dockerfile @@ -32,7 +32,7 @@ FROM ${BASE_IMAGE} AS final ARG BASE_IMAGE # bash is not located at /bin/bash in nixos/nix, so we need to create a symlink to it. -RUN if [ -d /nix ]; then \ +RUN if echo "${BASE_IMAGE}" | grep -qiE 'nixos'; then \ ln -s /root/.nix-profile/bin/bash /bin/bash; \ fi @@ -65,38 +65,44 @@ if [ ! -e "${target}" ]; then fi EOF -RUN < function run() { @@ -18,27 +20,34 @@ function run() { out_file="$(mktemp)" echo "=== Run ${binary} ===" - local rc=0 - "${binary}" >"${out_file}" 2>&1 || rc=$? + set +e + "${binary}" >"${out_file}" 2>&1 + local rc=$? + set -e cat "${out_file}" + local failed=0 if [ "${expected_rc}" = "nonzero" ]; then if [ "${rc}" -eq 0 ]; then echo "ERROR: expected non-zero exit code from ${binary}, got ${rc}" >&2 - exit 1 + failed=1 fi elif [ "${rc}" -ne "${expected_rc}" ]; then echo "ERROR: expected exit code ${expected_rc} from ${binary}, got ${rc}" >&2 - exit 1 + failed=1 fi - grep -q "${expected_output}" "${out_file}" || - { - echo "ERROR: expected '${expected_output}' from ${binary}" >&2 - exit 1 - } - echo "OK: '${expected_output}' detected" + if ! grep -q "${expected_output}" "${out_file}"; then + echo "ERROR: expected '${expected_output}' from ${binary}" >&2 + failed=1 + fi + + if [ "${failed}" -eq 0 ]; then + echo "OK: '${expected_output}' detected" + else + failed_binaries+=("${binary}") + fi } declare -A expect=( @@ -52,6 +61,15 @@ declare -A expect=( for compiler in g++ clang++; do for name in regular asan tsan ubsan; do binary="${bins_dir}/${name}-${compiler}" + + if [ "${name}" = "tsan" ] && [ "${compiler}" = "g++" ] && + grep -qi 'debian' /etc/os-release 2>/dev/null && + [ "$(uname -m)" = "aarch64" ]; then + echo "=== Skipping ${binary} (tsan-g++ unsupported on Debian ARM64) ===" + echo " NOTE: to enable it, add --security-opt seccomp=unconfined to your docker run command" + continue + fi + if [ "${name}" = "regular" ]; then expected_rc=0 else @@ -60,3 +78,9 @@ for compiler in g++ clang++; do run "${binary}" "${expect[$name]}" "${expected_rc}" done done + +if [ "${#failed_binaries[@]}" -gt 0 ]; then + echo "ERROR: the following binaries failed:" >&2 + printf ' %s\n' "${failed_binaries[@]}" >&2 + exit 1 +fi diff --git a/nix/packages.nix b/nix/packages.nix index d209620a68..3d92fedb4b 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -15,6 +15,7 @@ in git gnumake llvmPackages_22.clang-tools + less # needed for git diff mold ninja patchelf From 225ed204ad101527c19f1f3c9a32fda1bd28761b Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:12:09 +0200 Subject: [PATCH 054/135] test: Suppress invariant-failure logs in Vault and LoanBroker bug-regression tests (#7379) --- src/test/app/LoanBroker_test.cpp | 5 ++++- src/test/app/Vault_test.cpp | 26 ++++++++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index 92949256fd..0edb955b90 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -52,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -1708,7 +1710,8 @@ class LoanBroker_test : public beast::unit_test::Suite Account const alice("alice"); auto const withFix = features[fixCleanup3_2_0]; - Env env(*this, features); + std::string logs; + Env env(*this, features, std::make_unique(&logs)); env.fund(XRP(100'000), issuer, alice); env.close(); diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index bf707afae9..2c83ad91ec 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,7 @@ #include #include #include +#include #include #include #include @@ -6630,7 +6632,8 @@ class Vault_test : public beast::unit_test::Suite "fixed-asset amount with impaired loan"} + (withFix ? " (fixCleanup3_2_0)" : " (pre-fix)")); - Env env(*this, features); + std::string logs; + Env env(*this, features, std::make_unique(&logs)); auto const f = setupStuckDepositor(env); if (!f.vaultKeylet || !f.asset || f.sharesLender == 0) { @@ -6748,7 +6751,8 @@ class Vault_test : public beast::unit_test::Suite "burn is rejected while loss outstanding"} + (withFix ? " (fixCleanup3_2_0)" : " (pre-fix)")); - Env env(*this, features); + std::string logs; + Env env(*this, features, std::make_unique(&logs)); auto const f = setupStuckDepositor(env); if (!f.vaultKeylet || f.sharesLender == 0) { @@ -7074,7 +7078,8 @@ class Vault_test : public beast::unit_test::Suite using namespace test::jtx; auto runScenario = [this](FeatureBitset features, TER expected) { - Env env(*this, features); + std::string logs; + Env env(*this, features, std::make_unique(&logs)); Account const issuer{"issuer"}; Account const alice{"alice"}; @@ -7150,7 +7155,8 @@ class Vault_test : public beast::unit_test::Suite using namespace test::jtx; auto runScenario = [this](FeatureBitset features, TER expected) { - Env env(*this, features); + std::string logs; + Env env(*this, features, std::make_unique(&logs)); Account const issuer{"issuer"}; Account const alice{"alice"}; @@ -7226,7 +7232,8 @@ class Vault_test : public beast::unit_test::Suite enum class DestKind : bool { ThirdParty = false, Self = true }; auto runScenario = [this](FeatureBitset features, DestKind destKind, TER expected) { - Env env(*this, features); + std::string logs; + Env env(*this, features, std::make_unique(&logs)); Account const issuer{"issuer"}; Account const alice{"alice"}; @@ -7331,7 +7338,8 @@ class Vault_test : public beast::unit_test::Suite using namespace test::jtx; auto runScenario = [this](FeatureBitset features, TER expected) { - Env env(*this, features); + std::string logs; + Env env(*this, features, std::make_unique(&logs)); Account const issuer{"issuer"}; Account const alice{"alice"}; @@ -7414,7 +7422,8 @@ class Vault_test : public beast::unit_test::Suite { using namespace test::jtx; auto runScenario = [this](FeatureBitset features, TER expected) { - Env env(*this, features); + std::string logs; + Env env(*this, features, std::make_unique(&logs)); Account const issuer{"issuer"}; Account const alice{"alice"}; @@ -7489,7 +7498,8 @@ class Vault_test : public beast::unit_test::Suite using namespace test::jtx; auto runScenario = [this](FeatureBitset features, TER expected) { - Env env(*this, features); + std::string logs; + Env env(*this, features, std::make_unique(&logs)); Account const issuer{"issuer"}; Account const owner{"owner"}; From 1441d4690d91cf047704af27cca0381269c8b17e Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 3 Jun 2026 01:16:02 +0100 Subject: [PATCH 055/135] chore: Update flake.lock to allow conan with clang-22 support (#7390) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 3149f3feed..2013cfabd4 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1777954456, - "narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=", + "lastModified": 1780243769, + "narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1", + "rev": "331800de5053fcebacf6813adb5db9c9dca22a0c", "type": "github" }, "original": { From 96b2c0964f0d19f48853fd3fcec362e9bfba5d60 Mon Sep 17 00:00:00 2001 From: Bart Date: Wed, 3 Jun 2026 11:34:19 -0400 Subject: [PATCH 056/135] refactor: Replace `intr_ptr::SharedPtr` by `SHAMapTreeNodePtr` (#7396) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- include/xrpl/shamap/SHAMap.h | 57 ++++++++----------- .../xrpl/shamap/SHAMapAccountStateLeafNode.h | 2 +- include/xrpl/shamap/SHAMapInnerNode.h | 16 +++--- include/xrpl/shamap/SHAMapTreeNode.h | 15 +++-- include/xrpl/shamap/SHAMapTxLeafNode.h | 2 +- .../xrpl/shamap/SHAMapTxPlusMetaLeafNode.h | 2 +- include/xrpl/shamap/TreeNodeCache.h | 2 +- include/xrpl/shamap/detail/TaggedPointer.h | 4 +- include/xrpl/shamap/detail/TaggedPointer.ipp | 38 ++++++------- src/libxrpl/shamap/SHAMap.cpp | 57 ++++++++----------- src/libxrpl/shamap/SHAMapDelta.cpp | 7 +-- src/libxrpl/shamap/SHAMapInnerNode.cpp | 26 +++++---- src/libxrpl/shamap/SHAMapSync.cpp | 8 +-- src/libxrpl/shamap/SHAMapTreeNode.cpp | 10 ++-- 14 files changed, 115 insertions(+), 131 deletions(-) diff --git a/include/xrpl/shamap/SHAMap.h b/include/xrpl/shamap/SHAMap.h index f63fc95b27..32e87b64c6 100644 --- a/include/xrpl/shamap/SHAMap.h +++ b/include/xrpl/shamap/SHAMap.h @@ -85,7 +85,7 @@ private: /** The sequence of the ledger that this map references, if any. */ std::uint32_t ledgerSeq_ = 0; - intr_ptr::SharedPtr root_; + SHAMapTreeNodePtr root_; mutable SHAMapState state_; SHAMapType const type_; bool backed_ = true; // Map is backed by the database @@ -326,36 +326,32 @@ public: invariants() const; private: - using SharedPtrNodeStack = - std::stack, SHAMapNodeID>>; + using SharedPtrNodeStack = std::stack>; using DeltaRef = std::pair, boost::intrusive_ptr>; // tree node cache operations - intr_ptr::SharedPtr + SHAMapTreeNodePtr cacheLookup(SHAMapHash const& hash) const; void - canonicalize(SHAMapHash const& hash, intr_ptr::SharedPtr&) const; + canonicalize(SHAMapHash const& hash, SHAMapTreeNodePtr&) const; // database operations - intr_ptr::SharedPtr + SHAMapTreeNodePtr fetchNodeFromDB(SHAMapHash const& hash) const; - intr_ptr::SharedPtr + SHAMapTreeNodePtr fetchNodeNT(SHAMapHash const& hash) const; - intr_ptr::SharedPtr + SHAMapTreeNodePtr fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter* filter) const; - intr_ptr::SharedPtr + SHAMapTreeNodePtr fetchNode(SHAMapHash const& hash) const; - intr_ptr::SharedPtr + SHAMapTreeNodePtr checkFilter(SHAMapHash const& hash, SHAMapSyncFilter* filter) const; /** Update hashes up to the root */ void - dirtyUp( - SharedPtrNodeStack& stack, - uint256 const& target, - intr_ptr::SharedPtr terminal); + dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNodePtr terminal); /** Walk towards the specified id, returning the node. Caller must check if the return is nullptr, and if not, if the node->peekItem()->key() == @@ -377,25 +373,21 @@ private: preFlushNode(intr_ptr::SharedPtr node) const; /** write and canonicalize modified node */ - intr_ptr::SharedPtr - writeNode(NodeObjectType t, intr_ptr::SharedPtr node) const; + SHAMapTreeNodePtr + writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const; // returns the first item at or below this node SHAMapLeafNode* - firstBelow(intr_ptr::SharedPtr, SharedPtrNodeStack& stack, int branch = 0) - const; + firstBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch = 0) const; // returns the last item at or below this node SHAMapLeafNode* - lastBelow( - intr_ptr::SharedPtr node, - SharedPtrNodeStack& stack, - int branch = kBranchFactor) const; + lastBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch = kBranchFactor) const; // helper function for firstBelow and lastBelow SHAMapLeafNode* belowHelper( - intr_ptr::SharedPtr node, + SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch, std::tuple, std::function> const& loopParams) @@ -407,15 +399,14 @@ private: descend(SHAMapInnerNode*, int branch) const; SHAMapTreeNode* descendThrow(SHAMapInnerNode*, int branch) const; - intr_ptr::SharedPtr + SHAMapTreeNodePtr descend(SHAMapInnerNode&, int branch) const; - intr_ptr::SharedPtr + SHAMapTreeNodePtr descendThrow(SHAMapInnerNode&, int branch) const; // Descend with filter // If pending, callback is called as if it called fetchNodeNT - using descendCallback = - std::function, SHAMapHash const&)>; + using descendCallback = std::function; SHAMapTreeNode* descendAsync( SHAMapInnerNode* parent, @@ -433,7 +424,7 @@ private: // Non-storing // Does not hook the returned node to its parent - intr_ptr::SharedPtr + SHAMapTreeNodePtr descendNoStore(SHAMapInnerNode&, int branch) const; /** If there is only one leaf below this node, get its contents */ @@ -495,10 +486,10 @@ private: // nodes we may have acquired from deferred reads using DeferredNode = std::tuple< - SHAMapInnerNode*, // parent node - SHAMapNodeID, // parent node ID - int, // branch - intr_ptr::SharedPtr>; // node + SHAMapInnerNode*, // parent node + SHAMapNodeID, // parent node ID + int, // branch + SHAMapTreeNodePtr>; // node int deferred; std::mutex deferLock; @@ -524,7 +515,7 @@ private: gmnProcessDeferredReads(MissingNodes&); // fetch from DB helper function - intr_ptr::SharedPtr + SHAMapTreeNodePtr finishFetch(SHAMapHash const& hash, std::shared_ptr const& object) const; }; diff --git a/include/xrpl/shamap/SHAMapAccountStateLeafNode.h b/include/xrpl/shamap/SHAMapAccountStateLeafNode.h index c67b32d4e7..e388d205d1 100644 --- a/include/xrpl/shamap/SHAMapAccountStateLeafNode.h +++ b/include/xrpl/shamap/SHAMapAccountStateLeafNode.h @@ -27,7 +27,7 @@ public: { } - intr_ptr::SharedPtr + SHAMapTreeNodePtr clone(std::uint32_t cowid) const final { return intr_ptr::makeShared(item_, cowid, hash_); diff --git a/include/xrpl/shamap/SHAMapInnerNode.h b/include/xrpl/shamap/SHAMapInnerNode.h index 48416a93e6..cafb498218 100644 --- a/include/xrpl/shamap/SHAMapInnerNode.h +++ b/include/xrpl/shamap/SHAMapInnerNode.h @@ -87,7 +87,7 @@ public: void partialDestructor() override; - intr_ptr::SharedPtr + SHAMapTreeNodePtr clone(std::uint32_t cowid) const override; SHAMapNodeType @@ -121,19 +121,19 @@ public: getChildHash(int m) const; void - setChild(int m, intr_ptr::SharedPtr child); + setChild(int m, SHAMapTreeNodePtr child); void - shareChild(int m, intr_ptr::SharedPtr const& child); + shareChild(int m, SHAMapTreeNodePtr const& child); SHAMapTreeNode* getChildPointer(int branch); - intr_ptr::SharedPtr + SHAMapTreeNodePtr getChild(int branch); - intr_ptr::SharedPtr - canonicalizeChild(int branch, intr_ptr::SharedPtr node); + SHAMapTreeNodePtr + canonicalizeChild(int branch, SHAMapTreeNodePtr node); // sync functions bool @@ -161,10 +161,10 @@ public: void invariants(bool isRoot = false) const override; - static intr_ptr::SharedPtr + static SHAMapTreeNodePtr makeFullInner(Slice data, SHAMapHash const& hash, bool hashValid); - static intr_ptr::SharedPtr + static SHAMapTreeNodePtr makeCompressedInner(Slice data); }; diff --git a/include/xrpl/shamap/SHAMapTreeNode.h b/include/xrpl/shamap/SHAMapTreeNode.h index ee74155ac4..5cca2ea41a 100644 --- a/include/xrpl/shamap/SHAMapTreeNode.h +++ b/include/xrpl/shamap/SHAMapTreeNode.h @@ -13,6 +13,9 @@ namespace xrpl { +class SHAMapTreeNode; +using SHAMapTreeNodePtr = intr_ptr::SharedPtr; + // These are wire-protocol identifiers used during serialization to encode the // type of a node. They should not be arbitrarily be changed. static constexpr unsigned char const kWireTypeTransaction = 0; @@ -112,7 +115,7 @@ public: } /** Make a copy of this node, setting the owner. */ - virtual intr_ptr::SharedPtr + virtual SHAMapTreeNodePtr clone(std::uint32_t cowid) const = 0; /** @} */ @@ -153,20 +156,20 @@ public: virtual void invariants(bool isRoot = false) const = 0; - static intr_ptr::SharedPtr + static SHAMapTreeNodePtr makeFromPrefix(Slice rawNode, SHAMapHash const& hash); - static intr_ptr::SharedPtr + static SHAMapTreeNodePtr makeFromWire(Slice rawNode); private: - static intr_ptr::SharedPtr + static SHAMapTreeNodePtr makeTransaction(Slice data, SHAMapHash const& hash, bool hashValid); - static intr_ptr::SharedPtr + static SHAMapTreeNodePtr makeAccountState(Slice data, SHAMapHash const& hash, bool hashValid); - static intr_ptr::SharedPtr + static SHAMapTreeNodePtr makeTransactionWithMeta(Slice data, SHAMapHash const& hash, bool hashValid); }; diff --git a/include/xrpl/shamap/SHAMapTxLeafNode.h b/include/xrpl/shamap/SHAMapTxLeafNode.h index 72be5b1962..49f4f90906 100644 --- a/include/xrpl/shamap/SHAMapTxLeafNode.h +++ b/include/xrpl/shamap/SHAMapTxLeafNode.h @@ -26,7 +26,7 @@ public: { } - intr_ptr::SharedPtr + SHAMapTreeNodePtr clone(std::uint32_t cowid) const final { return intr_ptr::makeShared(item_, cowid, hash_); diff --git a/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h b/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h index 44562aeaba..3f4163ac41 100644 --- a/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h +++ b/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h @@ -27,7 +27,7 @@ public: { } - intr_ptr::SharedPtr + SHAMapTreeNodePtr clone(std::uint32_t cowid) const override { return intr_ptr::makeShared(item_, cowid, hash_); diff --git a/include/xrpl/shamap/TreeNodeCache.h b/include/xrpl/shamap/TreeNodeCache.h index 4edb6348ec..2d5782c7e9 100644 --- a/include/xrpl/shamap/TreeNodeCache.h +++ b/include/xrpl/shamap/TreeNodeCache.h @@ -11,5 +11,5 @@ using TreeNodeCache = TaggedCache< SHAMapTreeNode, /*IsKeyCache*/ false, intr_ptr::SharedWeakUnionPtr, - intr_ptr::SharedPtr>; + SHAMapTreeNodePtr>; } // namespace xrpl diff --git a/include/xrpl/shamap/detail/TaggedPointer.h b/include/xrpl/shamap/detail/TaggedPointer.h index 94dbe95284..5eb3863de0 100644 --- a/include/xrpl/shamap/detail/TaggedPointer.h +++ b/include/xrpl/shamap/detail/TaggedPointer.h @@ -148,7 +148,7 @@ public: /** Get the number of elements in each array and a pointer to the start of each array. */ - [[nodiscard]] std::tuple*> + [[nodiscard]] std::tuple getHashesAndChildren() const; /** Get the `hashes` array */ @@ -156,7 +156,7 @@ public: getHashes() const; /** Get the `children` array */ - [[nodiscard]] intr_ptr::SharedPtr* + [[nodiscard]] SHAMapTreeNodePtr* getChildren() const; /** Call the `f` callback for all 16 (branchFactor) branches - even if diff --git a/include/xrpl/shamap/detail/TaggedPointer.ipp b/include/xrpl/shamap/detail/TaggedPointer.ipp index 2e6e31fed8..6606c49a6b 100644 --- a/include/xrpl/shamap/detail/TaggedPointer.ipp +++ b/include/xrpl/shamap/detail/TaggedPointer.ipp @@ -26,8 +26,7 @@ static_assert( // Terminology: A chunk is the memory being allocated from a block. A block // contains multiple chunks. This is the terminology the boost documentation // uses. Pools use "Simple Segregated Storage" as their storage format. -constexpr size_t kElementSizeBytes = - (sizeof(SHAMapHash) + sizeof(intr_ptr::SharedPtr)); +constexpr size_t kElementSizeBytes = sizeof(SHAMapHash) + sizeof(SHAMapTreeNodePtr); constexpr size_t kBlockSizeBytes = kilobytes(512); @@ -364,8 +363,7 @@ inline TaggedPointer::TaggedPointer( // keep new (&dstHashes[dstIndex]) SHAMapHash{srcHashes[srcIndex]}; - new (&dstChildren[dstIndex]) - intr_ptr::SharedPtr{std::move(srcChildren[srcIndex])}; + new (&dstChildren[dstIndex]) SHAMapTreeNodePtr{std::move(srcChildren[srcIndex])}; ++dstIndex; ++srcIndex; } @@ -376,7 +374,7 @@ inline TaggedPointer::TaggedPointer( if (dstIsDense) { new (&dstHashes[dstIndex]) SHAMapHash{}; - new (&dstChildren[dstIndex]) intr_ptr::SharedPtr{}; + new (&dstChildren[dstIndex]) SHAMapTreeNodePtr{}; ++dstIndex; } } @@ -384,7 +382,7 @@ inline TaggedPointer::TaggedPointer( { // add new (&dstHashes[dstIndex]) SHAMapHash{}; - new (&dstChildren[dstIndex]) intr_ptr::SharedPtr{}; + new (&dstChildren[dstIndex]) SHAMapTreeNodePtr{}; ++dstIndex; if (srcIsDense) { @@ -397,7 +395,7 @@ inline TaggedPointer::TaggedPointer( if (dstIsDense) { new (&dstHashes[dstIndex]) SHAMapHash{}; - new (&dstChildren[dstIndex]) intr_ptr::SharedPtr{}; + new (&dstChildren[dstIndex]) SHAMapTreeNodePtr{}; ++dstIndex; } if (srcIsDense) @@ -414,7 +412,7 @@ inline TaggedPointer::TaggedPointer( for (int i = dstIndex; i < dstNumAllocated; ++i) { new (&dstHashes[i]) SHAMapHash{}; - new (&dstChildren[i]) intr_ptr::SharedPtr{}; + new (&dstChildren[i]) SHAMapTreeNodePtr{}; } *this = std::move(dst); } @@ -433,8 +431,10 @@ inline TaggedPointer::TaggedPointer( // allocate hashes and children, but do not run constructors TaggedPointer newHashesAndChildren{RawAllocateTag{}, toAllocate}; - SHAMapHash *newHashes = nullptr, *oldHashes = nullptr; - intr_ptr::SharedPtr*newChildren = nullptr, *oldChildren = nullptr; + SHAMapHash* newHashes = nullptr; + SHAMapHash* oldHashes = nullptr; + SHAMapTreeNodePtr* newChildren = nullptr; + SHAMapTreeNodePtr* oldChildren = nullptr; std::uint8_t newNumAllocated = 0; // structured bindings can't be captured in c++ 17; use tie instead std::tie(newNumAllocated, newHashes, newChildren) = newHashesAndChildren.getHashesAndChildren(); @@ -445,8 +445,7 @@ inline TaggedPointer::TaggedPointer( // new arrays are dense, old arrays are sparse iterNonEmptyChildIndexes(isBranch, [&](auto branchNum, auto indexNum) { new (&newHashes[branchNum]) SHAMapHash{oldHashes[indexNum]}; - new (&newChildren[branchNum]) - intr_ptr::SharedPtr{std::move(oldChildren[indexNum])}; + new (&newChildren[branchNum]) SHAMapTreeNodePtr{std::move(oldChildren[indexNum])}; }); // Run the constructors for the remaining elements for (int i = 0; i < SHAMapInnerNode::kBranchFactor; ++i) @@ -454,7 +453,7 @@ inline TaggedPointer::TaggedPointer( if (((1 << i) & isBranch) != 0) continue; new (&newHashes[i]) SHAMapHash{}; - new (&newChildren[i]) intr_ptr::SharedPtr{}; + new (&newChildren[i]) SHAMapTreeNodePtr{}; } } else @@ -464,14 +463,14 @@ inline TaggedPointer::TaggedPointer( iterNonEmptyChildIndexes(isBranch, [&](auto branchNum, auto indexNum) { new (&newHashes[curCompressedIndex]) SHAMapHash{oldHashes[indexNum]}; new (&newChildren[curCompressedIndex]) - intr_ptr::SharedPtr{std::move(oldChildren[indexNum])}; + SHAMapTreeNodePtr{std::move(oldChildren[indexNum])}; ++curCompressedIndex; }); // Run the constructors for the remaining elements for (int i = curCompressedIndex; i < newNumAllocated; ++i) { new (&newHashes[i]) SHAMapHash{}; - new (&newChildren[i]) intr_ptr::SharedPtr{}; + new (&newChildren[i]) SHAMapTreeNodePtr{}; } } @@ -485,7 +484,7 @@ inline TaggedPointer::TaggedPointer(std::uint8_t numChildren) for (std::size_t i = 0; i < numAllocated; ++i) { new (&hashes[i]) SHAMapHash{}; - new (&children[i]) intr_ptr::SharedPtr{}; + new (&children[i]) SHAMapTreeNodePtr{}; } } @@ -523,14 +522,13 @@ TaggedPointer::isDense() const return (tp_ & kTagMask) == kBoundaries.size() - 1; } -[[nodiscard]] inline std::tuple*> +[[nodiscard]] inline std::tuple TaggedPointer::getHashesAndChildren() const { auto const [tag, ptr] = decode(); auto const hashes = reinterpret_cast(ptr); std::uint8_t const numAllocated = kBoundaries[tag]; - auto const children = - reinterpret_cast*>(hashes + numAllocated); + auto const children = reinterpret_cast(hashes + numAllocated); return {numAllocated, hashes, children}; }; @@ -540,7 +538,7 @@ TaggedPointer::getHashes() const return reinterpret_cast(tp_ & kPtrMask); }; -[[nodiscard]] inline intr_ptr::SharedPtr* +[[nodiscard]] inline SHAMapTreeNodePtr* TaggedPointer::getChildren() const { auto [unused1, unused2, result] = getHashesAndChildren(); diff --git a/src/libxrpl/shamap/SHAMap.cpp b/src/libxrpl/shamap/SHAMap.cpp index d3a7d49da6..4aad255d81 100644 --- a/src/libxrpl/shamap/SHAMap.cpp +++ b/src/libxrpl/shamap/SHAMap.cpp @@ -97,10 +97,7 @@ SHAMap::snapShot(bool isMutable) const } void -SHAMap::dirtyUp( - SharedPtrNodeStack& stack, - uint256 const& target, - intr_ptr::SharedPtr child) +SHAMap::dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNodePtr child) { // walk the tree up from through the inner nodes to the root_ // update hashes and links @@ -165,7 +162,7 @@ SHAMap::findKey(uint256 const& id) const return leaf; } -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMap::fetchNodeFromDB(SHAMapHash const& hash) const { XRPL_ASSERT(backed_, "xrpl::SHAMap::fetchNodeFromDB : is backed"); @@ -173,7 +170,7 @@ SHAMap::fetchNodeFromDB(SHAMapHash const& hash) const return finishFetch(hash, obj); } -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMap::finishFetch(SHAMapHash const& hash, std::shared_ptr const& object) const { XRPL_ASSERT(backed_, "xrpl::SHAMap::finishFetch : is backed"); @@ -208,7 +205,7 @@ SHAMap::finishFetch(SHAMapHash const& hash, std::shared_ptr const& o } // See if a sync filter has a node -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMap::checkFilter(SHAMapHash const& hash, SHAMapSyncFilter* filter) const { if (auto nodeData = filter->getNode(hash)) @@ -234,7 +231,7 @@ SHAMap::checkFilter(SHAMapHash const& hash, SHAMapSyncFilter* filter) const // Get a node without throwing // Used on maps where missing nodes are expected -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMap::fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter* filter) const { auto node = cacheLookup(hash); @@ -257,7 +254,7 @@ SHAMap::fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter* filter) const return node; } -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMap::fetchNodeNT(SHAMapHash const& hash) const { auto node = cacheLookup(hash); @@ -269,7 +266,7 @@ SHAMap::fetchNodeNT(SHAMapHash const& hash) const } // Throw if the node is missing -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMap::fetchNode(SHAMapHash const& hash) const { auto node = fetchNodeNT(hash); @@ -291,10 +288,10 @@ SHAMap::descendThrow(SHAMapInnerNode* parent, int branch) const return ret; } -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMap::descendThrow(SHAMapInnerNode& parent, int branch) const { - intr_ptr::SharedPtr ret = descend(parent, branch); + SHAMapTreeNodePtr ret = descend(parent, branch); if (!ret && !parent.isEmptyBranch(branch)) Throw(type_, parent.getChildHash(branch)); @@ -309,7 +306,7 @@ SHAMap::descend(SHAMapInnerNode* parent, int branch) const if ((ret != nullptr) || !backed_) return ret; - intr_ptr::SharedPtr node = fetchNodeNT(parent->getChildHash(branch)); + SHAMapTreeNodePtr node = fetchNodeNT(parent->getChildHash(branch)); if (!node) return nullptr; @@ -317,10 +314,10 @@ SHAMap::descend(SHAMapInnerNode* parent, int branch) const return node.get(); } -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMap::descend(SHAMapInnerNode& parent, int branch) const { - intr_ptr::SharedPtr node = parent.getChild(branch); + SHAMapTreeNodePtr node = parent.getChild(branch); if (node || !backed_) return node; @@ -334,10 +331,10 @@ SHAMap::descend(SHAMapInnerNode& parent, int branch) const // Gets the node that would be hooked to this branch, // but doesn't hook it up. -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMap::descendNoStore(SHAMapInnerNode& parent, int branch) const { - intr_ptr::SharedPtr ret = parent.getChild(branch); + SHAMapTreeNodePtr ret = parent.getChild(branch); if (!ret && backed_) ret = fetchNode(parent.getChildHash(branch)); return ret; @@ -361,7 +358,7 @@ SHAMap::descend( if (child == nullptr) { auto const& childHash = parent->getChildHash(branch); - intr_ptr::SharedPtr childNode = fetchNodeNT(childHash, filter); + SHAMapTreeNodePtr childNode = fetchNodeNT(childHash, filter); if (childNode) { @@ -434,7 +431,7 @@ SHAMap::unshareNode(intr_ptr::SharedPtr node, SHAMapNodeID const& nodeID) SHAMapLeafNode* SHAMap::belowHelper( - intr_ptr::SharedPtr node, + SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch, std::tuple, std::function> const& loopParams) const @@ -479,8 +476,7 @@ SHAMap::belowHelper( return nullptr; } SHAMapLeafNode* -SHAMap::lastBelow(intr_ptr::SharedPtr node, SharedPtrNodeStack& stack, int branch) - const +SHAMap::lastBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch) const { auto init = kBranchFactor - 1; auto cmp = [](int i) { return i >= 0; }; @@ -489,8 +485,7 @@ SHAMap::lastBelow(intr_ptr::SharedPtr node, SharedPtrNodeStack& return belowHelper(node, stack, branch, {init, cmp, incr}); } SHAMapLeafNode* -SHAMap::firstBelow(intr_ptr::SharedPtr node, SharedPtrNodeStack& stack, int branch) - const +SHAMap::firstBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch) const { auto init = 0; auto cmp = [](int i) { return i <= kBranchFactor; }; @@ -699,10 +694,8 @@ SHAMap::delItem(uint256 const& id) SHAMapNodeType const type = leaf->getType(); - using TreeNodeType = intr_ptr::SharedPtr; - // What gets attached to the end of the chain (For now, nothing, since we deleted the leaf) - TreeNodeType prevNode; + SHAMapTreeNodePtr prevNode; while (!stack.empty()) { @@ -728,7 +721,7 @@ SHAMap::delItem(uint256 const& id) // no children below this branch // // Note: This is unnecessary due to the std::move above but left here for safety - prevNode = TreeNodeType{}; + prevNode = SHAMapTreeNodePtr{}; } else if (bc == 1) { @@ -741,7 +734,7 @@ SHAMap::delItem(uint256 const& id) { if (!node->isEmptyBranch(i)) { - node->setChild(i, TreeNodeType{}); + node->setChild(i, SHAMapTreeNodePtr{}); break; } } @@ -937,8 +930,8 @@ SHAMap::fetchRoot(SHAMapHash const& hash, SHAMapSyncFilter* filter) @note The node must have already been unshared by having the caller first call SHAMapTreeNode::unshare(). */ -intr_ptr::SharedPtr -SHAMap::writeNode(NodeObjectType t, intr_ptr::SharedPtr node) const +SHAMapTreeNodePtr +SHAMap::writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const { XRPL_ASSERT(node->cowid() == 0, "xrpl::SHAMap::writeNode : valid input node"); XRPL_ASSERT(backed_, "xrpl::SHAMap::writeNode : is backed"); @@ -1155,7 +1148,7 @@ SHAMap::dump(bool hash) const JLOG(journal_.info()) << leafCount << " resident leaves"; } -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMap::cacheLookup(SHAMapHash const& hash) const { auto ret = f_.getTreeNodeCache()->fetch(hash.asUInt256()); @@ -1164,7 +1157,7 @@ SHAMap::cacheLookup(SHAMapHash const& hash) const } void -SHAMap::canonicalize(SHAMapHash const& hash, intr_ptr::SharedPtr& node) const +SHAMap::canonicalize(SHAMapHash const& hash, SHAMapTreeNodePtr& node) const { XRPL_ASSERT(backed_, "xrpl::SHAMap::canonicalize : is backed"); XRPL_ASSERT(node->cowid() == 0, "xrpl::SHAMap::canonicalize : valid node input"); diff --git a/src/libxrpl/shamap/SHAMapDelta.cpp b/src/libxrpl/shamap/SHAMapDelta.cpp index b1aeac18e8..8336ce5481 100644 --- a/src/libxrpl/shamap/SHAMapDelta.cpp +++ b/src/libxrpl/shamap/SHAMapDelta.cpp @@ -261,7 +261,7 @@ SHAMap::walkMap(std::vector& missingNodes, int maxMissing) co { if (!node->isEmptyBranch(i)) { - intr_ptr::SharedPtr const nextNode = descendNoStore(*node, i); + SHAMapTreeNodePtr const nextNode = descendNoStore(*node, i); if (nextNode) { @@ -286,7 +286,7 @@ SHAMap::walkMapParallel(std::vector& missingNodes, int maxMis return false; using StackEntry = intr_ptr::SharedPtr; - std::array, 16> topChildren; + std::array topChildren; { auto const& innerRoot = intr_ptr::staticPointerCast(root_); for (int i = 0; i < 16; ++i) @@ -331,8 +331,7 @@ SHAMap::walkMapParallel(std::vector& missingNodes, int maxMis { if (node->isEmptyBranch(i)) continue; - intr_ptr::SharedPtr const nextNode = - descendNoStore(*node, i); + SHAMapTreeNodePtr const nextNode = descendNoStore(*node, i); if (nextNode) { diff --git a/src/libxrpl/shamap/SHAMapInnerNode.cpp b/src/libxrpl/shamap/SHAMapInnerNode.cpp index f31b75ad39..ee6ebf7f3f 100644 --- a/src/libxrpl/shamap/SHAMapInnerNode.cpp +++ b/src/libxrpl/shamap/SHAMapInnerNode.cpp @@ -37,7 +37,7 @@ SHAMapInnerNode::~SHAMapInnerNode() = default; void SHAMapInnerNode::partialDestructor() { - intr_ptr::SharedPtr* children = nullptr; + SHAMapTreeNodePtr* children = nullptr; // structured bindings can't be captured in c++ 17; use tie instead std::tie(std::ignore, std::ignore, children) = hashesAndChildren_.getHashesAndChildren(); iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) { children[indexNum].reset(); }); @@ -69,7 +69,7 @@ SHAMapInnerNode::getChildIndex(int i) const return hashesAndChildren_.getChildIndex(isBranch_, i); } -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMapInnerNode::clone(std::uint32_t cowid) const { auto const branchCount = getBranchCount(); @@ -78,8 +78,10 @@ SHAMapInnerNode::clone(std::uint32_t cowid) const p->hash_ = hash_; p->isBranch_ = isBranch_; p->fullBelowGen_ = fullBelowGen_; - SHAMapHash *cloneHashes = nullptr, *thisHashes = nullptr; - intr_ptr::SharedPtr*cloneChildren = nullptr, *thisChildren = nullptr; + SHAMapHash* cloneHashes = nullptr; + SHAMapHash* thisHashes = nullptr; + SHAMapTreeNodePtr* cloneChildren = nullptr; + SHAMapTreeNodePtr* thisChildren = nullptr; // structured bindings can't be captured in c++ 17; use tie instead std::tie(std::ignore, cloneHashes, cloneChildren) = p->hashesAndChildren_.getHashesAndChildren(); @@ -118,7 +120,7 @@ SHAMapInnerNode::clone(std::uint32_t cowid) const return p; } -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMapInnerNode::makeFullInner(Slice data, SHAMapHash const& hash, bool hashValid) { // A full inner node is serialized as 16 256-bit hashes, back to back: @@ -153,7 +155,7 @@ SHAMapInnerNode::makeFullInner(Slice data, SHAMapHash const& hash, bool hashVali return ret; } -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMapInnerNode::makeCompressedInner(Slice data) { // A compressed inner node is serialized as a series of 33 byte chunks, @@ -207,7 +209,7 @@ void SHAMapInnerNode::updateHashDeep() { SHAMapHash* hashes = nullptr; - intr_ptr::SharedPtr* children = nullptr; + SHAMapTreeNodePtr* children = nullptr; // structured bindings can't be captured in c++ 17; use tie instead std::tie(std::ignore, hashes, children) = hashesAndChildren_.getHashesAndChildren(); iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) { @@ -265,7 +267,7 @@ SHAMapInnerNode::getString(SHAMapNodeID const& id) const // We are modifying an inner node void -SHAMapInnerNode::setChild(int m, intr_ptr::SharedPtr child) +SHAMapInnerNode::setChild(int m, SHAMapTreeNodePtr child) { XRPL_ASSERT( (m >= 0) && (m < kBranchFactor), "xrpl::SHAMapInnerNode::setChild : valid branch input"); @@ -307,7 +309,7 @@ SHAMapInnerNode::setChild(int m, intr_ptr::SharedPtr child) // finished modifying, now make shareable void -SHAMapInnerNode::shareChild(int m, intr_ptr::SharedPtr const& child) +SHAMapInnerNode::shareChild(int m, SHAMapTreeNodePtr const& child) { XRPL_ASSERT( (m >= 0) && (m < kBranchFactor), "xrpl::SHAMapInnerNode::shareChild : valid branch input"); @@ -337,7 +339,7 @@ SHAMapInnerNode::getChildPointer(int branch) return hashesAndChildren_.getChildren()[index].get(); } -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMapInnerNode::getChild(int branch) { XRPL_ASSERT( @@ -365,8 +367,8 @@ SHAMapInnerNode::getChildHash(int m) const return kZeroShaMapHash; } -intr_ptr::SharedPtr -SHAMapInnerNode::canonicalizeChild(int branch, intr_ptr::SharedPtr node) +SHAMapTreeNodePtr +SHAMapInnerNode::canonicalizeChild(int branch, SHAMapTreeNodePtr node) { XRPL_ASSERT( branch >= 0 && branch < kBranchFactor, diff --git a/src/libxrpl/shamap/SHAMapSync.cpp b/src/libxrpl/shamap/SHAMapSync.cpp index cd2654c603..0601bfefda 100644 --- a/src/libxrpl/shamap/SHAMapSync.cpp +++ b/src/libxrpl/shamap/SHAMapSync.cpp @@ -66,7 +66,7 @@ SHAMap::visitNodes(std::function const& function) const { if (!node->isEmptyBranch(pos)) { - intr_ptr::SharedPtr const child = descendNoStore(*node, pos); + SHAMapTreeNodePtr const child = descendNoStore(*node, pos); if (!function(*child)) return; @@ -204,8 +204,7 @@ SHAMap::gmnProcessNodes(MissingNodes& mn, MissingNodes::StackEntry& se) branch, mn.filter, pending, - [node, nodeID, branch, &mn]( - intr_ptr::SharedPtr found, SHAMapHash const&) { + [node, nodeID, branch, &mn](SHAMapTreeNodePtr found, SHAMapHash const&) { // a read completed asynchronously std::unique_lock const lock{mn.deferLock}; mn.finishedReads.emplace_back(node, nodeID, branch, std::move(found)); @@ -266,8 +265,7 @@ SHAMap::gmnProcessDeferredReads(MissingNodes& mn) int complete = 0; while (complete != mn.deferred) { - std::tuple> - deferredNode; + std::tuple deferredNode; { std::unique_lock lock{mn.deferLock}; diff --git a/src/libxrpl/shamap/SHAMapTreeNode.cpp b/src/libxrpl/shamap/SHAMapTreeNode.cpp index 3b8d976c69..1ae7cf18af 100644 --- a/src/libxrpl/shamap/SHAMapTreeNode.cpp +++ b/src/libxrpl/shamap/SHAMapTreeNode.cpp @@ -25,7 +25,7 @@ namespace xrpl { -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMapTreeNode::makeTransaction(Slice data, SHAMapHash const& hash, bool hashValid) { if (data.size() < kMinShaMapItemBytes) @@ -43,7 +43,7 @@ SHAMapTreeNode::makeTransaction(Slice data, SHAMapHash const& hash, bool hashVal return intr_ptr::makeShared(std::move(item), 0); } -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMapTreeNode::makeTransactionWithMeta(Slice data, SHAMapHash const& hash, bool hashValid) { Serializer s(data.data(), data.size()); @@ -83,7 +83,7 @@ SHAMapTreeNode::makeTransactionWithMeta(Slice data, SHAMapHash const& hash, bool return intr_ptr::makeShared(std::move(item), 0); } -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMapTreeNode::makeAccountState(Slice data, SHAMapHash const& hash, bool hashValid) { Serializer s(data.data(), data.size()); @@ -124,7 +124,7 @@ SHAMapTreeNode::makeAccountState(Slice data, SHAMapHash const& hash, bool hashVa return intr_ptr::makeShared(std::move(item), 0); } -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMapTreeNode::makeFromWire(Slice rawNode) { if (rawNode.empty()) @@ -155,7 +155,7 @@ SHAMapTreeNode::makeFromWire(Slice rawNode) Throw("wire: Unknown type (" + std::to_string(type) + ")"); } -intr_ptr::SharedPtr +SHAMapTreeNodePtr SHAMapTreeNode::makeFromPrefix(Slice rawNode, SHAMapHash const& hash) { if (rawNode.size() < 4) From 023bdaeeedc6ebcff3a2eef136d0f2bdac4a8296 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 3 Jun 2026 20:14:17 +0100 Subject: [PATCH 057/135] ci: Install gcov, nettools, cacert in nix images (#7398) --- docker/{check-tool-versions.sh => check-tools.sh} | 8 ++++++++ docker/nix.Dockerfile | 10 ++++++++-- nix/ci-env.nix | 14 ++++++++++++++ nix/packages.nix | 1 + 4 files changed, 31 insertions(+), 2 deletions(-) rename docker/{check-tool-versions.sh => check-tools.sh} (59%) diff --git a/docker/check-tool-versions.sh b/docker/check-tools.sh similarity index 59% rename from docker/check-tool-versions.sh rename to docker/check-tools.sh index db20be45e4..faa4586832 100755 --- a/docker/check-tool-versions.sh +++ b/docker/check-tools.sh @@ -10,11 +10,13 @@ cmake --version conan --version g++ --version gcc --version +gcov --version gcovr --version git --version less --version make --version mold --version +netstat --version ninja --version perl --version pkg-config --version @@ -22,3 +24,9 @@ pre-commit --version python3 --version run-clang-tidy --help vim --version + +# A simple test to verify that git can clone a repository over HTTPS +# (i.e. the CA bundle is wired up). Clone to a temp dir and clean up. +tmp_clone="$(mktemp -d)" +git clone --depth 1 https://github.com/XRPLF/actions.git "${tmp_clone}/actions" +rm -rf "${tmp_clone}" diff --git a/docker/nix.Dockerfile b/docker/nix.Dockerfile index a0eab31769..6248708417 100644 --- a/docker/nix.Dockerfile +++ b/docker/nix.Dockerfile @@ -47,6 +47,12 @@ COPY --from=builder /tmp/build/result /nix/ci-env ENV PATH="/nix/ci-env/bin:${PATH}" +# Point HTTPS clients (git, curl, conan, ...) at the CA bundle shipped in the +# Nix CI environment, so TLS verification works without ca-certificates being +# installed in the system. +ENV SSL_CERT_FILE="/nix/ci-env/etc/ssl/certs/ca-bundle.crt" +ENV GIT_SSL_CAINFO="/nix/ci-env/etc/ssl/certs/ca-bundle.crt" + # Externally-built dynamically-linked ELF binaries hard-code the loader path # (e.g. /lib64/ld-linux-x86-64.so.2) in their PT_INTERP header. Install it # from the Nix store when the base image doesn't already provide one. @@ -65,8 +71,8 @@ if [ ! -e "${target}" ]; then fi EOF -COPY docker/check-tool-versions.sh /tmp/check-tool-versions.sh -RUN /tmp/check-tool-versions.sh +COPY docker/check-tools.sh /tmp/check-tools.sh +RUN /tmp/check-tools.sh # Sanity-check that the g++/clang++ are able to build binaries, including sanitizer-instrumented ones. COPY docker/test_files/cpp_sources/ /tmp/cpp_sources/ diff --git a/nix/ci-env.nix b/nix/ci-env.nix index 0d617913d9..0ef7410250 100644 --- a/nix/ci-env.nix +++ b/nix/ci-env.nix @@ -43,6 +43,15 @@ let bintools = customBinutils; }; + # gcov ships in gcc's `cc` output, but the cc-wrapper doesn't expose it. + # Surface the gcov from our rebuilt gcc (linked against the custom glibc, so + # it runs under the loader installed in the image) and matching the exact + # compiler version, so gcovr can produce coverage reports in the CI env. + customGcov = pkgs.runCommand "gcov-custom-for-ci-env" { } '' + mkdir -p "$out/bin" + ln -s "${customGccCc}/bin/gcov" "$out/bin/gcov" + ''; + # stdenv built around the rebuilt gcc / custom glibc. Used to rebuild # compiler-rt below so its sanitizer runtimes see the custom glibc # headers. @@ -105,11 +114,16 @@ in name = "xrpld-ci-env"; paths = commonPackages ++ [ customGcc + customGcov customClangForCiEnv customBinutils + # CA certificate bundle so HTTPS clients (git, curl, conan) can verify + # TLS connections without ca-certificates being installed in the system. + pkgs.cacert ]; pathsToLink = [ "/bin" + "/etc/ssl/certs" "/lib" "/include" "/share" diff --git a/nix/packages.nix b/nix/packages.nix index 3d92fedb4b..b608677aea 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -17,6 +17,7 @@ in llvmPackages_22.clang-tools less # needed for git diff mold + nettools # provides netstat, used to debug failures in CI ninja patchelf perl # needed for openssl From e5cf1a0985ee27e5519fa5de1d0a664c57b73d1a Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Wed, 3 Jun 2026 15:30:20 -0400 Subject: [PATCH 058/135] fix: Add zero NFT Offer ID check for NFTokenCancelOffer (#7391) --- .../tx/transactors/nft/NFTokenCancelOffer.cpp | 11 +++++++++-- src/test/app/NFToken_test.cpp | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp index 1614f90202..d04714907e 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -21,8 +22,14 @@ namespace xrpl { NotTEC NFTokenCancelOffer::preflight(PreflightContext const& ctx) { - if (auto const& ids = ctx.tx[sfNFTokenOffers]; - ids.empty() || (ids.size() > kMaxTokenOfferCancelCount)) + auto const& offerIds = ctx.tx[sfNFTokenOffers]; + + if (offerIds.empty() || (offerIds.size() > kMaxTokenOfferCancelCount)) + return temMALFORMED; + + // Zero offer IDs cannot be passed as ledger entry keys. + if (ctx.rules.enabled(fixCleanup3_2_0) && + std::ranges::any_of(offerIds, [](uint256 const& id) { return id.isZero(); })) return temMALFORMED; // In order to prevent unnecessarily overlarge transactions, we diff --git a/src/test/app/NFToken_test.cpp b/src/test/app/NFToken_test.cpp index ebd470ec92..269bc72c53 100644 --- a/src/test/app/NFToken_test.cpp +++ b/src/test/app/NFToken_test.cpp @@ -892,6 +892,25 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite BEAST_EXPECT(ownerCount(env, buyer) == 1); } + // Only test this with fixCleanup3_2_0 enabled. Without the fix, + // an assert-enabled build can crash when Ledger::read() receives + // a zero-key offer ID. + if (features[fixCleanup3_2_0]) + { + // Zero is not a valid offer ID. + env(token::cancelOffer(buyer, {uint256{}}), Ter(temMALFORMED)); + env.close(); + BEAST_EXPECT(ownerCount(env, buyer) == 1); + + // List of offer IDs containing zero is invalid. + // craftedIndex is not a valid offer index but it is not zero. + auto const craftedIndex = keylet::nftoffer(gw, env.seq(gw)).key; + env(token::cancelOffer(buyer, {buyerOfferIndex, uint256{}, craftedIndex}), + Ter(temMALFORMED)); + env.close(); + BEAST_EXPECT(ownerCount(env, buyer) == 1); + } + // List of tokens to delete is too long. { std::vector const offers(kMaxTokenOfferCancelCount + 1, buyerOfferIndex); From 6c543426c3461f98d62146a445dada2f47516a93 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 3 Jun 2026 23:19:15 +0100 Subject: [PATCH 059/135] ci: Fix clang asan include dirs in nix images, add curl & gnupg (#7400) --- cspell.config.yaml | 1 + docker/check-tools.sh | 2 ++ docker/test_files/cpp_sources/asan.cpp | 7 +++++++ nix/ci-env.nix | 8 ++++++++ nix/packages.nix | 2 ++ 5 files changed, 20 insertions(+) diff --git a/cspell.config.yaml b/cspell.config.yaml index ed936941e4..da5dc9b072 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -134,6 +134,7 @@ words: - iou - ious - isrdc + - isystem - itype - jemalloc - jlog diff --git a/docker/check-tools.sh b/docker/check-tools.sh index faa4586832..eb72e8f357 100755 --- a/docker/check-tools.sh +++ b/docker/check-tools.sh @@ -8,11 +8,13 @@ clang++ --version clang-format --version cmake --version conan --version +curl --version g++ --version gcc --version gcov --version gcovr --version git --version +gpg --version less --version make --version mold --version diff --git a/docker/test_files/cpp_sources/asan.cpp b/docker/test_files/cpp_sources/asan.cpp index 8347f58d37..deefdec79a 100644 --- a/docker/test_files/cpp_sources/asan.cpp +++ b/docker/test_files/cpp_sources/asan.cpp @@ -2,6 +2,13 @@ #include #include +// Regression test: the compiler-rt sanitizer interface headers must be on the +// include path. A bare on-PATH clang in the Nix CI env doesn't get them +// propagated automatically, so this include would fail to compile with clang++ +// if the env isn't wired up correctly. abseil hits the same include during +// sanitizer builds. LeakSanitizer ships with AddressSanitizer. +#include + #if defined(__clang__) || defined(__GNUC__) __attribute__((noinline)) #elif defined(_MSC_VER) diff --git a/nix/ci-env.nix b/nix/ci-env.nix index 0ef7410250..f823f71de0 100644 --- a/nix/ci-env.nix +++ b/nix/ci-env.nix @@ -94,6 +94,14 @@ let ln -s "${customCompilerRt.out}/lib" "$rsrc/lib" ln -s "${customCompilerRt.out}/share" "$rsrc/share" || true echo "-resource-dir=$rsrc" >> $out/nix-support/cc-cflags + # compiler-rt ships the sanitizer/profile/xray interface headers (e.g. + # ) in its `dev` output. In a normal Nix + # build these reach the include path because compiler-rt is propagated + # via depsTargetTargetPropagated and stdenv's setup hooks add its + # dev/include. The CI image runs clang outside a Nix stdenv (binaries + # on PATH, no setup hooks), so that never happens; add the headers + # explicitly. gcc ships its own copy, which is why this is clang-only. + echo "-isystem ${customCompilerRt.dev}/include" >> $out/nix-support/cc-cflags ''; }; diff --git a/nix/packages.nix b/nix/packages.nix index b608677aea..f282c15df9 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -11,9 +11,11 @@ in ccache cmake conan + curlMinimal # needed for codecov/codecov-action gcovr git gnumake + gnupg # needed for signing commits & codecov/codecov-action llvmPackages_22.clang-tools less # needed for git diff mold From 12e81abef3aa4c05bc2f21ea1be49644cb7d478a Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 4 Jun 2026 15:52:42 +0100 Subject: [PATCH 060/135] ci: Improve sanitizer-libs, add doxygen, dpkg, rpm in nix (#7403) --- docker/check-tools.sh | 3 + docker/install-sanitizer-libs.sh | 124 ++++++++++++++++++------------- nix/packages.nix | 3 + 3 files changed, 80 insertions(+), 50 deletions(-) diff --git a/docker/check-tools.sh b/docker/check-tools.sh index eb72e8f357..c446dc1b4a 100755 --- a/docker/check-tools.sh +++ b/docker/check-tools.sh @@ -9,6 +9,8 @@ clang-format --version cmake --version conan --version curl --version +doxygen --version +dpkg-buildpackage --version g++ --version gcc --version gcov --version @@ -24,6 +26,7 @@ perl --version pkg-config --version pre-commit --version python3 --version +rpmbuild --version run-clang-tidy --help vim --version diff --git a/docker/install-sanitizer-libs.sh b/docker/install-sanitizer-libs.sh index a28efeab3f..dc1ba1b350 100755 --- a/docker/install-sanitizer-libs.sh +++ b/docker/install-sanitizer-libs.sh @@ -27,63 +27,87 @@ fi echo "Detected OS: ${ID} ${VERSION_ID:-}" case "${ID}" in - debian) - apt-get update -y - apt-get install -y --no-install-recommends \ - libasan8 \ - libtsan2 \ - libubsan1 - - apt-get clean - rm -rf /var/lib/apt/lists/* + ubuntu | debian | rhel | centos | rocky | almalinux) + echo "Supported OS detected: ${ID}" ;; - - ubuntu) - apt-get update -y - apt-get install -y --no-install-recommends \ - gnupg \ - software-properties-common - add-apt-repository -y ppa:ubuntu-toolchain-r/test - apt-get update -y - apt-get install -y --no-install-recommends \ - libasan8 \ - libtsan2 \ - libubsan1 - - apt-get clean - rm -rf /var/lib/apt/lists/* - ;; - - rhel | centos | rocky | almalinux) - dnf install -y \ - libasan8 \ - libtsan2 \ - libubsan - - dnf clean -y all - rm -rf /var/cache/dnf/* - ;; - *) echo "ERROR: unsupported OS '${ID}'. Supported: debian, ubuntu, rhel-family" >&2 exit 1 ;; esac -# Verify that every expected library is now resolvable by the dynamic linker. -missing=0 -for lib in libasan.so.8 libtsan.so.2 libubsan.so.1; do - if ldconfig -p | grep -q "${lib}"; then - echo "OK: ${lib} found" - else - echo "ERROR: ${lib} not found after installation" >&2 - missing=$((missing + 1)) - fi -done +function preinstall() { + case "${ID}" in + ubuntu) + apt-get update -y + apt-get install -y --no-install-recommends \ + gnupg \ + software-properties-common + add-apt-repository -y ppa:ubuntu-toolchain-r/test + ;; + esac +} -if [ "${missing}" -ne 0 ]; then - echo "ERROR: ${missing} library/libraries missing" >&2 - exit 1 -fi +function install() { + case "${ID}" in + debian | ubuntu) + apt-get update -y + apt-get install -y --no-install-recommends \ + libasan8 \ + libtsan2 \ + libubsan1 + ;; + + rhel | centos | rocky | almalinux) + dnf install -y \ + libasan8 \ + libtsan2 \ + libubsan + ;; + esac +} + +function postinstall() { + # Don't clear cache in non-CI environments + if [ -z "${CI:-}" ]; then + echo "Not running in CI environment; skipping cache cleanup" + return + fi + + case "${ID}" in + debian | ubuntu) + apt-get clean + rm -rf /var/lib/apt/lists/* + ;; + + rhel | centos | rocky | almalinux) + dnf clean -y all + rm -rf /var/cache/dnf/* + ;; + esac +} + +function verify() { + # Verify that every expected library is now resolvable by the dynamic linker. + missing=0 + for lib in libasan.so.8 libtsan.so.2 libubsan.so.1; do + if ldconfig -p | grep -q "${lib}"; then + echo "OK: ${lib} found" + else + echo "ERROR: ${lib} not found after installation" >&2 + missing=$((missing + 1)) + fi + done + + if [ "${missing}" -ne 0 ]; then + echo "ERROR: ${missing} library/libraries missing" >&2 + exit 1 + fi +} + +preinstall +install +postinstall +verify echo "All sanitizer runtime libraries installed successfully." diff --git a/nix/packages.nix b/nix/packages.nix index f282c15df9..c51077367e 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -12,6 +12,8 @@ in cmake conan curlMinimal # needed for codecov/codecov-action + doxygen + dpkg # needed for dpkg-buildpackage gcovr git gnumake @@ -26,6 +28,7 @@ in pkg-config pre-commit python3 + rpm # needed for rpmbuild runClangTidy vim ]; From 5b8e6cd1dd6796c94cf471c223028d817cbc3906 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 4 Jun 2026 20:35:36 +0100 Subject: [PATCH 061/135] test: Fix LCOV_EXCL_END -> LCOV_EXCL_STOP (#7407) --- src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp index cfcf79fdba..05dcfea506 100644 --- a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp @@ -300,7 +300,7 @@ VaultWithdraw::doApply() << "VaultWithdraw: " // "Cannot burn all outstanding shares while unrealized loss is non-zero"; return tefINTERNAL; - // LCOV_EXCL_END + // LCOV_EXCL_STOP } STAmount const allAvailable{vaultAsset, *assetsAvailable}; From 8abe82eefa2357777af075d252ea8613f2948ae6 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 4 Jun 2026 21:02:59 +0100 Subject: [PATCH 062/135] ci: Redesign matrix configuration based on Nix images (#7385) Co-authored-by: semgrep-companion-app[bot] <218312740+semgrep-companion-app[bot]@users.noreply.github.com> --- .github/actions/build-deps/action.yml | 3 +- .github/actions/set-compiler-env/action.yml | 34 + .github/actions/setup-conan/action.yml | 25 +- .github/scripts/strategy-matrix/generate.py | 579 +++++++----------- .github/scripts/strategy-matrix/linux.json | 300 +++------ .github/scripts/strategy-matrix/macos.json | 24 +- .github/scripts/strategy-matrix/windows.json | 23 +- .github/workflows/on-tag.yml | 1 - .github/workflows/on-trigger.yml | 1 - .../workflows/reusable-build-test-config.yml | 49 +- .github/workflows/reusable-build-test.yml | 12 +- .github/workflows/reusable-package.yml | 39 +- .../workflows/reusable-strategy-matrix.yml | 15 +- .github/workflows/upload-conan-deps.yml | 11 +- cmake/XrplCompiler.cmake | 32 +- conan/profiles/ci | 7 + cspell.config.yaml | 1 + docker/check-tools.sh | 2 - nix/packages.nix | 2 - 19 files changed, 512 insertions(+), 648 deletions(-) create mode 100644 .github/actions/set-compiler-env/action.yml diff --git a/.github/actions/build-deps/action.yml b/.github/actions/build-deps/action.yml index 0891d56dfa..044c264ef0 100644 --- a/.github/actions/build-deps/action.yml +++ b/.github/actions/build-deps/action.yml @@ -35,9 +35,8 @@ runs: LOG_VERBOSITY: ${{ inputs.log_verbosity }} SANITIZERS: ${{ inputs.sanitizers }} run: | - echo 'Installing dependencies.' conan install \ - --profile ci \ + --profile:all ci \ --build="${BUILD_OPTION}" \ --options:host='&:tests=True' \ --options:host='&:xrpld=True' \ diff --git a/.github/actions/set-compiler-env/action.yml b/.github/actions/set-compiler-env/action.yml new file mode 100644 index 0000000000..a16dde2b30 --- /dev/null +++ b/.github/actions/set-compiler-env/action.yml @@ -0,0 +1,34 @@ +name: Set compiler environment +description: "Set CC and CXX environment variables for the given compiler." + +inputs: + compiler: + description: 'The compiler to use ("gcc" or "clang").' + required: true + +runs: + using: composite + + steps: + - name: Set CC and CXX for gcc + if: ${{ inputs.compiler == 'gcc' }} + shell: bash + run: | + echo "CC=gcc" >>"${GITHUB_ENV}" + echo "CXX=g++" >>"${GITHUB_ENV}" + + - name: Set CC and CXX for clang + if: ${{ inputs.compiler == 'clang' }} + shell: bash + run: | + echo "CC=clang" >>"${GITHUB_ENV}" + echo "CXX=clang++" >>"${GITHUB_ENV}" + + - name: Fail on unknown compiler + if: ${{ inputs.compiler != 'gcc' && inputs.compiler != 'clang' }} + shell: bash + env: + COMPILER: ${{ inputs.compiler }} + run: | + echo "Unknown compiler: $COMPILER" >&2 + exit 1 diff --git a/.github/actions/setup-conan/action.yml b/.github/actions/setup-conan/action.yml index 9d834884d2..0dd22f0d92 100644 --- a/.github/actions/setup-conan/action.yml +++ b/.github/actions/setup-conan/action.yml @@ -15,32 +15,35 @@ runs: using: composite steps: - - name: Set up Conan configuration + - name: Apply custom configuration to global.conf shell: bash run: | - echo 'Installing configuration.' cat conan/global.conf ${{ runner.os == 'Linux' && '>>' || '>' }} $(conan config home)/global.conf - echo 'Conan configuration:' - conan config show '*' - - - name: Set up Conan profile + - name: Show global configuration + shell: bash + run: | + conan config show '*' + + - name: Install profiles shell: bash run: | - echo 'Installing profile.' conan config install conan/profiles/ -tf $(conan config home)/profiles/ - echo 'Conan profile:' + - name: Show CI profile + shell: bash + run: | conan profile show --profile ci - - name: Set up Conan remote + - name: Add a remote shell: bash env: REMOTE_NAME: ${{ inputs.remote_name }} REMOTE_URL: ${{ inputs.remote_url }} run: | - echo "Adding Conan remote '${REMOTE_NAME}' at '${REMOTE_URL}'." conan remote add --index 0 --force "${REMOTE_NAME}" "${REMOTE_URL}" - echo 'Listing Conan remotes.' + - name: List remotes + shell: bash + run: | conan remote list diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index 6eccfcc6be..aaf84a51d0 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -1,384 +1,281 @@ #!/usr/bin/env python3 import argparse +import dataclasses import itertools import json -from dataclasses import dataclass from pathlib import Path THIS_DIR = Path(__file__).parent.resolve() +_BASE_CMAKE_ARGS = ["-Dtests=ON", "-Dwerr=ON", "-Dxrpld=ON", "-Dwextra=ON"] -@dataclass -class Config: - architecture: list[dict] - os: list[dict] +# Maps sanitizer names (as used in cmake) to short config-name suffixes. +_SANITIZER_SUFFIX: dict[str, str] = { + "address": "asan", + "undefinedbehavior": "ubsan", + "thread": "tsan", +} + + +def get_cmake_args(build_type: str, extra_args: str) -> str: + """Get the full list of CMake arguments for a config.""" + args = _BASE_CMAKE_ARGS.copy() + if build_type == "Release": + args.append("-Dassert=ON") + if extra_args: + args.extend(extra_args.split()) + return " ".join(args) + + +# --------------------------------------------------------------------------- +# Input types — shapes of the JSON config files +# --------------------------------------------------------------------------- + + +@dataclasses.dataclass +class LinuxConfig: + """One entry in linux.json's 'configs' or 'package_configs' arrays.""" + + compiler: list[str] build_type: list[str] - cmake_args: list[str] + arch: list[str] + sanitizers: list[str] = dataclasses.field(default_factory=list) + suffix: str = "" + extra_cmake_args: str = "" + image: str = "" # only used by package_configs entries -""" -Generate a strategy matrix for GitHub Actions CI. +@dataclasses.dataclass +class LinuxFile: + """Shape of linux.json.""" -On each PR commit we will build a selection of Debian, RHEL, Ubuntu, MacOS, and -Windows configurations, while upon merge into the develop or release branches, -we will build all configurations, and test most of them. + image_tag: str + configs: dict[str, list[LinuxConfig]] # distro → configs + package_configs: dict[str, list[LinuxConfig]] # distro → packaging configs -We will further set additional CMake arguments as follows: -- All builds will have the `tests`, `werr`, and `xrpld` options. -- All builds will have the `wextra` option except for GCC 12 and Clang 16. -- All release builds will have the `assert` option. -- Certain Debian Bookworm configurations will change the reference fee, enable - codecov, and enable voidstar in PRs. -""" + @classmethod + def load(cls, path: Path) -> "LinuxFile": + data = json.loads(path.read_text()) + + def parse(section: dict) -> dict[str, list[LinuxConfig]]: + return { + distro: [LinuxConfig(**c) for c in cfgs] + for distro, cfgs in section.items() + } + + return cls( + image_tag=data["image_tag"], + configs=parse(data["configs"]), + package_configs=parse(data.get("package_configs", {})), + ) -def build_config_name(os_entry: dict[str, str], platform: str, build_type: str) -> str: - parts = [os_entry["distro_name"]] - for key in ("distro_version", "compiler_name", "compiler_version"): - if value := os_entry[key]: - parts.append(value) - parts.append("arm64" if "arm64" in platform else "amd64") - parts.append(build_type.lower()) - return "-".join(parts) +@dataclasses.dataclass +class PlatformConfig: + """One entry in macos.json's or windows.json's 'configs' array.""" + + build_type: list[str] + build_only: bool = False # if true, skip tests (e.g. macos/Windows Debug) + extra_cmake_args: str = "" + + def __post_init__(self) -> None: + if isinstance(self.build_type, str): + self.build_type = [self.build_type] -def generate_packaging_matrix(config: Config) -> list[dict]: - """Emit one entry per os entry with `package: true`. Architecture is - hardcoded to linux/amd64 here (and the runner is hardcoded at the - workflow level) until arm64 packaging is ready. +@dataclasses.dataclass +class PlatformFile: + """Shape of macos.json and windows.json.""" + + platform: str # e.g. "macos/arm64" or "windows/amd64" + runner: list[str] # GitHub Actions runner labels + configs: list[PlatformConfig] + + @classmethod + def load(cls, path: Path) -> "PlatformFile": + data = json.loads(path.read_text()) + return cls( + platform=data["platform"], + runner=data["runner"], + configs=[PlatformConfig(**c) for c in data["configs"]], + ) + + +# --------------------------------------------------------------------------- +# Output types — shapes of the generated GitHub Actions matrix entries +# --------------------------------------------------------------------------- + + +@dataclasses.dataclass +class Architecture: + platform: str + runner: list[str] + + +@dataclasses.dataclass +class MatrixEntry: + """One entry in the generated build/test strategy matrix.""" + + config_name: str + cmake_args: str + cmake_target: str + build_only: bool + build_type: str + architecture: Architecture + sanitizers: str + image: str = "" # container image; empty for macOS/Windows (runs natively) + compiler: str = "" # compiler name ("gcc" or "clang"); empty for macOS/Windows + + +@dataclasses.dataclass +class PackagingEntry: + """One entry in the generated packaging strategy matrix.""" + + artifact_name: str + image: str + distro: str # e.g. "debian" or "rhel"; drives package-format-specific steps + + +# --------------------------------------------------------------------------- +# Matrix expansion +# --------------------------------------------------------------------------- + +_ARCHS: dict[str, Architecture] = { + "amd64": Architecture( + platform="linux/amd64", runner=["self-hosted", "Linux", "X64", "heavy"] + ), + "arm64": Architecture( + platform="linux/arm64", + runner=["self-hosted", "Linux", "ARM64", "heavy-arm64"], + ), +} + + +def expand_linux_matrix(linux: LinuxFile) -> list[MatrixEntry]: + """Expand a LinuxFile into a flat list of matrix entries. + + Each config entry is expanded over the cross-product of its + compiler, build_type, sanitizers, and architecture lists. """ - return [ - { - "artifact_name": f"xrpld-{build_config_name(os, 'linux/amd64', 'Release')}", - "os": os, - } - for os in config.os - if os.get("package", False) - ] + entries: list[MatrixEntry] = [] + for distro, configs in linux.configs.items(): + for cfg in configs: + # An empty sanitizers list means "one entry with no sanitizer". + effective_sanitizers = cfg.sanitizers or [""] + effective_archs = {arch: _ARCHS[arch] for arch in cfg.arch} -def generate_strategy_matrix(all: bool, config: Config) -> list[dict]: - configurations = [] - for architecture, os, build_type, cmake_args in itertools.product( - config.architecture, config.os, config.build_type, config.cmake_args - ): - # The default CMake target is 'all' for Linux and MacOS and 'install' - # for Windows, but it can get overridden for certain configurations. - cmake_target = "install" if os["distro_name"] == "windows" else "all" - - # We build and test all configurations by default, except for Windows in - # Debug, because it is too slow, as well as when code coverage is - # enabled as that mode already runs the tests. - build_only = False - if os["distro_name"] == "windows" and build_type == "Debug": - build_only = True - - # Only generate a subset of configurations in PRs. - if not all: - # Debian: - # - Bookworm using GCC 13: Debug on linux/amd64, set the reference - # fee to 500 and enable code coverage (which will be done below). - # - Bookworm using GCC 15: Debug on linux/amd64, enable Address and - # UB sanitizers (which will be done below). - # - Bookworm using Clang 16: Debug on linux/amd64, enable voidstar. - # - Bookworm using Clang 17: Release on linux/amd64, set the - # reference fee to 1000. - # - Bookworm using Clang 20: Debug on linux/amd64, enable Address - # and UB sanitizers (which will be done below). - if os["distro_name"] == "debian": - skip = True - if os["distro_version"] == "bookworm": - if ( - f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-13" - and build_type == "Debug" - and architecture["platform"] == "linux/amd64" - ): - cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=500 {cmake_args}" - skip = False - if ( - f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15" - and build_type == "Release" - and architecture["platform"] == "linux/amd64" - ): - skip = False - if ( - f"{os['compiler_name']}-{os['compiler_version']}" == "clang-16" - and build_type == "Debug" - and architecture["platform"] == "linux/amd64" - ): - cmake_args = f"-Dvoidstar=ON {cmake_args}" - skip = False - if ( - f"{os['compiler_name']}-{os['compiler_version']}" == "clang-17" - and build_type == "Release" - and architecture["platform"] == "linux/amd64" - ): - cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=1000 {cmake_args}" - skip = False - elif os["distro_version"] == "trixie": - if ( - f"{os['compiler_name']}-{os['compiler_version']}" == "clang-22" - and build_type == "Debug" - and architecture["platform"] == "linux/amd64" - ): - skip = False - if skip: - continue - - # RHEL: - # - 9 using GCC 12: Debug and Release on linux/amd64 - # (Release is required for RPM packaging). - # - 10 using Clang: Release on linux/amd64. - if os["distro_name"] == "rhel": - skip = True - if os["distro_version"] == "9": - if ( - f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12" - and build_type in ["Debug", "Release"] - and architecture["platform"] == "linux/amd64" - ): - skip = False - elif os["distro_version"] == "10": - if ( - f"{os['compiler_name']}-{os['compiler_version']}" == "clang-any" - and build_type == "Release" - and architecture["platform"] == "linux/amd64" - ): - skip = False - if skip: - continue - - # Ubuntu: - # - Jammy using GCC 12: Debug on linux/arm64, Release on - # linux/amd64 (Release is required for DEB packaging). - # - Noble using GCC 14: Release on linux/amd64. - # - Noble using Clang 18: Debug on linux/amd64. - # - Noble using Clang 19: Release on linux/arm64. - if os["distro_name"] == "ubuntu": - skip = True - if os["distro_version"] == "jammy": - if ( - f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12" - and build_type == "Debug" - and architecture["platform"] == "linux/arm64" - ): - skip = False - if ( - f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12" - and build_type == "Release" - and architecture["platform"] == "linux/amd64" - ): - skip = False - elif os["distro_version"] == "noble": - if ( - f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-14" - and build_type == "Release" - and architecture["platform"] == "linux/amd64" - ): - skip = False - if ( - f"{os['compiler_name']}-{os['compiler_version']}" == "clang-18" - and build_type == "Debug" - and architecture["platform"] == "linux/amd64" - ): - skip = False - if ( - f"{os['compiler_name']}-{os['compiler_version']}" == "clang-19" - and build_type == "Release" - and architecture["platform"] == "linux/arm64" - ): - skip = False - if skip: - continue - - # MacOS: - # - Debug on macos/arm64. - if os["distro_name"] == "macos" and not ( - build_type == "Debug" and architecture["platform"] == "macos/arm64" + for compiler, build_type, sanitizer, (arch, arch_info) in itertools.product( + cfg.compiler, + cfg.build_type, + effective_sanitizers, + effective_archs.items(), ): - continue + name = f"{distro}-{compiler}-{build_type.lower()}-{arch}" + suffix_parts = [ + s for s in [cfg.suffix, _SANITIZER_SUFFIX.get(sanitizer, "")] if s + ] + if suffix_parts: + name += "-" + "-".join(suffix_parts) - # Windows: - # - Release on windows/amd64. - if os["distro_name"] == "windows" and not ( - build_type == "Release" and architecture["platform"] == "windows/amd64" - ): - continue - - # Additional CMake arguments. - cmake_args = f"{cmake_args} -Dtests=ON -Dwerr=ON -Dxrpld=ON" - if not f"{os['compiler_name']}-{os['compiler_version']}" in [ - "gcc-12", - "clang-16", - ]: - cmake_args = f"{cmake_args} -Dwextra=ON" - if build_type == "Release": - cmake_args = f"{cmake_args} -Dassert=ON" - - # We skip all RHEL on arm64 due to a build failure that needs further - # investigation. - if os["distro_name"] == "rhel" and architecture["platform"] == "linux/arm64": - continue - - # We skip all clang 20+ on arm64 due to Boost build error. - if ( - os["compiler_name"] == "clang" - and os["compiler_version"].isdigit() - and int(os["compiler_version"]) >= 20 - and architecture["platform"] == "linux/arm64" - ): - continue - - # Enable code coverage for Debian Bookworm using GCC 13 in Debug on - # linux/amd64. - if ( - f"{os['distro_name']}-{os['distro_version']}" == "debian-bookworm" - and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-13" - and build_type == "Debug" - and architecture["platform"] == "linux/amd64" - ): - cmake_args = f"{cmake_args} -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0" - - # Enable unity build for Ubuntu Jammy using GCC 12 in Debug on - # linux/amd64. - if ( - f"{os['distro_name']}-{os['distro_version']}" == "ubuntu-jammy" - and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12" - and build_type == "Debug" - and architecture["platform"] == "linux/amd64" - ): - cmake_args = f"{cmake_args} -Dunity=ON" - - # Generate a unique name for the configuration, e.g. macos-arm64-debug - # or debian-bookworm-gcc-12-amd64-release. - config_name = build_config_name(os, architecture["platform"], build_type) - if "-Dcoverage=ON" in cmake_args: - config_name += "-coverage" - if "-Dunity=ON" in cmake_args: - config_name += "-unity" - - # Add the configuration to the list, with the most unique fields first, - # so that they are easier to identify in the GitHub Actions UI, as long - # names get truncated. - # Add Address and UB sanitizers as separate configurations for specific - # bookworm distros. Thread sanitizer is currently disabled (see below). - # GCC-Asan xrpld-embedded tests are failing because of https://github.com/google/sanitizers/issues/856 - if ( - os["distro_version"] == "bookworm" - and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15" - ) or ( - os["distro_version"] == "trixie" - and f"{os['compiler_name']}-{os['compiler_version']}" == "clang-22" - ): - # Add ASAN and UBSAN configurations for both gcc-15 and clang-22 - configurations.append( - { - "config_name": config_name + "-asan", - "cmake_args": cmake_args, - "cmake_target": cmake_target, - "build_only": build_only, - "build_type": build_type, - "os": os, - "architecture": architecture, - "sanitizers": "address", - } - ) - configurations.append( - { - "config_name": config_name + "-ubsan", - "cmake_args": cmake_args, - "cmake_target": cmake_target, - "build_only": build_only, - "build_type": build_type, - "os": os, - "architecture": architecture, - "sanitizers": "undefinedbehavior", - } - ) - # TSAN is deactivated due to seg faults with latest compilers. - activate_tsan = False - if activate_tsan: - configurations.append( - { - "config_name": config_name + "-tsan-ubsan", - "cmake_args": cmake_args, - "cmake_target": cmake_target, - "build_only": build_only, - "build_type": build_type, - "os": os, - "architecture": architecture, - "sanitizers": "thread,undefinedbehavior", - } + entries.append( + MatrixEntry( + config_name=name, + image=f"ghcr.io/xrplf/xrpld/nix-{distro}:{linux.image_tag}", + cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args), + cmake_target="all", + build_only=False, + build_type=build_type, + architecture=arch_info, + sanitizers=sanitizer, + compiler=compiler, + ) + ) + + return entries + + +def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]: + """Generate the packaging matrix from a LinuxFile's package_configs section. + + Packaging uses vanilla distro images (debian:bookworm, ubi9, …) instead of + the nix-based build images, because deb/rpm tooling (debhelper, rpm-build) + is taken from the distro's archive rather than from nixpkgs. Each config + entry carries its own 'image'. + """ + entries = [] + for distro, configs in linux.package_configs.items(): + for cfg in configs: + for compiler, build_type in itertools.product(cfg.compiler, cfg.build_type): + entries.append( + PackagingEntry( + artifact_name=f"xrpld-{distro}-{compiler}-{build_type.lower()}-amd64", + image=cfg.image, + distro=distro, + ) + ) + + return entries + + +def expand_platform_matrix(pf: PlatformFile) -> list[MatrixEntry]: + """Expand a PlatformFile (macOS or Windows) into matrix entries.""" + platform_name, arch = pf.platform.split("/") + is_windows = platform_name == "windows" + + entries: list[MatrixEntry] = [] + for cfg in pf.configs: + for build_type in cfg.build_type: + entries.append( + MatrixEntry( + config_name=f"{platform_name}-{arch}-{build_type.lower()}", + cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args), + cmake_target="install" if is_windows else "all", + build_only=cfg.build_only, + build_type=build_type, + architecture=Architecture(platform=pf.platform, runner=pf.runner), + sanitizers="", ) - else: - configurations.append( - { - "config_name": config_name, - "cmake_args": cmake_args, - "cmake_target": cmake_target, - "build_only": build_only, - "build_type": build_type, - "os": os, - "architecture": architecture, - "sanitizers": "", - } ) - - return configurations + return entries -def read_config(file: Path) -> Config: - config = json.loads(file.read_text()) - if ( - config["architecture"] is None - or config["os"] is None - or config["build_type"] is None - or config["cmake_args"] is None - ): - raise Exception("Invalid configuration file.") - - return Config(**config) +# --------------------------------------------------------------------------- +# Entry point +# --------------------------------------------------------------------------- if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument( - "-a", - "--all", - help="Set to generate all configurations (generally used when merging a PR) or leave unset to generate a subset of configurations (generally used when committing to a PR).", - action="store_true", + parser = argparse.ArgumentParser( + description="Generate a CI strategy matrix for all platforms or a specific one." ) parser.add_argument( "-c", "--config", - help="Path to the JSON file containing the strategy matrix configurations.", - required=False, - type=Path, + help="Platform to generate for ('linux', 'macos', or 'windows'). Defaults to all platforms.", + choices=["linux", "macos", "windows"], + default=None, ) parser.add_argument( "-p", "--packaging", - help="Emit the packaging matrix (derived from the 'package' field on os entries) instead of the build/test matrix.", + help="Emit the Linux packaging matrix instead of the build/test matrix.", action="store_true", ) args = parser.parse_args() - matrix = [] - if args.packaging: - config_path = args.config if args.config else THIS_DIR / "linux.json" - matrix += generate_packaging_matrix(read_config(config_path)) - elif args.config is None or args.config == "": - matrix += generate_strategy_matrix( - args.all, read_config(THIS_DIR / "linux.json") - ) - matrix += generate_strategy_matrix( - args.all, read_config(THIS_DIR / "macos.json") - ) - matrix += generate_strategy_matrix( - args.all, read_config(THIS_DIR / "windows.json") - ) - else: - matrix += generate_strategy_matrix(args.all, read_config(args.config)) + matrix: list[MatrixEntry] | list[PackagingEntry] = [] - # Generate the strategy matrix. - print(f"matrix={json.dumps({'include': matrix})}") + if args.packaging: + matrix = expand_linux_packaging(LinuxFile.load(THIS_DIR / "linux.json")) + else: + if args.config in ("linux", None): + matrix += expand_linux_matrix(LinuxFile.load(THIS_DIR / "linux.json")) + if args.config in ("macos", None): + matrix += expand_platform_matrix(PlatformFile.load(THIS_DIR / "macos.json")) + if args.config in ("windows", None): + matrix += expand_platform_matrix( + PlatformFile.load(THIS_DIR / "windows.json") + ) + + print(f"matrix={json.dumps({'include': [dataclasses.asdict(e) for e in matrix]})}") diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index 4f090a81a3..3070b8d9f4 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -1,221 +1,83 @@ { - "architecture": [ - { - "platform": "linux/amd64", - "runner": ["self-hosted", "Linux", "X64", "heavy"] - }, - { - "platform": "linux/arm64", - "runner": ["self-hosted", "Linux", "ARM64", "heavy-arm64"] - } - ], - "os": [ - { - "distro_name": "debian", - "distro_version": "bookworm", - "compiler_name": "gcc", - "compiler_version": "12", - "image_sha": "4c086b9" - }, - { - "distro_name": "debian", - "distro_version": "bookworm", - "compiler_name": "gcc", - "compiler_version": "13", - "image_sha": "4c086b9" - }, - { - "distro_name": "debian", - "distro_version": "bookworm", - "compiler_name": "gcc", - "compiler_version": "14", - "image_sha": "4c086b9" - }, - { - "distro_name": "debian", - "distro_version": "bookworm", - "compiler_name": "gcc", - "compiler_version": "15", - "image_sha": "4c086b9" - }, - { - "distro_name": "debian", - "distro_version": "bookworm", - "compiler_name": "clang", - "compiler_version": "16", - "image_sha": "4c086b9" - }, - { - "distro_name": "debian", - "distro_version": "bookworm", - "compiler_name": "clang", - "compiler_version": "17", - "image_sha": "4c086b9" - }, - { - "distro_name": "debian", - "distro_version": "bookworm", - "compiler_name": "clang", - "compiler_version": "18", - "image_sha": "4c086b9" - }, - { - "distro_name": "debian", - "distro_version": "bookworm", - "compiler_name": "clang", - "compiler_version": "19", - "image_sha": "4c086b9" - }, - { - "distro_name": "debian", - "distro_version": "bookworm", - "compiler_name": "clang", - "compiler_version": "20", - "image_sha": "4c086b9" - }, - { - "distro_name": "debian", - "distro_version": "trixie", - "compiler_name": "gcc", - "compiler_version": "14", - "image_sha": "4c086b9" - }, - { - "distro_name": "debian", - "distro_version": "trixie", - "compiler_name": "gcc", - "compiler_version": "15", - "image_sha": "4c086b9" - }, - { - "distro_name": "debian", - "distro_version": "trixie", - "compiler_name": "clang", - "compiler_version": "20", - "image_sha": "4c086b9" - }, - { - "distro_name": "debian", - "distro_version": "trixie", - "compiler_name": "clang", - "compiler_version": "21", - "image_sha": "4c086b9" - }, - { - "distro_name": "debian", - "distro_version": "trixie", - "compiler_name": "clang", - "compiler_version": "22", - "image_sha": "4c086b9" - }, - { - "distro_name": "rhel", - "distro_version": "8", - "compiler_name": "gcc", - "compiler_version": "14", - "image_sha": "4c086b9" - }, - { - "distro_name": "rhel", - "distro_version": "8", - "compiler_name": "clang", - "compiler_version": "any", - "image_sha": "4c086b9" - }, - { - "distro_name": "rhel", - "distro_version": "9", - "compiler_name": "gcc", - "compiler_version": "12", - "image_sha": "4c086b9", - "package": true - }, - { - "distro_name": "rhel", - "distro_version": "9", - "compiler_name": "gcc", - "compiler_version": "13", - "image_sha": "4c086b9" - }, - { - "distro_name": "rhel", - "distro_version": "9", - "compiler_name": "gcc", - "compiler_version": "14", - "image_sha": "4c086b9" - }, - { - "distro_name": "rhel", - "distro_version": "9", - "compiler_name": "clang", - "compiler_version": "any", - "image_sha": "4c086b9" - }, - { - "distro_name": "rhel", - "distro_version": "10", - "compiler_name": "gcc", - "compiler_version": "14", - "image_sha": "4c086b9" - }, - { - "distro_name": "rhel", - "distro_version": "10", - "compiler_name": "clang", - "compiler_version": "any", - "image_sha": "4c086b9" - }, - { - "distro_name": "ubuntu", - "distro_version": "jammy", - "compiler_name": "gcc", - "compiler_version": "12", - "image_sha": "4c086b9", - "package": true - }, - { - "distro_name": "ubuntu", - "distro_version": "noble", - "compiler_name": "gcc", - "compiler_version": "13", - "image_sha": "4c086b9" - }, - { - "distro_name": "ubuntu", - "distro_version": "noble", - "compiler_name": "gcc", - "compiler_version": "14", - "image_sha": "4c086b9" - }, - { - "distro_name": "ubuntu", - "distro_version": "noble", - "compiler_name": "clang", - "compiler_version": "16", - "image_sha": "4c086b9" - }, - { - "distro_name": "ubuntu", - "distro_version": "noble", - "compiler_name": "clang", - "compiler_version": "17", - "image_sha": "4c086b9" - }, - { - "distro_name": "ubuntu", - "distro_version": "noble", - "compiler_name": "clang", - "compiler_version": "18", - "image_sha": "4c086b9" - }, - { - "distro_name": "ubuntu", - "distro_version": "noble", - "compiler_name": "clang", - "compiler_version": "19", - "image_sha": "4c086b9" - } - ], - "build_type": ["Debug", "Release"], - "cmake_args": [""] + "image_tag": "sha-6c54342", + "configs": { + "ubuntu": [ + { + "compiler": ["gcc", "clang"], + "build_type": ["Debug", "Release"], + "arch": ["amd64", "arm64"] + }, + + { + "compiler": ["gcc", "clang"], + "build_type": ["Debug"], + "arch": ["amd64"], + "sanitizers": ["address", "undefinedbehavior"] + }, + + { + "compiler": ["gcc"], + "build_type": ["Debug"], + "arch": ["amd64"], + "suffix": "coverage", + "extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=500 -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0" + }, + { + "compiler": ["clang"], + "build_type": ["Debug"], + "arch": ["amd64"], + "suffix": "voidstar", + "extra_cmake_args": "-Dvoidstar=ON" + }, + { + "compiler": ["clang"], + "build_type": ["Release"], + "arch": ["amd64"], + "suffix": "reffee", + "extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=1000" + }, + { + "compiler": ["gcc"], + "build_type": ["Debug"], + "arch": ["amd64"], + "suffix": "unity", + "extra_cmake_args": "-Dunity=ON" + } + ], + + "debian": [ + { + "compiler": ["gcc"], + "build_type": ["Release"], + "arch": ["amd64"] + } + ], + + "rhel": [ + { + "compiler": ["gcc"], + "build_type": ["Release"], + "arch": ["amd64"] + } + ] + }, + "package_configs": { + "debian": [ + { + "compiler": ["gcc"], + "build_type": ["Release"], + "arch": ["amd64"], + "image": "debian:bookworm" + } + ], + + "rhel": [ + { + "compiler": ["gcc"], + "build_type": ["Release"], + "arch": ["amd64"], + "image": "registry.access.redhat.com/ubi9/ubi:latest" + } + ] + } } diff --git a/.github/scripts/strategy-matrix/macos.json b/.github/scripts/strategy-matrix/macos.json index 6fc44d0f80..5b9e32f88e 100644 --- a/.github/scripts/strategy-matrix/macos.json +++ b/.github/scripts/strategy-matrix/macos.json @@ -1,19 +1,15 @@ { - "architecture": [ + "platform": "macos/arm64", + "runner": ["self-hosted", "macOS", "ARM64", "mac-runner-m1"], + "configs": [ { - "platform": "macos/arm64", - "runner": ["self-hosted", "macOS", "ARM64", "mac-runner-m1"] - } - ], - "os": [ + "build_type": "Release", + "extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5" + }, { - "distro_name": "macos", - "distro_version": "", - "compiler_name": "", - "compiler_version": "", - "image_sha": "" + "build_type": "Debug", + "extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5", + "build_only": true } - ], - "build_type": ["Debug", "Release"], - "cmake_args": ["-DCMAKE_POLICY_VERSION_MINIMUM=3.5"] + ] } diff --git a/.github/scripts/strategy-matrix/windows.json b/.github/scripts/strategy-matrix/windows.json index 8c536c70f2..e4678b60db 100644 --- a/.github/scripts/strategy-matrix/windows.json +++ b/.github/scripts/strategy-matrix/windows.json @@ -1,19 +1,8 @@ { - "architecture": [ - { - "platform": "windows/amd64", - "runner": ["self-hosted", "Windows", "devbox"] - } - ], - "os": [ - { - "distro_name": "windows", - "distro_version": "", - "compiler_name": "", - "compiler_version": "", - "image_sha": "" - } - ], - "build_type": ["Debug", "Release"], - "cmake_args": [""] + "platform": "windows/amd64", + "runner": ["self-hosted", "Windows", "devbox"], + "configs": [ + { "build_type": "Release" }, + { "build_type": "Debug", "build_only": true } + ] } diff --git a/.github/workflows/on-tag.yml b/.github/workflows/on-tag.yml index b7517ccf11..42d5827cab 100644 --- a/.github/workflows/on-tag.yml +++ b/.github/workflows/on-tag.yml @@ -33,7 +33,6 @@ jobs: with: ccache_enabled: false os: ${{ matrix.os }} - strategy_matrix: minimal secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index 803ba3c87b..74bca82019 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -88,7 +88,6 @@ jobs: # not identical to a regular compilation. ccache_enabled: ${{ github.repository_owner == 'XRPLF' && !startsWith(github.ref, 'refs/heads/release') }} os: ${{ matrix.os }} - strategy_matrix: ${{ github.event_name == 'schedule' && 'all' || 'minimal' }} secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 31457bb892..e1154f74be 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -57,6 +57,12 @@ on: type: string default: "" + compiler: + description: 'The compiler to use ("gcc" or "clang"). Leave empty for macOS/Windows (uses system default).' + required: false + type: string + default: "" + secrets: CODECOV_TOKEN: description: "The Codecov token to use for uploading coverage reports." @@ -124,6 +130,12 @@ jobs: with: subtract: ${{ inputs.nproc_subtract }} + - name: Set compiler environment (Linux) + if: ${{ runner.os == 'Linux' }} + uses: ./.github/actions/set-compiler-env + with: + compiler: ${{ inputs.compiler }} + - name: Setup Conan env: SANITIZERS: ${{ inputs.sanitizers }} @@ -191,6 +203,21 @@ jobs: --parallel "${BUILD_NPROC}" \ --target "${CMAKE_TARGET}" + # This step is needed to allow running in non-Nix environments + - name: Patch binary to use default loader and remove rpath (Linux) + if: ${{ runner.os == 'Linux' && env.SANITIZERS_ENABLED == 'false' }} + run: | + loader="$(/tmp/loader-path.sh)" + patchelf --set-interpreter "${loader}" --remove-rpath "${{ env.BUILD_DIR }}/xrpld" + + # We're only running aarch64 Linux builds in Ubuntu-based images, so this is kept simple + - name: Install libatomic (Linux aarch64) + if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }} + run: | + apt update --yes + apt install -y --no-install-recommends \ + libatomic1 + - name: Show ccache statistics if: ${{ inputs.ccache_enabled }} run: | @@ -217,7 +244,7 @@ jobs: ./xrpld --definitions | python3 -m json.tool >server_definitions.json - name: Upload server definitions - if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-bookworm-gcc-13-amd64-release' }} + if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-gcc-release-amd64' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: server-definitions @@ -279,7 +306,25 @@ jobs: set -o pipefail # Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness [ "$COVERAGE_ENABLED" = "true" ] && BUILD_NPROC=$((BUILD_NPROC - 2)) - ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log + + # The resolver/preload workaround is only correct for the ASan build: + # a regular build doesn't hit the __dn_expand interceptor bug, and must + # NOT have libasan injected. So only preload when xrpld is ASan-built. + # + # libresolv hosts getaddrinfo's resolver helpers (dn_expand, res_*). Under ASan + # these are intercepted via dlsym(RTLD_NEXT, ...), which yields a NULL pointer + # and crashes DNS resolution if libresolv isn't loaded. Linking it guarantees + # the symbols are present; it's a harmless no-op on glibc >= 2.34 (merged into + # libc) and is what the compiler driver already does for sanitizer builds. + # https://github.com/llvm/llvm-project/issues/59007 + # https://github.com/google/sanitizers/issues/1592 + if ldd ./xrpld | grep -q libasan; then + PRELOAD="$(gcc -print-file-name=libasan.so):/usr/lib/x86_64-linux-gnu/libresolv.so.2" + else + PRELOAD="" + fi + + LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log - name: Show test failure summary if: ${{ failure() && !inputs.build_only }} diff --git a/.github/workflows/reusable-build-test.yml b/.github/workflows/reusable-build-test.yml index 0086cbbfb5..4b64c53521 100644 --- a/.github/workflows/reusable-build-test.yml +++ b/.github/workflows/reusable-build-test.yml @@ -19,13 +19,6 @@ on: required: true type: string - strategy_matrix: - # TODO: Support additional strategies, e.g. "ubuntu" for generating all Ubuntu configurations. - description: 'The strategy matrix to use for generating the configurations ("minimal", "all").' - required: false - type: string - default: "minimal" - secrets: CODECOV_TOKEN: description: "The Codecov token to use for uploading coverage reports." @@ -37,7 +30,6 @@ jobs: uses: ./.github/workflows/reusable-strategy-matrix.yml with: os: ${{ inputs.os }} - strategy_matrix: ${{ inputs.strategy_matrix }} # Build and test the binary for each configuration. build-test-config: @@ -47,7 +39,6 @@ jobs: strategy: fail-fast: ${{ github.event_name == 'merge_group' }} matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} - max-parallel: 10 with: build_only: ${{ matrix.build_only }} build_type: ${{ matrix.build_type }} @@ -55,8 +46,9 @@ jobs: cmake_args: ${{ matrix.cmake_args }} cmake_target: ${{ matrix.cmake_target }} runs_on: ${{ toJSON(matrix.architecture.runner) }} - image: ${{ contains(matrix.architecture.platform, 'linux') && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) || '' }} + image: ${{ matrix.image || '' }} config_name: ${{ matrix.config_name }} sanitizers: ${{ matrix.sanitizers }} + compiler: ${{ matrix.compiler || '' }} secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/reusable-package.yml b/.github/workflows/reusable-package.yml index 2a3ed8a33e..670c01733e 100644 --- a/.github/workflows/reusable-package.yml +++ b/.github/workflows/reusable-package.yml @@ -1,8 +1,7 @@ # Build Linux packages (DEB and RPM) from pre-built binary artifacts. -# Discovers which configurations to package from linux.json (os entries -# with "package": true) and fans out one job per entry. Today only -# linux/amd64 is emitted; the architecture is hardcoded both here -# (runner) and in generate.py. +# Discovers which configurations to package from linux.json (configs in +# "package_configs") and fans out one job per distro. Only linux/amd64 is +# supported; the runner is hardcoded in the job below. name: Package on: @@ -33,13 +32,12 @@ jobs: - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: 3.13 + python-version: "3.13" - name: Generate packaging matrix id: generate working-directory: .github/scripts/strategy-matrix - run: | - ./generate.py --packaging --config=linux.json >>"${GITHUB_OUTPUT}" + run: ./generate.py --packaging >>"${GITHUB_OUTPUT}" generate-version: runs-on: ubuntu-latest @@ -66,10 +64,35 @@ jobs: permissions: contents: read runs-on: ["self-hosted", "Linux", "X64", "heavy"] - container: ${{ format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) }} + container: ${{ matrix.image }} timeout-minutes: 30 steps: + # Packaging runs in a vanilla distro image, so the tooling has to come + # from the distro's archive: debhelper for deb, rpm-build (and the + # systemd / find-debuginfo macros it depends on) for rpm. Run this + # before actions/checkout so the latter can use git (real history) for + # build_pkg.sh's SOURCE_DATE_EPOCH; otherwise it falls back to a tarball + # download and the timestamp comes from wall-clock time. + - name: Install packaging tooling (deb) + if: ${{ matrix.distro == 'debian' }} + run: | + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y --no-install-recommends \ + ca-certificates \ + debhelper \ + git + + - name: Install packaging tooling (rpm) + if: ${{ matrix.distro == 'rhel' }} + run: | + dnf install -y --setopt=install_weak_deps=False \ + git \ + rpm-build \ + redhat-rpm-config \ + systemd-rpm-macros + - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.github/workflows/reusable-strategy-matrix.yml b/.github/workflows/reusable-strategy-matrix.yml index 62d65ad3fa..16a2b4e336 100644 --- a/.github/workflows/reusable-strategy-matrix.yml +++ b/.github/workflows/reusable-strategy-matrix.yml @@ -4,15 +4,9 @@ on: workflow_call: inputs: os: - description: 'The operating system to use for the build ("linux", "macos", "windows").' + description: 'The operating system to use for the build ("linux", "macos", "windows", or empty for all).' required: false type: string - strategy_matrix: - # TODO: Support additional strategies, e.g. "ubuntu" for generating all Ubuntu configurations. - description: 'The strategy matrix to use for generating the configurations ("minimal", "all").' - required: false - type: string - default: "minimal" outputs: matrix: description: "The generated strategy matrix." @@ -34,12 +28,11 @@ jobs: - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: 3.13 + python-version: "3.13" - name: Generate strategy matrix working-directory: .github/scripts/strategy-matrix id: generate env: - GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}.json', inputs.os) || '' }} - GENERATE_OPTION: ${{ inputs.strategy_matrix == 'all' && '--all' || '' }} - run: ./generate.py ${GENERATE_OPTION} ${GENERATE_CONFIG} >>"${GITHUB_OUTPUT}" + GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}', inputs.os) || '' }} + run: ./generate.py ${GENERATE_CONFIG} >>"${GITHUB_OUTPUT}" diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 34dce28334..87465b4d3d 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -48,8 +48,6 @@ jobs: # Generate the strategy matrix to be used by the following job. generate-matrix: uses: ./.github/workflows/reusable-strategy-matrix.yml - with: - strategy_matrix: ${{ github.event_name == 'pull_request' && 'minimal' || 'all' }} # Build and upload the dependencies for each configuration. run-upload-conan-deps: @@ -58,9 +56,8 @@ jobs: strategy: fail-fast: false matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} - max-parallel: 10 runs-on: ${{ matrix.architecture.runner }} - container: ${{ contains(matrix.architecture.platform, 'linux') && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) || null }} + container: ${{ matrix.image || null }} steps: - name: Cleanup workspace (macOS and Windows) if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }} @@ -83,6 +80,12 @@ jobs: with: subtract: ${{ env.NPROC_SUBTRACT }} + - name: Set compiler environment (Linux) + if: ${{ runner.os == 'Linux' }} + uses: ./.github/actions/set-compiler-env + with: + compiler: ${{ matrix.compiler }} + - name: Setup Conan env: SANITIZERS: ${{ matrix.sanitizers }} diff --git a/cmake/XrplCompiler.cmake b/cmake/XrplCompiler.cmake index 0b77ff3525..9af8e962d0 100644 --- a/cmake/XrplCompiler.cmake +++ b/cmake/XrplCompiler.cmake @@ -145,13 +145,39 @@ else() INTERFACE -rdynamic $<$:-Wl,-z,relro,-z,now,--build-id> - # link to static libc/c++ iff: * static option set and * NOT APPLE (AppleClang does not support static - # libc/c++) and * NOT SANITIZERS (sanitizers typically don't work with static libc/c++) - $<$,$>,$>>: + # link to static libc/c++ if: + # * static option set and + # * NOT APPLE (AppleClang does not support static libc/c++) + $<$,$>>: -static-libstdc++ -static-libgcc > ) + + # Keep -stdlib=libstdc++ off the compile commands, but preserve it for linking. + # + # Conan turns `compiler.libcxx=libstdc++` into `-stdlib=libstdc++` and puts it in + # CMAKE_CXX_FLAGS, which CMake passes to BOTH compile and link steps. On a normal Clang + # the compile step consumes it while choosing the C++ stdlib include paths. The Nixpkgs + # Clang wrapper supplies those paths itself (via -nostdinc++), so at compile time the + # flag is unused -> Clang errors under our -Werror. At link time the flag IS consumed + # (it selects the C++ runtime), so we move it there instead of dropping it entirely. + get_filename_component(_cxx_real "${CMAKE_CXX_COMPILER}" REALPATH) + if( + _cxx_real MATCHES "^/nix/store/" + AND is_linux + AND is_clang + AND CMAKE_CXX_FLAGS MATCHES "stdlib=libstdc" + ) + string( + REPLACE "-stdlib=libstdc++" + "" + CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS}" + ) + string(STRIP "${CMAKE_CXX_FLAGS}" CMAKE_CXX_FLAGS) + add_link_options($<$:-stdlib=libstdc++>) + endif() endif() # Antithesis instrumentation will only be built and deployed using machines running Linux. diff --git a/conan/profiles/ci b/conan/profiles/ci index ae93187026..9422addfe3 100644 --- a/conan/profiles/ci +++ b/conan/profiles/ci @@ -1 +1,8 @@ +{% set os = detect_api.detect_os() %} include(sanitizers) + +[conf] +{% if os == "Linux" %} +user.package:libc_version=2.31 +tools.info.package_id:confs+=["user.package:libc_version"] +{% endif %} diff --git a/cspell.config.yaml b/cspell.config.yaml index da5dc9b072..cab2fc3da6 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -50,6 +50,7 @@ words: - AMMXRP - amt - amts + - archs - asnode - asynchrony - attestation diff --git a/docker/check-tools.sh b/docker/check-tools.sh index c446dc1b4a..faa6520678 100755 --- a/docker/check-tools.sh +++ b/docker/check-tools.sh @@ -10,7 +10,6 @@ cmake --version conan --version curl --version doxygen --version -dpkg-buildpackage --version g++ --version gcc --version gcov --version @@ -26,7 +25,6 @@ perl --version pkg-config --version pre-commit --version python3 --version -rpmbuild --version run-clang-tidy --help vim --version diff --git a/nix/packages.nix b/nix/packages.nix index c51077367e..6a83446d88 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -13,7 +13,6 @@ in conan curlMinimal # needed for codecov/codecov-action doxygen - dpkg # needed for dpkg-buildpackage gcovr git gnumake @@ -28,7 +27,6 @@ in pkg-config pre-commit python3 - rpm # needed for rpmbuild runClangTidy vim ]; From 2111bb4b9593a5c4f4a43ad1a27cde310159bdf5 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 5 Jun 2026 15:11:47 +0100 Subject: [PATCH 063/135] ci: Update clang-tidy to nix-based v22 (#7412) --- .clang-tidy | 2 +- .github/workflows/reusable-clang-tidy.yml | 7 +- include/xrpl/nodestore/detail/varint.h | 2 +- src/libxrpl/basics/Log.cpp | 3 +- src/libxrpl/basics/Number.cpp | 8 +- src/libxrpl/beast/core/CurrentThreadName.cpp | 1 - src/libxrpl/server/Port.cpp | 4 +- src/test/app/AccountDelete_test.cpp | 2 +- src/test/app/Batch_test.cpp | 972 +++++++++++++++--- src/test/app/Credentials_test.cpp | 4 +- src/test/app/CrossingLimitsMPT_test.cpp | 4 +- src/test/app/CrossingLimits_test.cpp | 4 +- src/test/app/DepositAuth_test.cpp | 87 +- src/test/app/Escrow_test.cpp | 4 +- src/test/app/LedgerReplay_test.cpp | 2 +- src/test/app/PayStrand_test.cpp | 28 +- src/test/app/PermissionedDEX_test.cpp | 6 +- src/test/app/PermissionedDomains_test.cpp | 98 +- src/test/app/TxQ_test.cpp | 2 +- src/test/app/ValidatorList_test.cpp | 87 +- src/test/app/ValidatorSite_test.cpp | 391 ++++--- src/test/basics/PerfLog_test.cpp | 2 +- .../beast/aged_associative_container_test.cpp | 6 +- src/test/core/Config_test.cpp | 16 +- src/test/jtx/impl/WSClient.cpp | 2 +- src/test/jtx/impl/permissioned_dex.cpp | 2 +- src/test/jtx/impl/permissioned_domains.cpp | 4 +- src/test/nodestore/import_test.cpp | 6 +- src/test/rpc/AccountObjects_test.cpp | 4 +- src/test/rpc/DepositAuthorized_test.cpp | 4 +- src/test/rpc/LedgerEntry_test.cpp | 49 +- src/xrpld/app/misc/detail/ValidatorList.cpp | 2 +- .../peerfinder/detail/PeerfinderConfig.cpp | 3 +- src/xrpld/rpc/detail/Pathfinder.cpp | 84 +- src/xrpld/rpc/detail/ServerHandler.cpp | 18 +- 35 files changed, 1392 insertions(+), 528 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index b23d7ccbff..2d72eae701 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -154,7 +154,7 @@ Checks: "-*, " # --- # readability-inconsistent-declaration-parameter-name, # in this codebase this check will break a lot of arg names -# readability-static-accessed-through-instance, # this check is probably unnecessary. it makes the code less readable +# readability-static-accessed-through-instance, # this check is probably unnecessary. It makes the code less readable # --- CheckOptions: diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index 8be1db5fb2..cfbd8af963 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -36,7 +36,7 @@ jobs: needs: [determine-files] if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }} runs-on: ["self-hosted", "Linux", "X64", "heavy"] - container: "ghcr.io/xrplf/ci/debian-trixie:clang-21-sha-53033a2" + container: "ghcr.io/xrplf/xrpld/nix-debian:sha-8abe82e" permissions: contents: read issues: write @@ -56,6 +56,11 @@ jobs: uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf id: nproc + - name: Set compiler environment + uses: ./.github/actions/set-compiler-env + with: + compiler: clang + - name: Setup Conan uses: ./.github/actions/setup-conan diff --git a/include/xrpl/nodestore/detail/varint.h b/include/xrpl/nodestore/detail/varint.h index e6b78fcf08..0c49274d70 100644 --- a/include/xrpl/nodestore/detail/varint.h +++ b/include/xrpl/nodestore/detail/varint.h @@ -25,7 +25,7 @@ struct varint_traits { explicit varint_traits() = default; - static constexpr std::size_t kMax = (8 * sizeof(T) + 6) / 7; + static constexpr std::size_t kMax = ((8 * sizeof(T)) + 6) / 7; }; // Returns: Number of bytes consumed or 0 on error, diff --git a/src/libxrpl/basics/Log.cpp b/src/libxrpl/basics/Log.cpp index 1079f91280..d1e54a515f 100644 --- a/src/libxrpl/basics/Log.cpp +++ b/src/libxrpl/basics/Log.cpp @@ -61,7 +61,8 @@ Logs::File::open(boost::filesystem::path const& path) bool wasOpened = false; // VFALCO TODO Make this work with Unicode file paths - std::unique_ptr stream(new std::ofstream(path.c_str(), std::fstream::app)); + std::unique_ptr stream = + std::make_unique(path.c_str(), std::fstream::app); if (stream->good()) { diff --git a/src/libxrpl/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index 275d82d8c9..23e913bbdc 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -1241,9 +1241,11 @@ root(Number f, unsigned d) } // Quadratic least squares curve fit of f^(1/d) in the range [0, 1] - auto const D = (((6 * di + 11) * di + 6) * di) + 1; // NOLINT(readability-identifier-naming) - auto const a0 = 3 * di * ((2 * di - 3) * di + 1); - auto const a1 = 24 * di * (2 * di - 1); + + // NOLINTNEXTLINE(readability-identifier-naming) + auto const D = (((((6 * di) + 11) * di) + 6) * di) + 1; + auto const a0 = 3 * di * ((((2 * di) - 3) * di) + 1); + auto const a1 = 24 * di * ((2 * di) - 1); auto const a2 = -30 * (di - 1) * di; Number r = ((Number{a2} * f + Number{a1}) * f + Number{a0}) / Number{D}; if (neg) diff --git a/src/libxrpl/beast/core/CurrentThreadName.cpp b/src/libxrpl/beast/core/CurrentThreadName.cpp index 52d9063179..628fec5b7a 100644 --- a/src/libxrpl/beast/core/CurrentThreadName.cpp +++ b/src/libxrpl/beast/core/CurrentThreadName.cpp @@ -71,7 +71,6 @@ setCurrentThreadNameImpl(std::string_view name) #if BOOST_OS_LINUX #include -#include #include // IWYU pragma: keep namespace beast::detail { diff --git a/src/libxrpl/server/Port.cpp b/src/libxrpl/server/Port.cpp index 00c10b2b55..b3fd7a1526 100644 --- a/src/libxrpl/server/Port.cpp +++ b/src/libxrpl/server/Port.cpp @@ -26,8 +26,8 @@ namespace xrpl { bool Port::secure() const { - return protocol.count("peer") > 0 || protocol.count("https") > 0 || protocol.count("wss") > 0 || - protocol.count("wss2") > 0; + return protocol.contains("peer") || protocol.contains("https") || protocol.contains("wss") || + protocol.contains("wss2"); } std::string diff --git a/src/test/app/AccountDelete_test.cpp b/src/test/app/AccountDelete_test.cpp index 951f99919b..65ff9ed839 100644 --- a/src/test/app/AccountDelete_test.cpp +++ b/src/test/app/AccountDelete_test.cpp @@ -813,7 +813,7 @@ public: env.close(); // alice create DepositPreauth Object - env(deposit::authCredentials(alice, {{carol, credType}})); + env(deposit::authCredentials(alice, {{.issuer = carol, .credType = credType}})); env.close(); // becky attempts to delete her account, but alice won't take her diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 8755fe9f9c..791bb5a4d6 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -1017,7 +1017,11 @@ class Batch_test : public beast::unit_test::Suite env.close(); { std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, }; validateClosedLedger(env, testCases); } @@ -1059,7 +1063,11 @@ class Batch_test : public beast::unit_test::Suite env.close(); { std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, }; validateClosedLedger(env, testCases); } @@ -1101,7 +1109,11 @@ class Batch_test : public beast::unit_test::Suite env.close(); { std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, }; validateClosedLedger(env, testCases); } @@ -1143,7 +1155,11 @@ class Batch_test : public beast::unit_test::Suite env.close(); { std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, }; validateClosedLedger(env, testCases); } @@ -1185,7 +1201,11 @@ class Batch_test : public beast::unit_test::Suite env.close(); { std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, }; validateClosedLedger(env, testCases); } @@ -1520,9 +1540,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -1552,7 +1584,11 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, }; validateClosedLedger(env, testCases); @@ -1581,7 +1617,11 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, }; validateClosedLedger(env, testCases); @@ -1610,7 +1650,11 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, }; validateClosedLedger(env, testCases); @@ -1662,10 +1706,26 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tecUNFUNDED_PAYMENT", txIDs[0], batchID}, - {2, "Payment", "tecUNFUNDED_PAYMENT", txIDs[1], batchID}, - {3, "Payment", "tecUNFUNDED_PAYMENT", txIDs[2], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tecUNFUNDED_PAYMENT", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tecUNFUNDED_PAYMENT", + .txHash = txIDs[1], + .batchID = batchID}, + {.index = 3, + .txType = "Payment", + .result = "tecUNFUNDED_PAYMENT", + .txHash = txIDs[2], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -1695,9 +1755,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tecUNFUNDED_PAYMENT", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tecUNFUNDED_PAYMENT", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -1727,8 +1799,16 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -1758,8 +1838,16 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -1789,8 +1877,16 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -1826,11 +1922,31 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "OfferCreate", "tecKILLED", txIDs[0], batchID}, - {2, "OfferCreate", "tecKILLED", txIDs[1], batchID}, - {3, "OfferCreate", "tecKILLED", txIDs[2], batchID}, - {4, "Payment", "tesSUCCESS", txIDs[3], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "OfferCreate", + .result = "tecKILLED", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "OfferCreate", + .result = "tecKILLED", + .txHash = txIDs[1], + .batchID = batchID}, + {.index = 3, + .txType = "OfferCreate", + .result = "tecKILLED", + .txHash = txIDs[2], + .batchID = batchID}, + {.index = 4, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[3], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -1878,8 +1994,16 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tecUNFUNDED_PAYMENT", txIDs[0], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tecUNFUNDED_PAYMENT", + .txHash = txIDs[0], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -1909,11 +2033,31 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, - {3, "Payment", "tesSUCCESS", txIDs[2], batchID}, - {4, "Payment", "tesSUCCESS", txIDs[3], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, + {.index = 3, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[2], + .batchID = batchID}, + {.index = 4, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[3], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -1944,10 +2088,26 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, - {3, "Payment", "tecUNFUNDED_PAYMENT", txIDs[2], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, + {.index = 3, + .txType = "Payment", + .result = "tecUNFUNDED_PAYMENT", + .txHash = txIDs[2], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -1978,9 +2138,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -2011,9 +2183,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -2044,10 +2228,26 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, - {3, "OfferCreate", "tecKILLED", txIDs[2], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, + {.index = 3, + .txType = "OfferCreate", + .result = "tecKILLED", + .txHash = txIDs[2], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -2095,11 +2295,31 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tecUNFUNDED_PAYMENT", txIDs[1], batchID}, - {3, "Payment", "tecUNFUNDED_PAYMENT", txIDs[2], batchID}, - {4, "Payment", "tesSUCCESS", txIDs[3], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tecUNFUNDED_PAYMENT", + .txHash = txIDs[1], + .batchID = batchID}, + {.index = 3, + .txType = "Payment", + .result = "tecUNFUNDED_PAYMENT", + .txHash = txIDs[2], + .batchID = batchID}, + {.index = 4, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[3], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -2130,11 +2350,31 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, - {3, "Payment", "tecUNFUNDED_PAYMENT", txIDs[2], batchID}, - {4, "Payment", "tesSUCCESS", txIDs[3], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, + {.index = 3, + .txType = "Payment", + .result = "tecUNFUNDED_PAYMENT", + .txHash = txIDs[2], + .batchID = batchID}, + {.index = 4, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[3], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -2165,10 +2405,26 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, - {3, "Payment", "tesSUCCESS", txIDs[3], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, + {.index = 3, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[3], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -2199,10 +2455,26 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, - {3, "Payment", "tesSUCCESS", txIDs[3], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, + {.index = 3, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[3], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -2232,10 +2504,26 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, - {3, "OfferCreate", "tecKILLED", txIDs[2], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, + {.index = 3, + .txType = "OfferCreate", + .result = "tecKILLED", + .txHash = txIDs[2], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -2454,9 +2742,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "AccountSet", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "AccountSet", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -2503,9 +2803,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "AccountSet", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "AccountSet", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -2558,9 +2870,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "AccountDelete", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "AccountDelete", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -2601,10 +2925,26 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "AccountDelete", "tecHAS_OBLIGATIONS", txIDs[1], batchID}, - {3, "Payment", "tesSUCCESS", txIDs[2], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "AccountDelete", + .result = "tecHAS_OBLIGATIONS", + .txHash = txIDs[1], + .batchID = batchID}, + {.index = 3, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[2], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -2642,7 +2982,11 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, }; validateClosedLedger(env, testCases); @@ -2876,9 +3220,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID}, - {2, "CheckCash", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "CheckCreate", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "CheckCash", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -2922,9 +3278,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "CheckCreate", "tecDST_TAG_NEEDED", txIDs[0], batchID}, - {2, "CheckCash", "tecNO_ENTRY", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "CheckCreate", + .result = "tecDST_TAG_NEEDED", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "CheckCash", + .result = "tecNO_ENTRY", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -2987,10 +3355,26 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "TicketCreate", "tesSUCCESS", txIDs[0], batchID}, - {2, "CheckCreate", "tesSUCCESS", txIDs[1], batchID}, - {3, "CheckCash", "tesSUCCESS", txIDs[2], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "TicketCreate", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "CheckCreate", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, + {.index = 3, + .txType = "CheckCash", + .result = "tesSUCCESS", + .txHash = txIDs[2], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -3047,9 +3431,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID}, - {2, "CheckCash", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "CheckCreate", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "CheckCash", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -3099,9 +3495,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -3147,9 +3555,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -3196,9 +3616,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -3257,9 +3689,21 @@ class Batch_test : public beast::unit_test::Suite { std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); } @@ -3268,7 +3712,11 @@ class Batch_test : public beast::unit_test::Suite { // next ledger contains noop txn std::vector const testCases = { - {0, "AccountSet", "tesSUCCESS", noopTxnID, std::nullopt}, + {.index = 0, + .txType = "AccountSet", + .result = "tesSUCCESS", + .txHash = noopTxnID, + .batchID = std::nullopt}, }; validateClosedLedger(env, testCases); } @@ -3301,9 +3749,21 @@ class Batch_test : public beast::unit_test::Suite { std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); } @@ -3340,9 +3800,21 @@ class Batch_test : public beast::unit_test::Suite { std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); } @@ -3382,10 +3854,26 @@ class Batch_test : public beast::unit_test::Suite { std::vector const testCases = { - {0, "AccountSet", "tesSUCCESS", noopTxnID, std::nullopt}, - {1, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {2, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {3, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "AccountSet", + .result = "tesSUCCESS", + .txHash = noopTxnID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 3, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); } @@ -3442,9 +3930,21 @@ class Batch_test : public beast::unit_test::Suite { std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); } @@ -3489,9 +3989,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); { std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); } @@ -3552,10 +4064,26 @@ class Batch_test : public beast::unit_test::Suite env.close(); { std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, - {3, "CheckCash", "tesSUCCESS", objTxnID, std::nullopt}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "CheckCreate", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, + {.index = 3, + .txType = "CheckCash", + .result = "tesSUCCESS", + .txHash = objTxnID, + .batchID = std::nullopt}, }; validateClosedLedger(env, testCases); } @@ -3601,10 +4129,26 @@ class Batch_test : public beast::unit_test::Suite env.close(); { std::vector const testCases = { - {0, "CheckCreate", "tesSUCCESS", objTxnID, std::nullopt}, - {1, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {2, "CheckCash", "tesSUCCESS", txIDs[0], batchID}, - {3, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "CheckCreate", + .result = "tesSUCCESS", + .txHash = objTxnID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 2, + .txType = "CheckCash", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 3, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); } @@ -3646,10 +4190,26 @@ class Batch_test : public beast::unit_test::Suite env.close(); { std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, - {3, "CheckCash", "tesSUCCESS", objTxnID, std::nullopt}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "CheckCreate", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, + {.index = 3, + .txType = "CheckCash", + .result = "tesSUCCESS", + .txHash = objTxnID, + .batchID = std::nullopt}, }; validateClosedLedger(env, testCases); } @@ -3742,10 +4302,26 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Payment", "tesSUCCESS", payTxn1ID, std::nullopt}, - {1, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {2, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {3, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = payTxn1ID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 3, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -3753,7 +4329,11 @@ class Batch_test : public beast::unit_test::Suite { // next ledger includes the payment txn std::vector const testCases = { - {0, "Payment", "tesSUCCESS", payTxn2ID, std::nullopt}, + {.index = 0, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = payTxn2ID, + .batchID = std::nullopt}, }; validateClosedLedger(env, testCases); } @@ -3965,9 +4545,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -4014,9 +4606,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "Payment", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -4064,9 +4668,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "AccountSet", "tesSUCCESS", txIDs[0], batchID}, - {2, "Payment", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "AccountSet", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "Payment", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); @@ -4126,9 +4742,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "MPTokenIssuanceSet", "tesSUCCESS", txIDs[0], batchID}, - {2, "MPTokenIssuanceSet", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "MPTokenIssuanceSet", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "MPTokenIssuanceSet", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); } @@ -4167,9 +4795,21 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "TrustSet", "tesSUCCESS", txIDs[0], batchID}, - {2, "TrustSet", "tesSUCCESS", txIDs[1], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "TrustSet", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, + {.index = 2, + .txType = "TrustSet", + .result = "tesSUCCESS", + .txHash = txIDs[1], + .batchID = batchID}, }; validateClosedLedger(env, testCases); } @@ -4207,8 +4847,16 @@ class Batch_test : public beast::unit_test::Suite env.close(); std::vector const testCases = { - {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, - {1, "TrustSet", "tesSUCCESS", txIDs[0], batchID}, + {.index = 0, + .txType = "Batch", + .result = "tesSUCCESS", + .txHash = batchID, + .batchID = std::nullopt}, + {.index = 1, + .txType = "TrustSet", + .result = "tesSUCCESS", + .txHash = txIDs[0], + .batchID = batchID}, // jv2 fails with terNO_DELEGATE_PERMISSION. }; validateClosedLedger(env, testCases); diff --git a/src/test/app/Credentials_test.cpp b/src/test/app/Credentials_test.cpp index 9416ca222f..456a53bc01 100644 --- a/src/test/app/Credentials_test.cpp +++ b/src/test/app/Credentials_test.cpp @@ -1078,7 +1078,7 @@ struct Credentials_test : public beast::unit_test::Suite } // Create DepositPreauth - env(deposit::authCredentials(becky, {{subject, credType}})); + env(deposit::authCredentials(becky, {{.issuer = subject, .credType = credType}})); env.close(); // env(); auto jtx = env.jt(pay(subject, becky, XRP(100)), credentials::Ids({credIdx})); @@ -1087,7 +1087,7 @@ struct Credentials_test : public beast::unit_test::Suite auto const stx = std::make_shared(*jtx.stx); // Create PermissionedDomain - env(pdomain::setTx(becky, {{issuer, credType}})); + env(pdomain::setTx(becky, {{.issuer = issuer, .credType = credType}})); env.close(); auto const objects = pdomain::getObjects(becky, env); if (!BEAST_EXPECT(!objects.empty())) diff --git a/src/test/app/CrossingLimitsMPT_test.cpp b/src/test/app/CrossingLimitsMPT_test.cpp index 4a016f31dc..8bd0c767f7 100644 --- a/src/test/app/CrossingLimitsMPT_test.cpp +++ b/src/test/app/CrossingLimitsMPT_test.cpp @@ -270,7 +270,7 @@ public: env.require(Balance(alice, usd(2'503))); env.require(Balance(alice, eur(1'100))); - auto const numAOffers = 2'000 + 100 + 1'000 + 1 - (2 * 100 + 2 * 199 + 1 + 1); + auto const numAOffers = 2'000 + 100 + 1'000 + 1 - ((2 * 100) + (2 * 199) + 1 + 1); env.require(offers(alice, numAOffers)); env.require(Owners(alice, numAOffers + 2)); @@ -358,7 +358,7 @@ public: env.require(Balance(alice, usd(2'494))); env.require(Balance(alice, eur(1'100))); auto const numAOffers = - 1 + 2'000 + 100 + 1'000 + 1 - (1 + 2 * 100 + 2 * 199 + 1 + 1); + 1 + 2'000 + 100 + 1'000 + 1 - (1 + (2 * 100) + (2 * 199) + 1 + 1); env.require(offers(alice, numAOffers)); env.require(Owners(alice, numAOffers + 2)); diff --git a/src/test/app/CrossingLimits_test.cpp b/src/test/app/CrossingLimits_test.cpp index 3cf8f50990..c48892f04e 100644 --- a/src/test/app/CrossingLimits_test.cpp +++ b/src/test/app/CrossingLimits_test.cpp @@ -258,7 +258,7 @@ public: env.require(Balance(alice, usd(2503))); env.require(Balance(alice, eur(1100))); - auto const numAOffers = 2000 + 100 + 1000 + 1 - (2 * 100 + 2 * 199 + 1 + 1); + auto const numAOffers = 2000 + 100 + 1000 + 1 - ((2 * 100) + (2 * 199) + 1 + 1); env.require(offers(alice, numAOffers)); env.require(Owners(alice, numAOffers + 2)); @@ -329,7 +329,7 @@ public: env.require(Balance(alice, usd(2494))); env.require(Balance(alice, eur(1100))); - auto const numAOffers = 1 + 2000 + 100 + 1000 + 1 - (1 + 2 * 100 + 2 * 199 + 1 + 1); + auto const numAOffers = 1 + 2000 + 100 + 1000 + 1 - (1 + (2 * 100) + (2 * 199) + 1 + 1); env.require(offers(alice, numAOffers)); env.require(Owners(alice, numAOffers + 2)); diff --git a/src/test/app/DepositAuth_test.cpp b/src/test/app/DepositAuth_test.cpp index 98597a175f..3496e67b54 100644 --- a/src/test/app/DepositAuth_test.cpp +++ b/src/test/app/DepositAuth_test.cpp @@ -614,7 +614,8 @@ struct DepositPreauth_test : public beast::unit_test::Suite TER const expectTer(!supportsCredentials ? TER(temDISABLED) : TER(tesSUCCESS)); - env(deposit::authCredentials(becky, {{carol, credType}}), Ter(expectTer)); + env(deposit::authCredentials(becky, {{.issuer = carol, .credType = credType}}), + Ter(expectTer)); env.close(); // gw accept credentials @@ -744,7 +745,8 @@ struct DepositPreauth_test : public beast::unit_test::Suite env.close(); // Setup DepositPreauth object failed - amendent is not supported - env(deposit::authCredentials(bob, {{issuer, credType}}), Ter(temDISABLED)); + env(deposit::authCredentials(bob, {{.issuer = issuer, .credType = credType}}), + Ter(temDISABLED)); env.close(); // But can create old DepositPreauth @@ -782,10 +784,11 @@ struct DepositPreauth_test : public beast::unit_test::Suite // Bob will accept payments from accounts with credentials signed // by 'issuer' - env(deposit::authCredentials(bob, {{issuer, credType}})); + env(deposit::authCredentials(bob, {{.issuer = issuer, .credType = credType}})); env.close(); - auto const jDP = ledgerEntryDepositPreauth(env, bob, {{issuer, credType}}); + auto const jDP = + ledgerEntryDepositPreauth(env, bob, {{.issuer = issuer, .credType = credType}}); BEAST_EXPECT( jDP.isObject() && jDP.isMember(jss::result) && !jDP[jss::result].isMember(jss::error) && jDP[jss::result].isMember(jss::node) && @@ -858,11 +861,14 @@ struct DepositPreauth_test : public beast::unit_test::Suite } // Bob setup DepositPreauth object, duplicates is not allowed - env(deposit::authCredentials(bob, {{issuer, credType}, {issuer, credType}}), + env(deposit::authCredentials( + bob, + {{.issuer = issuer, .credType = credType}, + {.issuer = issuer, .credType = credType}}), Ter(temMALFORMED)); // Bob setup DepositPreauth object - env(deposit::authCredentials(bob, {{issuer, credType}})); + env(deposit::authCredentials(bob, {{.issuer = issuer, .credType = credType}})); env.close(); { @@ -928,35 +934,37 @@ struct DepositPreauth_test : public beast::unit_test::Suite { // both included [AuthorizeCredentials UnauthorizeCredentials] - auto jv = deposit::authCredentials(bob, {{issuer, credType}}); + auto jv = deposit::authCredentials(bob, {{.issuer = issuer, .credType = credType}}); jv[sfUnauthorizeCredentials.jsonName] = json::ValueType::Array; env(jv, Ter(temMALFORMED)); } { // both included [Unauthorize, AuthorizeCredentials] - auto jv = deposit::authCredentials(bob, {{issuer, credType}}); + auto jv = deposit::authCredentials(bob, {{.issuer = issuer, .credType = credType}}); jv[sfUnauthorize.jsonName] = issuer.human(); env(jv, Ter(temMALFORMED)); } { // both included [Authorize, AuthorizeCredentials] - auto jv = deposit::authCredentials(bob, {{issuer, credType}}); + auto jv = deposit::authCredentials(bob, {{.issuer = issuer, .credType = credType}}); jv[sfAuthorize.jsonName] = issuer.human(); env(jv, Ter(temMALFORMED)); } { // both included [Unauthorize, UnauthorizeCredentials] - auto jv = deposit::unauthCredentials(bob, {{issuer, credType}}); + auto jv = + deposit::unauthCredentials(bob, {{.issuer = issuer, .credType = credType}}); jv[sfUnauthorize.jsonName] = issuer.human(); env(jv, Ter(temMALFORMED)); } { // both included [Authorize, UnauthorizeCredentials] - auto jv = deposit::unauthCredentials(bob, {{issuer, credType}}); + auto jv = + deposit::unauthCredentials(bob, {{.issuer = issuer, .credType = credType}}); jv[sfAuthorize.jsonName] = issuer.human(); env(jv, Ter(temMALFORMED)); } @@ -983,7 +991,7 @@ struct DepositPreauth_test : public beast::unit_test::Suite { // empty credential type - auto jv = deposit::authCredentials(bob, {{issuer, {}}}); + auto jv = deposit::authCredentials(bob, {{.issuer = issuer, .credType = {}}}); env(jv, Ter(temMALFORMED)); } @@ -993,14 +1001,23 @@ struct DepositPreauth_test : public beast::unit_test::Suite i("i"); auto const& z = credType; auto jv = deposit::authCredentials( - bob, {{a, z}, {b, z}, {c, z}, {d, z}, {e, z}, {f, z}, {g, z}, {h, z}, {i, z}}); + bob, + {{.issuer = a, .credType = z}, + {.issuer = b, .credType = z}, + {.issuer = c, .credType = z}, + {.issuer = d, .credType = z}, + {.issuer = e, .credType = z}, + {.issuer = f, .credType = z}, + {.issuer = g, .credType = z}, + {.issuer = h, .credType = z}, + {.issuer = i, .credType = z}}); env(jv, Ter(temARRAY_TOO_LARGE)); } { // Can't create with non-existing issuer Account const rick{"rick"}; - auto jv = deposit::authCredentials(bob, {{rick, credType}}); + auto jv = deposit::authCredentials(bob, {{.issuer = rick, .credType = credType}}); env(jv, Ter(tecNO_ISSUER)); env.close(); } @@ -1010,21 +1027,24 @@ struct DepositPreauth_test : public beast::unit_test::Suite Account const john{"john"}; env.fund(env.current()->fees().accountReserve(0), john); env.close(); - auto jv = deposit::authCredentials(john, {{issuer, credType}}); + auto jv = + deposit::authCredentials(john, {{.issuer = issuer, .credType = credType}}); env(jv, Ter(tecINSUFFICIENT_RESERVE)); } { // NO deposit object exists - env(deposit::unauthCredentials(bob, {{issuer, credType}}), Ter(tecNO_ENTRY)); + env(deposit::unauthCredentials(bob, {{.issuer = issuer, .credType = credType}}), + Ter(tecNO_ENTRY)); } // Create DepositPreauth object { - env(deposit::authCredentials(bob, {{issuer, credType}})); + env(deposit::authCredentials(bob, {{.issuer = issuer, .credType = credType}})); env.close(); - auto const jDP = ledgerEntryDepositPreauth(env, bob, {{issuer, credType}}); + auto const jDP = + ledgerEntryDepositPreauth(env, bob, {{.issuer = issuer, .credType = credType}}); BEAST_EXPECT( jDP.isObject() && jDP.isMember(jss::result) && !jDP[jss::result].isMember(jss::error) && @@ -1045,14 +1065,16 @@ struct DepositPreauth_test : public beast::unit_test::Suite } // can't create duplicate - env(deposit::authCredentials(bob, {{issuer, credType}}), Ter(tecDUPLICATE)); + env(deposit::authCredentials(bob, {{.issuer = issuer, .credType = credType}}), + Ter(tecDUPLICATE)); } // Delete DepositPreauth object { - env(deposit::unauthCredentials(bob, {{issuer, credType}})); + env(deposit::unauthCredentials(bob, {{.issuer = issuer, .credType = credType}})); env.close(); - auto const jDP = ledgerEntryDepositPreauth(env, bob, {{issuer, credType}}); + auto const jDP = + ledgerEntryDepositPreauth(env, bob, {{.issuer = issuer, .credType = credType}}); BEAST_EXPECT( jDP.isObject() && jDP.isMember(jss::result) && jDP[jss::result].isMember(jss::error) && @@ -1119,7 +1141,10 @@ struct DepositPreauth_test : public beast::unit_test::Suite env(fset(bob, asfDepositAuth)); env.close(); // Bob setup DepositPreauth object - env(deposit::authCredentials(bob, {{issuer, credType}, {issuer, credType2}})); + env(deposit::authCredentials( + bob, + {{.issuer = issuer, .credType = credType}, + {.issuer = issuer, .credType = credType2}})); env.close(); { @@ -1228,7 +1253,7 @@ struct DepositPreauth_test : public beast::unit_test::Suite env(fset(bob, asfDepositAuth)); env.close(); // Bob setup DepositPreauth object - env(deposit::authCredentials(bob, {{issuer, credType}})); + env(deposit::authCredentials(bob, {{.issuer = issuer, .credType = credType}})); env.close(); auto const seq = env.seq(alice); @@ -1286,14 +1311,14 @@ struct DepositPreauth_test : public beast::unit_test::Suite env.fund(XRP(5000), stock, alice, bob); std::vector credentials = { - {"a", "a"}, - {"b", "b"}, - {"c", "c"}, - {"d", "d"}, - {"e", "e"}, - {"f", "f"}, - {"g", "g"}, - {"h", "h"}}; + {.issuer = "a", .credType = "a"}, + {.issuer = "b", .credType = "b"}, + {.issuer = "c", .credType = "c"}, + {.issuer = "d", .credType = "d"}, + {.issuer = "e", .credType = "e"}, + {.issuer = "f", .credType = "f"}, + {.issuer = "g", .credType = "g"}, + {.issuer = "h", .credType = "h"}}; for (auto const& c : credentials) env.fund(XRP(5000), c.issuer); diff --git a/src/test/app/Escrow_test.cpp b/src/test/app/Escrow_test.cpp index 3e76524cf1..5623bc4443 100644 --- a/src/test/app/Escrow_test.cpp +++ b/src/test/app/Escrow_test.cpp @@ -1544,7 +1544,7 @@ struct Escrow_test : public beast::unit_test::Suite credentials::Ids({credIdx}), Ter(tecNO_PERMISSION)); - env(deposit::authCredentials(bob, {{zelda, credType}})); + env(deposit::authCredentials(bob, {{.issuer = zelda, .credType = credType}})); env.close(); // Success @@ -1601,7 +1601,7 @@ struct Escrow_test : public beast::unit_test::Suite // Bob require pre-authorization env(fset(bob, asfDepositAuth)); env.close(); - env(deposit::authCredentials(bob, {{zelda, credType}})); + env(deposit::authCredentials(bob, {{.issuer = zelda, .credType = credType}})); env.close(); // Use any valid credentials if account == dst diff --git a/src/test/app/LedgerReplay_test.cpp b/src/test/app/LedgerReplay_test.cpp index 1978d04fe1..810d93e6e1 100644 --- a/src/test/app/LedgerReplay_test.cpp +++ b/src/test/app/LedgerReplay_test.cpp @@ -549,7 +549,7 @@ struct LedgerServer while (senders.contains(fromIdx)) fromIdx = (fromIdx + 1) % fundedAccounts; senders.insert(fromIdx); - toIdx = (toIdx + r * 2) % fundedAccounts; + toIdx = (toIdx + (r * 2)) % fundedAccounts; if (toIdx == fromIdx) toIdx = (toIdx + 1) % fundedAccounts; }; diff --git a/src/test/app/PayStrand_test.cpp b/src/test/app/PayStrand_test.cpp index 67a37833b2..471c641f36 100644 --- a/src/test/app/PayStrand_test.cpp +++ b/src/test/app/PayStrand_test.cpp @@ -632,7 +632,13 @@ struct PayStrand_test : public beast::unit_test::Suite // Insert implied account test( - env, usd, std::nullopt, STPath(), tesSUCCESS, D{alice, gw, usdC}, D{gw, bob, usdC}); + env, + usd, + std::nullopt, + STPath(), + tesSUCCESS, + D{.src = alice, .dst = gw, .currency = usdC}, + D{.src = gw, .dst = bob, .currency = usdC}); env.trust(eur(1000), alice, bob); // Insert implied offer @@ -642,9 +648,9 @@ struct PayStrand_test : public beast::unit_test::Suite usd, STPath(), tesSUCCESS, - D{alice, gw, usdC}, + D{.src = alice, .dst = gw, .currency = usdC}, B{usd, eur, std::nullopt}, - D{gw, bob, eurC}); + D{.src = gw, .dst = bob, .currency = eurC}); // Path with explicit offer test( @@ -653,9 +659,9 @@ struct PayStrand_test : public beast::unit_test::Suite usd, STPath({ipe(eur)}), tesSUCCESS, - D{alice, gw, usdC}, + D{.src = alice, .dst = gw, .currency = usdC}, B{usd, eur, std::nullopt}, - D{gw, bob, eurC}); + D{.src = gw, .dst = bob, .currency = eurC}); // Path with offer that changes issuer only env.trust(carol["USD"](1000), bob); @@ -665,9 +671,9 @@ struct PayStrand_test : public beast::unit_test::Suite usd, STPath({iape(carol)}), tesSUCCESS, - D{alice, gw, usdC}, + D{.src = alice, .dst = gw, .currency = usdC}, B{usd, carol["USD"], std::nullopt}, - D{carol, bob, usdC}); + D{.src = carol, .dst = bob, .currency = usdC}); // Path with XRP src currency test( @@ -678,7 +684,7 @@ struct PayStrand_test : public beast::unit_test::Suite tesSUCCESS, XRPS{alice}, B{XRP, usd, std::nullopt}, - D{gw, bob, usdC}); + D{.src = gw, .dst = bob, .currency = usdC}); // Path with XRP dst currency. test( @@ -688,7 +694,7 @@ struct PayStrand_test : public beast::unit_test::Suite STPath({STPathElement{ STPathElement::TypeCurrency, xrpAccount(), xrpCurrency(), xrpAccount()}}), tesSUCCESS, - D{alice, gw, usdC}, + D{.src = alice, .dst = gw, .currency = usdC}, B{usd, XRP, std::nullopt}, XRPS{bob}); @@ -699,10 +705,10 @@ struct PayStrand_test : public beast::unit_test::Suite usd, STPath({cpe(xrpCurrency())}), tesSUCCESS, - D{alice, gw, usdC}, + D{.src = alice, .dst = gw, .currency = usdC}, B{usd, XRP, std::nullopt}, B{XRP, eur, std::nullopt}, - D{gw, bob, eurC}); + D{.src = gw, .dst = bob, .currency = eurC}); // XRP -> XRP transaction can't include a path test(env, XRP, std::nullopt, STPath({ape(carol)}), temBAD_PATH); diff --git a/src/test/app/PermissionedDEX_test.cpp b/src/test/app/PermissionedDEX_test.cpp index a88cbaa868..d534f20248 100644 --- a/src/test/app/PermissionedDEX_test.cpp +++ b/src/test/app/PermissionedDEX_test.cpp @@ -704,7 +704,8 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); auto const badCredType = "badCred"; - pdomain::Credentials const credentials{{badDomainOwner, badCredType}}; + pdomain::Credentials const credentials{ + {.issuer = badDomainOwner, .credType = badCredType}}; env(pdomain::setTx(badDomainOwner, credentials)); auto objects = pdomain::getObjects(badDomainOwner, env); @@ -1222,7 +1223,8 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); auto const badCredType = "badCred"; - pdomain::Credentials const credentials{{badDomainOwner, badCredType}}; + pdomain::Credentials const credentials{ + {.issuer = badDomainOwner, .credType = badCredType}}; env(pdomain::setTx(badDomainOwner, credentials)); auto objects = pdomain::getObjects(badDomainOwner, env); diff --git a/src/test/app/PermissionedDomains_test.cpp b/src/test/app/PermissionedDomains_test.cpp index 0857a4bdef..f2d7bce152 100644 --- a/src/test/app/PermissionedDomains_test.cpp +++ b/src/test/app/PermissionedDomains_test.cpp @@ -62,7 +62,7 @@ class PermissionedDomains_test : public beast::unit_test::Suite Account const alice("alice"); Env env(*this, features); env.fund(XRP(1000), alice); - pdomain::Credentials const credentials{{alice, "first credential"}}; + pdomain::Credentials const credentials{{.issuer = alice, .credType = "first credential"}}; env(pdomain::setTx(alice, credentials)); BEAST_EXPECT(env.ownerCount(alice) == 1); auto objects = pdomain::getObjects(alice, env); @@ -84,7 +84,7 @@ class PermissionedDomains_test : public beast::unit_test::Suite Account const alice("alice"); Env env(*this, amendments); env.fund(XRP(1000), alice); - pdomain::Credentials const credentials{{alice, "first credential"}}; + pdomain::Credentials const credentials{{.issuer = alice, .credType = "first credential"}}; env(pdomain::setTx(alice, credentials), Ter(temDISABLED)); } @@ -96,7 +96,7 @@ class PermissionedDomains_test : public beast::unit_test::Suite Account const alice("alice"); Env env(*this, testableAmendments() - featurePermissionedDomains); env.fund(XRP(1000), alice); - pdomain::Credentials const credentials{{alice, "first credential"}}; + pdomain::Credentials const credentials{{.issuer = alice, .credType = "first credential"}}; env(pdomain::setTx(alice, credentials), Ter(temDISABLED)); env(pdomain::deleteTx(alice, uint256(75)), Ter(temDISABLED)); } @@ -124,40 +124,40 @@ class PermissionedDomains_test : public beast::unit_test::Suite // Test 11 credentials. pdomain::Credentials const credentials11{ - {alice2, "credential1"}, - {alice3, "credential2"}, - {alice4, "credential3"}, - {alice5, "credential4"}, - {alice6, "credential5"}, - {alice7, "credential6"}, - {alice8, "credential7"}, - {alice9, "credential8"}, - {alice10, "credential9"}, - {alice11, "credential10"}, - {alice12, "credential11"}}; + {.issuer = alice2, .credType = "credential1"}, + {.issuer = alice3, .credType = "credential2"}, + {.issuer = alice4, .credType = "credential3"}, + {.issuer = alice5, .credType = "credential4"}, + {.issuer = alice6, .credType = "credential5"}, + {.issuer = alice7, .credType = "credential6"}, + {.issuer = alice8, .credType = "credential7"}, + {.issuer = alice9, .credType = "credential8"}, + {.issuer = alice10, .credType = "credential9"}, + {.issuer = alice11, .credType = "credential10"}, + {.issuer = alice12, .credType = "credential11"}}; BEAST_EXPECT(credentials11.size() == kMaxPermissionedDomainCredentialsArraySize + 1); env(pdomain::setTx(account, credentials11, domain), Ter(temARRAY_TOO_LARGE)); // Test credentials including non-existent issuer. Account const nobody("nobody"); pdomain::Credentials const credentialsNon{ - {alice2, "credential1"}, - {alice3, "credential2"}, - {alice4, "credential3"}, - {nobody, "credential4"}, - {alice5, "credential5"}, - {alice6, "credential6"}, - {alice7, "credential7"}}; + {.issuer = alice2, .credType = "credential1"}, + {.issuer = alice3, .credType = "credential2"}, + {.issuer = alice4, .credType = "credential3"}, + {.issuer = nobody, .credType = "credential4"}, + {.issuer = alice5, .credType = "credential5"}, + {.issuer = alice6, .credType = "credential6"}, + {.issuer = alice7, .credType = "credential7"}}; env(pdomain::setTx(account, credentialsNon, domain), Ter(tecNO_ISSUER)); // Test bad fee env(pdomain::setTx(account, credentials11, domain), Fee(1, true), Ter(temBAD_FEE)); pdomain::Credentials const credentials4{ - {alice2, "credential1"}, - {alice3, "credential2"}, - {alice4, "credential3"}, - {alice5, "credential4"}, + {.issuer = alice2, .credType = "credential1"}, + {.issuer = alice3, .credType = "credential2"}, + {.issuer = alice4, .credType = "credential3"}, + {.issuer = alice5, .credType = "credential4"}, }; auto txJsonMutable = pdomain::setTx(account, credentials4, domain); auto const credentialOrig = txJsonMutable["AcceptedCredentials"][2u]; @@ -192,11 +192,11 @@ class PermissionedDomains_test : public beast::unit_test::Suite // permissioned domains, so transactions should return errors { pdomain::Credentials const credentialsDup{ - {alice7, "credential6"}, - {alice2, "credential1"}, - {alice3, "credential2"}, - {alice2, "credential1"}, - {alice5, "credential4"}, + {.issuer = alice7, .credType = "credential6"}, + {.issuer = alice2, .credType = "credential1"}, + {.issuer = alice3, .credType = "credential2"}, + {.issuer = alice2, .credType = "credential1"}, + {.issuer = alice5, .credType = "credential4"}, }; std::unordered_map human2Acc; @@ -230,11 +230,11 @@ class PermissionedDomains_test : public beast::unit_test::Suite // sort correctly. { pdomain::Credentials const credentialsSame{ - {alice2, "credential3"}, - {alice3, "credential2"}, - {alice2, "credential9"}, - {alice5, "credential4"}, - {alice2, "credential6"}, + {.issuer = alice2, .credType = "credential3"}, + {.issuer = alice3, .credType = "credential2"}, + {.issuer = alice2, .credType = "credential9"}, + {.issuer = alice5, .credType = "credential4"}, + {.issuer = alice2, .credType = "credential6"}, }; std::unordered_map human2Acc; for (auto const& c : credentialsSame) @@ -290,7 +290,7 @@ class PermissionedDomains_test : public beast::unit_test::Suite env.fund(XRP(1000), alice[i]); // Create new from existing account with a single credential. - pdomain::Credentials const credentials1{{alice[2], "credential1"}}; + pdomain::Credentials const credentials1{{.issuer = alice[2], .credType = "credential1"}}; { env(pdomain::setTx(alice[0], credentials1)); BEAST_EXPECT(env.ownerCount(alice[0]) == 1); @@ -314,7 +314,7 @@ class PermissionedDomains_test : public beast::unit_test::Suite "89"; static_assert(kLongCredentialType.size() == kMaxCredentialTypeLength); pdomain::Credentials const longCredentials{ - {alice[1], std::string(kLongCredentialType)}}; + {.issuer = alice[1], .credType = std::string(kLongCredentialType)}}; env(pdomain::setTx(alice[0], longCredentials)); @@ -345,16 +345,16 @@ class PermissionedDomains_test : public beast::unit_test::Suite // Create new from existing account with 10 credentials. // Last credential describe domain owner itself pdomain::Credentials const credentials10{ - {alice[2], "credential1"}, - {alice[3], "credential2"}, - {alice[4], "credential3"}, - {alice[5], "credential4"}, - {alice[6], "credential5"}, - {alice[7], "credential6"}, - {alice[8], "credential7"}, - {alice[9], "credential8"}, - {alice[10], "credential9"}, - {alice[0], "credential10"}, + {.issuer = alice[2], .credType = "credential1"}, + {.issuer = alice[3], .credType = "credential2"}, + {.issuer = alice[4], .credType = "credential3"}, + {.issuer = alice[5], .credType = "credential4"}, + {.issuer = alice[6], .credType = "credential5"}, + {.issuer = alice[7], .credType = "credential6"}, + {.issuer = alice[8], .credType = "credential7"}, + {.issuer = alice[9], .credType = "credential8"}, + {.issuer = alice[10], .credType = "credential9"}, + {.issuer = alice[0], .credType = "credential10"}, }; uint256 domain2; { @@ -434,7 +434,7 @@ class PermissionedDomains_test : public beast::unit_test::Suite env.fund(XRP(1000), alice); auto const setFee(drops(env.current()->fees().increment)); - pdomain::Credentials const credentials{{alice, "first credential"}}; + pdomain::Credentials const credentials{{.issuer = alice, .credType = "first credential"}}; env(pdomain::setTx(alice, credentials)); env.close(); @@ -498,7 +498,7 @@ class PermissionedDomains_test : public beast::unit_test::Suite BEAST_EXPECT(env.ownerCount(alice) == 0); // alice does not have enough XRP to cover the reserve. - pdomain::Credentials const credentials{{alice, "first credential"}}; + pdomain::Credentials const credentials{{.issuer = alice, .credType = "first credential"}}; env(pdomain::setTx(alice, credentials), Ter(tecINSUFFICIENT_RESERVE)); BEAST_EXPECT(env.ownerCount(alice) == 0); BEAST_EXPECT(pdomain::getObjects(alice, env).empty()); diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index 8333fce3b3..dc28388de4 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -1228,7 +1228,7 @@ public: // Try to replace a middle item in the queue // with enough fee to bankrupt bob and make the // later transactions unable to pay their fees - std::int64_t bobFee = env.le(bob)->getFieldAmount(sfBalance).xrp().drops() - (9 * 10 - 1); + std::int64_t bobFee = env.le(bob)->getFieldAmount(sfBalance).xrp().drops() - ((9 * 10) - 1); env(noop(bob), Seq(bobSeq + 5), Fee(bobFee), Ter(telCAN_NOT_QUEUE_BALANCE)); checkMetrics(*this, env, 10, 12, 7, 6); diff --git a/src/test/app/ValidatorList_test.cpp b/src/test/app/ValidatorList_test.cpp index 20a3557db5..80483446a2 100644 --- a/src/test/app/ValidatorList_test.cpp +++ b/src/test/app/ValidatorList_test.cpp @@ -632,7 +632,11 @@ private: checkResult( trustedKeys->applyLists( - manifest1, version, {{expiredblob, expiredSig, {}}, {blob2, sig2, {}}}, siteUri), + manifest1, + version, + {{.blob = expiredblob, .signature = expiredSig, .manifest = {}}, + {.blob = blob2, .signature = sig2, .manifest = {}}}, + siteUri), publisherPublic, ListDisposition::Expired, ListDisposition::Accepted); @@ -665,7 +669,11 @@ private: checkResult( trustedKeys->applyLists( - manifest1, version2, {{blob7, sig7, {}}, {blob8, sig8, {}}}, siteUri), + manifest1, + version2, + {{.blob = blob7, .signature = sig7, .manifest = {}}, + {.blob = blob8, .signature = sig8, .manifest = {}}}, + siteUri), publisherPublic, ListDisposition::Pending, ListDisposition::Pending); @@ -697,7 +705,11 @@ private: checkResult( trustedKeys->applyLists( - manifest1, version, {{blob6a, sig6a, {}}, {blob6, sig6, {}}}, siteUri), + manifest1, + version, + {{.blob = blob6a, .signature = sig6a, .manifest = {}}, + {.blob = blob6, .signature = sig6, .manifest = {}}}, + siteUri), publisherPublic, ListDisposition::Pending, ListDisposition::Pending); @@ -709,7 +721,11 @@ private: checkResult( trustedKeys->applyLists( - manifest1, version, {{blob7, sig7, {}}, {blob6, sig6, {}}}, siteUri), + manifest1, + version, + {{.blob = blob7, .signature = sig7, .manifest = {}}, + {.blob = blob6, .signature = sig6, .manifest = {}}}, + siteUri), publisherPublic, ListDisposition::KnownSequence, ListDisposition::KnownSequence); @@ -720,7 +736,12 @@ private: // try empty or mangled manifest checkResult( - trustedKeys->applyLists("", version, {{blob7, sig7, {}}, {blob6, sig6, {}}}, siteUri), + trustedKeys->applyLists( + "", + version, + {{.blob = blob7, .signature = sig7, .manifest = {}}, + {.blob = blob6, .signature = sig6, .manifest = {}}}, + siteUri), publisherPublic, ListDisposition::Invalid, ListDisposition::Invalid); @@ -729,7 +750,8 @@ private: trustedKeys->applyLists( base64Encode("not a manifest"), version, - {{blob7, sig7, {}}, {blob6, sig6, {}}}, + {{.blob = blob7, .signature = sig7, .manifest = {}}, + {.blob = blob6, .signature = sig6, .manifest = {}}}, siteUri), publisherPublic, ListDisposition::Invalid, @@ -740,7 +762,11 @@ private: randomMasterKey(), publisherSecret, pubSigningKeys1.first, pubSigningKeys1.second, 1)); checkResult( - trustedKeys->applyLists(untrustedManifest, version, {{blob2, sig2, {}}}, siteUri), + trustedKeys->applyLists( + untrustedManifest, + version, + {{.blob = blob2, .signature = sig2, .manifest = {}}}, + siteUri), publisherPublic, ListDisposition::Untrusted, ListDisposition::Untrusted); @@ -748,7 +774,11 @@ private: // do not use list with unhandled version auto const badVersion = 666; checkResult( - trustedKeys->applyLists(manifest1, badVersion, {{blob2, sig2, {}}}, siteUri), + trustedKeys->applyLists( + manifest1, + badVersion, + {{.blob = blob2, .signature = sig2, .manifest = {}}}, + siteUri), publisherPublic, ListDisposition::UnsupportedVersion, ListDisposition::UnsupportedVersion); @@ -759,7 +789,8 @@ private: auto const sig3 = signList(blob3, pubSigningKeys1); checkResult( - trustedKeys->applyLists(manifest1, version, {{blob3, sig3, {}}}, siteUri), + trustedKeys->applyLists( + manifest1, version, {{.blob = blob3, .signature = sig3, .manifest = {}}}, siteUri), publisherPublic, ListDisposition::Accepted, ListDisposition::Accepted); @@ -780,7 +811,11 @@ private: // do not re-apply lists with past or current sequence numbers checkResult( trustedKeys->applyLists( - manifest1, version, {{blob2, sig2, {}}, {blob3, sig3, {}}}, siteUri), + manifest1, + version, + {{.blob = blob2, .signature = sig2, .manifest = {}}, + {.blob = blob3, .signature = sig3, .manifest = {}}}, + siteUri), publisherPublic, ListDisposition::Stale, ListDisposition::SameSequence); @@ -799,7 +834,9 @@ private: trustedKeys->applyLists( manifest2, version, - {{blob2, sig2, manifest1}, {blob3, sig3, manifest1}, {blob4, sig4, {}}}, + {{.blob = blob2, .signature = sig2, .manifest = manifest1}, + {.blob = blob3, .signature = sig3, .manifest = manifest1}, + {.blob = blob4, .signature = sig4, .manifest = {}}}, siteUri), publisherPublic, ListDisposition::Stale, @@ -820,7 +857,11 @@ private: auto const blob5 = makeList(lists.at(5), sequence5, validUntil.time_since_epoch().count()); auto const badSig = signList(blob5, pubSigningKeys1); checkResult( - trustedKeys->applyLists(manifest1, version, {{blob5, badSig, {}}}, siteUri), + trustedKeys->applyLists( + manifest1, + version, + {{.blob = blob5, .signature = badSig, .manifest = {}}}, + siteUri), publisherPublic, ListDisposition::Invalid, ListDisposition::Invalid); @@ -833,7 +874,11 @@ private: // Reprocess the pending list, but the signature is no longer valid checkResult( trustedKeys->applyLists( - manifest1, version, {{blob7, sig7, {}}, {blob8, sig8, {}}}, siteUri), + manifest1, + version, + {{.blob = blob7, .signature = sig7, .manifest = {}}, + {.blob = blob8, .signature = sig8, .manifest = {}}}, + siteUri), publisherPublic, ListDisposition::Invalid, ListDisposition::Invalid); @@ -884,7 +929,11 @@ private: checkResult( trustedKeys->applyLists( - manifest2, version, {{blob8, sig8, manifest1}, {blob8, sig82, {}}}, siteUri), + manifest2, + version, + {{.blob = blob8, .signature = sig8, .manifest = manifest1}, + {.blob = blob8, .signature = sig82, .manifest = {}}}, + siteUri), publisherPublic, ListDisposition::Invalid, ListDisposition::SameSequence); @@ -903,7 +952,11 @@ private: auto const sig9 = signList(blob9, signingKeysMax); checkResult( - trustedKeys->applyLists(maxManifest, version, {{blob9, sig9, {}}}, siteUri), + trustedKeys->applyLists( + maxManifest, + version, + {{.blob = blob9, .signature = sig9, .manifest = {}}}, + siteUri), publisherPublic, ListDisposition::Untrusted, ListDisposition::Untrusted); @@ -1900,7 +1953,9 @@ private: return PreparedList{ .publisherPublic = publisherPublic, .manifest = manifest, - .blobs = {{blob1, sig1, {}}, {blob2, sig2, {}}}, + .blobs = + {{.blob = blob1, .signature = sig1, .manifest = {}}, + {.blob = blob2, .signature = sig2, .manifest = {}}}, .version = version, .expirations = {expiration1, expiration2}}; }; diff --git a/src/test/app/ValidatorSite_test.cpp b/src/test/app/ValidatorSite_test.cpp index f7f805faa2..e3e3ec27df 100644 --- a/src/test/app/ValidatorSite_test.cpp +++ b/src/test/app/ValidatorSite_test.cpp @@ -380,226 +380,333 @@ public: for (auto ssl : {true, false}) { // fetch single site - testFetchList(good, {{"/validators", "", ssl}}); - testFetchList(good, {{"/validators2", "", ssl}}); + testFetchList(good, {{.path = "/validators", .msg = "", .ssl = ssl}}); + testFetchList(good, {{.path = "/validators2", .msg = "", .ssl = ssl}}); // fetch multiple sites - testFetchList(good, {{"/validators", "", ssl}, {"/validators", "", ssl}}); - testFetchList(good, {{"/validators", "", ssl}, {"/validators2", "", ssl}}); - testFetchList(good, {{"/validators2", "", ssl}, {"/validators", "", ssl}}); - testFetchList(good, {{"/validators2", "", ssl}, {"/validators2", "", ssl}}); + testFetchList( + good, + {{.path = "/validators", .msg = "", .ssl = ssl}, + {.path = "/validators", .msg = "", .ssl = ssl}}); + testFetchList( + good, + {{.path = "/validators", .msg = "", .ssl = ssl}, + {.path = "/validators2", .msg = "", .ssl = ssl}}); + testFetchList( + good, + {{.path = "/validators2", .msg = "", .ssl = ssl}, + {.path = "/validators", .msg = "", .ssl = ssl}}); + testFetchList( + good, + {{.path = "/validators2", .msg = "", .ssl = ssl}, + {.path = "/validators2", .msg = "", .ssl = ssl}}); // fetch single site with single redirects - testFetchList(good, {{"/redirect_once/301", "", ssl}}); - testFetchList(good, {{"/redirect_once/302", "", ssl}}); - testFetchList(good, {{"/redirect_once/307", "", ssl}}); - testFetchList(good, {{"/redirect_once/308", "", ssl}}); + testFetchList(good, {{.path = "/redirect_once/301", .msg = "", .ssl = ssl}}); + testFetchList(good, {{.path = "/redirect_once/302", .msg = "", .ssl = ssl}}); + testFetchList(good, {{.path = "/redirect_once/307", .msg = "", .ssl = ssl}}); + testFetchList(good, {{.path = "/redirect_once/308", .msg = "", .ssl = ssl}}); // one redirect, one not - testFetchList(good, {{"/validators", "", ssl}, {"/redirect_once/302", "", ssl}}); - testFetchList(good, {{"/validators2", "", ssl}, {"/redirect_once/302", "", ssl}}); + testFetchList( + good, + {{.path = "/validators", .msg = "", .ssl = ssl}, + {.path = "/redirect_once/302", .msg = "", .ssl = ssl}}); + testFetchList( + good, + {{.path = "/validators2", .msg = "", .ssl = ssl}, + {.path = "/redirect_once/302", .msg = "", .ssl = ssl}}); // UNLs with a "gap" between validUntil of one and validFrom of the // next testFetchList( good, - {{"/validators2", - "", - ssl, - false, - false, - 1, - detail::kDefaultExpires, - std::chrono::seconds{-90}}}); + {{.path = "/validators2", + .msg = "", + .ssl = ssl, + .failFetch = false, + .failApply = false, + .serverVersion = 1, + .expiresFromNow = detail::kDefaultExpires, + .effectiveOverlap = std::chrono::seconds{-90}}}); // fetch single site with unending redirect (fails to load) testFetchList( - good, {{"/redirect_forever/301", "Exceeded max redirects", ssl, true, true}}); + good, + {{.path = "/redirect_forever/301", + .msg = "Exceeded max redirects", + .ssl = ssl, + .failFetch = true, + .failApply = true}}); // two that redirect forever testFetchList( good, - {{"/redirect_forever/307", "Exceeded max redirects", ssl, true, true}, - {"/redirect_forever/308", "Exceeded max redirects", ssl, true, true}}); + {{.path = "/redirect_forever/307", + .msg = "Exceeded max redirects", + .ssl = ssl, + .failFetch = true, + .failApply = true}, + {.path = "/redirect_forever/308", + .msg = "Exceeded max redirects", + .ssl = ssl, + .failFetch = true, + .failApply = true}}); // one unending redirect, one not testFetchList( good, - {{"/validators", "", ssl}, - {"/redirect_forever/302", "Exceeded max redirects", ssl, true, true}}); + {{.path = "/validators", .msg = "", .ssl = ssl}, + {.path = "/redirect_forever/302", + .msg = "Exceeded max redirects", + .ssl = ssl, + .failFetch = true, + .failApply = true}}); // one unending redirect, one not testFetchList( good, - {{"/validators2", "", ssl}, - {"/redirect_forever/302", "Exceeded max redirects", ssl, true, true}}); + {{.path = "/validators2", .msg = "", .ssl = ssl}, + {.path = "/redirect_forever/302", + .msg = "Exceeded max redirects", + .ssl = ssl, + .failFetch = true, + .failApply = true}}); // invalid redir Location testFetchList( good, - {{"/redirect_to/ftp://invalid-url/302", - "Invalid redirect location", - ssl, - true, - true}}); + {{.path = "/redirect_to/ftp://invalid-url/302", + .msg = "Invalid redirect location", + .ssl = ssl, + .failFetch = true, + .failApply = true}}); testFetchList( good, - {{"/redirect_to/file://invalid-url/302", - "Invalid redirect location", - ssl, - true, - true}}); + {{.path = "/redirect_to/file://invalid-url/302", + .msg = "Invalid redirect location", + .ssl = ssl, + .failFetch = true, + .failApply = true}}); // invalid json testFetchList( - good, {{"/validators/bad", "Unable to parse JSON response", ssl, true, true}}); + good, + {{.path = "/validators/bad", + .msg = "Unable to parse JSON response", + .ssl = ssl, + .failFetch = true, + .failApply = true}}); testFetchList( - good, {{"/validators2/bad", "Unable to parse JSON response", ssl, true, true}}); + good, + {{.path = "/validators2/bad", + .msg = "Unable to parse JSON response", + .ssl = ssl, + .failFetch = true, + .failApply = true}}); // error status returned - testFetchList(good, {{"/bad-resource", "returned bad status", ssl, true, true}}); + testFetchList( + good, + {{.path = "/bad-resource", + .msg = "returned bad status", + .ssl = ssl, + .failFetch = true, + .failApply = true}}); // location field missing testFetchList( good, - {{"/redirect_nolo/308", "returned a redirect with no Location", ssl, true, true}}); + {{.path = "/redirect_nolo/308", + .msg = "returned a redirect with no Location", + .ssl = ssl, + .failFetch = true, + .failApply = true}}); // json fields missing testFetchList( good, - {{"/validators/missing", "Missing fields in JSON response", ssl, true, true}}); + {{.path = "/validators/missing", + .msg = "Missing fields in JSON response", + .ssl = ssl, + .failFetch = true, + .failApply = true}}); testFetchList( good, - {{"/validators2/missing", "Missing fields in JSON response", ssl, true, true}}); + {{.path = "/validators2/missing", + .msg = "Missing fields in JSON response", + .ssl = ssl, + .failFetch = true, + .failApply = true}}); // timeout - testFetchList(good, {{"/sleep/13", "took too long", ssl, true, true}}); + testFetchList( + good, + {{.path = "/sleep/13", + .msg = "took too long", + .ssl = ssl, + .failFetch = true, + .failApply = true}}); // bad manifest format using known versions // * Retrieves a v1 formatted list claiming version 2 - testFetchList(good, {{"/validators", "Missing fields", ssl, true, true, 2}}); + testFetchList( + good, + {{.path = "/validators", + .msg = "Missing fields", + .ssl = ssl, + .failFetch = true, + .failApply = true, + .serverVersion = 2}}); // * Retrieves a v2 formatted list claiming version 1 - testFetchList(good, {{"/validators2", "Missing fields", ssl, true, true, 0}}); + testFetchList( + good, + {{.path = "/validators2", + .msg = "Missing fields", + .ssl = ssl, + .failFetch = true, + .failApply = true, + .serverVersion = 0}}); // bad manifest version // Because versions other than 1 are treated as v2, the v1 // list won't have the blobs_v2 fields, and thus will claim to have // missing fields - testFetchList(good, {{"/validators", "Missing fields", ssl, true, true, 4}}); - testFetchList(good, {{"/validators2", "1 unsupported version", ssl, false, true, 4}}); + testFetchList( + good, + {{.path = "/validators", + .msg = "Missing fields", + .ssl = ssl, + .failFetch = true, + .failApply = true, + .serverVersion = 4}}); + testFetchList( + good, + {{.path = "/validators2", + .msg = "1 unsupported version", + .ssl = ssl, + .failFetch = false, + .failApply = true, + .serverVersion = 4}}); using namespace std::chrono_literals; // get expired validator list testFetchList( good, - {{"/validators", "Applied 1 expired validator list(s)", ssl, false, false, 1, 0s}}); + {{.path = "/validators", + .msg = "Applied 1 expired validator list(s)", + .ssl = ssl, + .failFetch = false, + .failApply = false, + .serverVersion = 1, + .expiresFromNow = 0s}}); testFetchList( good, - {{"/validators2", - "Applied 1 expired validator list(s)", - ssl, - false, - false, - 1, - 0s, - -1s}}); + {{.path = "/validators2", + .msg = "Applied 1 expired validator list(s)", + .ssl = ssl, + .failFetch = false, + .failApply = false, + .serverVersion = 1, + .expiresFromNow = 0s, + .effectiveOverlap = -1s}}); // force an out-of-range validUntil value testFetchList( good, - {{"/validators", - "1 invalid validator list(s)", - ssl, - false, - true, - 1, - std::chrono::seconds{json::Value::kMinInt}}}); + {{.path = "/validators", + .msg = "1 invalid validator list(s)", + .ssl = ssl, + .failFetch = false, + .failApply = true, + .serverVersion = 1, + .expiresFromNow = std::chrono::seconds{json::Value::kMinInt}}}); // force an out-of-range validUntil value on the future list // The first list is accepted. The second fails. The parser // returns the "best" result, so this looks like a success. testFetchList( good, - {{"/validators2", - "", - ssl, - false, - false, - 1, - std::chrono::seconds{json::Value::kMaxInt - 300}, - 299s}}); + {{.path = "/validators2", + .msg = "", + .ssl = ssl, + .failFetch = false, + .failApply = false, + .serverVersion = 1, + .expiresFromNow = std::chrono::seconds{json::Value::kMaxInt - 300}, + .effectiveOverlap = 299s}}); // force an out-of-range validFrom value // The first list is accepted. The second fails. The parser // returns the "best" result, so this looks like a success. testFetchList( good, - {{"/validators2", - "", - ssl, - false, - false, - 1, - std::chrono::seconds{json::Value::kMaxInt - 300}, - 301s}}); + {{.path = "/validators2", + .msg = "", + .ssl = ssl, + .failFetch = false, + .failApply = false, + .serverVersion = 1, + .expiresFromNow = std::chrono::seconds{json::Value::kMaxInt - 300}, + .effectiveOverlap = 301s}}); // force an out-of-range validUntil value on _both_ lists testFetchList( good, - {{"/validators2", - "2 invalid validator list(s)", - ssl, - false, - true, - 1, - std::chrono::seconds{json::Value::kMinInt}, - std::chrono::seconds{json::Value::kMaxInt - 6000}}}); + {{.path = "/validators2", + .msg = "2 invalid validator list(s)", + .ssl = ssl, + .failFetch = false, + .failApply = true, + .serverVersion = 1, + .expiresFromNow = std::chrono::seconds{json::Value::kMinInt}, + .effectiveOverlap = std::chrono::seconds{json::Value::kMaxInt - 6000}}}); // verify refresh intervals are properly clamped testFetchList( good, - {{"/validators/refresh/0", - "", - ssl, - false, - false, - 1, - detail::kDefaultExpires, - detail::kDefaultEffectiveOverlap, - 1}}); // minimum of 1 minute + {{.path = "/validators/refresh/0", + .msg = "", + .ssl = ssl, + .failFetch = false, + .failApply = false, + .serverVersion = 1, + .expiresFromNow = detail::kDefaultExpires, + .effectiveOverlap = detail::kDefaultEffectiveOverlap, + .expectedRefreshMin = 1}}); // minimum of 1 minute testFetchList( good, - {{"/validators2/refresh/0", - "", - ssl, - false, - false, - 1, - detail::kDefaultExpires, - detail::kDefaultEffectiveOverlap, - 1}}); // minimum of 1 minute + {{.path = "/validators2/refresh/0", + .msg = "", + .ssl = ssl, + .failFetch = false, + .failApply = false, + .serverVersion = 1, + .expiresFromNow = detail::kDefaultExpires, + .effectiveOverlap = detail::kDefaultEffectiveOverlap, + .expectedRefreshMin = 1}}); // minimum of 1 minute testFetchList( good, - {{"/validators/refresh/10", - "", - ssl, - false, - false, - 1, - detail::kDefaultExpires, - detail::kDefaultEffectiveOverlap, - 10}}); // 10 minutes is fine + {{.path = "/validators/refresh/10", + .msg = "", + .ssl = ssl, + .failFetch = false, + .failApply = false, + .serverVersion = 1, + .expiresFromNow = detail::kDefaultExpires, + .effectiveOverlap = detail::kDefaultEffectiveOverlap, + .expectedRefreshMin = 10}}); // 10 minutes is fine testFetchList( good, - {{"/validators2/refresh/10", - "", - ssl, - false, - false, - 1, - detail::kDefaultExpires, - detail::kDefaultEffectiveOverlap, - 10}}); // 10 minutes is fine + {{.path = "/validators2/refresh/10", + .msg = "", + .ssl = ssl, + .failFetch = false, + .failApply = false, + .serverVersion = 1, + .expiresFromNow = detail::kDefaultExpires, + .effectiveOverlap = detail::kDefaultEffectiveOverlap, + .expectedRefreshMin = 10}}); // 10 minutes is fine testFetchList( good, - {{"/validators/refresh/2000", - "", - ssl, - false, - false, - 1, - detail::kDefaultExpires, - detail::kDefaultEffectiveOverlap, - 60 * 24}}); // max of 24 hours + {{.path = "/validators/refresh/2000", + .msg = "", + .ssl = ssl, + .failFetch = false, + .failApply = false, + .serverVersion = 1, + .expiresFromNow = detail::kDefaultExpires, + .effectiveOverlap = detail::kDefaultEffectiveOverlap, + .expectedRefreshMin = 60 * 24}}); // max of 24 hours testFetchList( good, - {{"/validators2/refresh/2000", - "", - ssl, - false, - false, - 1, - detail::kDefaultExpires, - detail::kDefaultEffectiveOverlap, - 60 * 24}}); // max of 24 hours + {{.path = "/validators2/refresh/2000", + .msg = "", + .ssl = ssl, + .failFetch = false, + .failApply = false, + .serverVersion = 1, + .expiresFromNow = detail::kDefaultExpires, + .effectiveOverlap = detail::kDefaultEffectiveOverlap, + .expectedRefreshMin = 60 * 24}}); // max of 24 hours } using namespace boost::filesystem; for (auto const& file : directory_iterator(good.subdir())) diff --git a/src/test/basics/PerfLog_test.cpp b/src/test/basics/PerfLog_test.cpp index c370c241c5..41b5f81f5d 100644 --- a/src/test/basics/PerfLog_test.cpp +++ b/src/test/basics/PerfLog_test.cpp @@ -619,7 +619,7 @@ public: // Total queued duration is triangle number of (i + 1). BEAST_EXPECT( - jsonToUInt64(total[jss::queued_duration_us]) == (((i * i) + 3 * i + 2) / 2)); + jsonToUInt64(total[jss::queued_duration_us]) == (((i * i) + (3 * i) + 2) / 2)); BEAST_EXPECT(total[jss::running_duration_us] == "0"); } diff --git a/src/test/beast/aged_associative_container_test.cpp b/src/test/beast/aged_associative_container_test.cpp index f2ce72b584..d7f74aaa7d 100644 --- a/src/test/beast/aged_associative_container_test.cpp +++ b/src/test/beast/aged_associative_container_test.cpp @@ -414,11 +414,11 @@ public: // unordered template - std::enable_if_t::type::is_unordered::value> + std::enable_if_t::is_unordered::value> checkUnorderedContentsRefRef(C&& c, Values const& v); template - std::enable_if_t::type::is_unordered::value> + std::enable_if_t::is_unordered::value> checkUnorderedContentsRefRef(C&&, Values const&) { } @@ -641,7 +641,7 @@ AgedAssociativeContainerTestBase::checkMapContents(Container& c, Values const& v // unordered template -std::enable_if_t::type::is_unordered::value> +std::enable_if_t::is_unordered::value> AgedAssociativeContainerTestBase::checkUnorderedContentsRefRef(C&& c, Values const& v) { using Cont = std::remove_reference_t; diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index ce6774827e..92f59fe644 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -1457,14 +1457,14 @@ r.ripple.com:51235 }; std::vector const units = { - {"seconds", 1, 15 * 60, false}, - {"minutes", 60, 14, false}, - {"minutes", 60, 15, true}, - {"hours", 3600, 10, true}, - {"days", 86400, 10, true}, - {"weeks", 604800, 2, true}, - {"months", 2592000, 1, false}, - {"years", 31536000, 1, false}}; + {.unit = "seconds", .numSeconds = 1, .configVal = 15 * 60, .shouldPass = false}, + {.unit = "minutes", .numSeconds = 60, .configVal = 14, .shouldPass = false}, + {.unit = "minutes", .numSeconds = 60, .configVal = 15, .shouldPass = true}, + {.unit = "hours", .numSeconds = 3600, .configVal = 10, .shouldPass = true}, + {.unit = "days", .numSeconds = 86400, .configVal = 10, .shouldPass = true}, + {.unit = "weeks", .numSeconds = 604800, .configVal = 2, .shouldPass = true}, + {.unit = "months", .numSeconds = 2592000, .configVal = 1, .shouldPass = false}, + {.unit = "years", .numSeconds = 31536000, .configVal = 1, .shouldPass = false}}; std::string space; for (auto& [unit, sec, val, shouldPass] : units) diff --git a/src/test/jtx/impl/WSClient.cpp b/src/test/jtx/impl/WSClient.cpp index 551fd1404b..6d069523d2 100644 --- a/src/test/jtx/impl/WSClient.cpp +++ b/src/test/jtx/impl/WSClient.cpp @@ -70,7 +70,7 @@ class WSClientImpl : public WSClient continue; ParsedPort pp; parsePort(pp, cfg[name], log); - if (pp.protocol.count(ps) == 0) + if (!pp.protocol.contains(ps)) continue; using namespace boost::asio::ip; if (pp.ip && pp.ip->is_unspecified()) diff --git a/src/test/jtx/impl/permissioned_dex.cpp b/src/test/jtx/impl/permissioned_dex.cpp index 5c059e9e80..a6b24d7ac6 100644 --- a/src/test/jtx/impl/permissioned_dex.cpp +++ b/src/test/jtx/impl/permissioned_dex.cpp @@ -26,7 +26,7 @@ setupDomain( env.fund(XRP(100000), domainOwner); env.close(); - pdomain::Credentials const credentials{{domainOwner, credType}}; + pdomain::Credentials const credentials{{.issuer = domainOwner, .credType = credType}}; env(pdomain::setTx(domainOwner, credentials)); auto const objects = pdomain::getObjects(domainOwner, env); diff --git a/src/test/jtx/impl/permissioned_domains.cpp b/src/test/jtx/impl/permissioned_domains.cpp index 690451c7d8..385008be43 100644 --- a/src/test/jtx/impl/permissioned_domains.cpp +++ b/src/test/jtx/impl/permissioned_domains.cpp @@ -130,7 +130,9 @@ credentialsFromJson( auto const& credentialType = obj["CredentialType"]; // NOLINTNEXTLINE(bugprone-unchecked-optional-access): used only in tests auto blob = strUnHex(credentialType.asString()).value(); - ret.push_back({human2Acc.at(issuer.asString()), std::string(blob.begin(), blob.end())}); + ret.push_back( + {.issuer = human2Acc.at(issuer.asString()), + .credType = std::string(blob.begin(), blob.end())}); } return ret; } diff --git a/src/test/nodestore/import_test.cpp b/src/test/nodestore/import_test.cpp index a80b5ccc93..de99edd655 100644 --- a/src/test/nodestore/import_test.cpp +++ b/src/test/nodestore/import_test.cpp @@ -297,17 +297,17 @@ public: auto const args = parseArgs(arg()); bool usage = args.empty(); - if (!usage && args.find("from") == args.end()) + if (!usage && !args.contains("from")) { log << "Missing parameter: from"; usage = true; } - if (!usage && args.find("to") == args.end()) + if (!usage && !args.contains("to")) { log << "Missing parameter: to"; usage = true; } - if (!usage && args.find("buffer") == args.end()) + if (!usage && !args.contains("buffer")) { log << "Missing parameter: buffer"; usage = true; diff --git a/src/test/rpc/AccountObjects_test.cpp b/src/test/rpc/AccountObjects_test.cpp index 4307b7ab7f..31b20b37d4 100644 --- a/src/test/rpc/AccountObjects_test.cpp +++ b/src/test/rpc/AccountObjects_test.cpp @@ -692,11 +692,11 @@ public: { std::string const credentialType1 = "credential1"; - Account issuer("issuer"); + Account const issuer("issuer"); env.fund(XRP(5000), issuer); // gw creates an PermissionedDomain. - env(pdomain::setTx(gw, {{issuer, credentialType1}})); + env(pdomain::setTx(gw, {{.issuer = issuer, .credType = credentialType1}})); env.close(); // Find the PermissionedDomain. diff --git a/src/test/rpc/DepositAuthorized_test.cpp b/src/test/rpc/DepositAuthorized_test.cpp index 89053557a7..e6720602c9 100644 --- a/src/test/rpc/DepositAuthorized_test.cpp +++ b/src/test/rpc/DepositAuthorized_test.cpp @@ -324,7 +324,7 @@ public: env.close(); // becky authorize any account recognized by carol to make a payment - env(deposit::authCredentials(becky, {{carol, credType}})); + env(deposit::authCredentials(becky, {{.issuer = carol, .credType = credType}})); env.close(); { @@ -507,7 +507,7 @@ public: env.close(); // becky authorize any account recognized by carol to make a payment - env(deposit::authCredentials(becky, {{carol, credType2}})); + env(deposit::authCredentials(becky, {{.issuer = carol, .credType = credType2}})); env.close(); { diff --git a/src/test/rpc/LedgerEntry_test.cpp b/src/test/rpc/LedgerEntry_test.cpp index d231a2d4a0..dd9eb1c119 100644 --- a/src/test/rpc/LedgerEntry_test.cpp +++ b/src/test/rpc/LedgerEntry_test.cpp @@ -784,8 +784,8 @@ class LedgerEntry_test : public beast::unit_test::Suite env, jss::amm, { - {jss::asset, "malformedRequest"}, - {jss::asset2, "malformedRequest"}, + {.fieldName = jss::asset, .malformedErrorMsg = "malformedRequest"}, + {.fieldName = jss::asset2, .malformedErrorMsg = "malformedRequest"}, }); }; auto getIOU = [&](Env& env) -> PrettyAsset { return alice["USD"]; }; @@ -900,9 +900,9 @@ class LedgerEntry_test : public beast::unit_test::Suite env, jss::credential, { - {jss::subject, "malformedRequest"}, - {jss::issuer, "malformedRequest"}, - {jss::credential_type, "malformedRequest"}, + {.fieldName = jss::subject, .malformedErrorMsg = "malformedRequest"}, + {.fieldName = jss::issuer, .malformedErrorMsg = "malformedRequest"}, + {.fieldName = jss::credential_type, .malformedErrorMsg = "malformedRequest"}, }); } } @@ -954,8 +954,8 @@ class LedgerEntry_test : public beast::unit_test::Suite env, jss::delegate, { - {jss::account, "malformedAddress"}, - {jss::authorize, "malformedAddress"}, + {.fieldName = jss::account, .malformedErrorMsg = "malformedAddress"}, + {.fieldName = jss::authorize, .malformedErrorMsg = "malformedAddress"}, }); } } @@ -1011,8 +1011,10 @@ class LedgerEntry_test : public beast::unit_test::Suite env, jss::deposit_preauth, { - {jss::owner, "malformedOwner"}, - {jss::authorized, "malformedAuthorized", false}, + {.fieldName = jss::owner, .malformedErrorMsg = "malformedOwner"}, + {.fieldName = jss::authorized, + .malformedErrorMsg = "malformedAuthorized", + .required = false}, }); } } @@ -1037,7 +1039,7 @@ class LedgerEntry_test : public beast::unit_test::Suite // Setup Bob with DepositAuth env(fset(bob, asfDepositAuth)); env.close(); - env(deposit::authCredentials(bob, {{issuer, credType}})); + env(deposit::authCredentials(bob, {{.issuer = issuer, .credType = credType}})); env.close(); } @@ -1458,7 +1460,10 @@ class LedgerEntry_test : public beast::unit_test::Suite { // Malformed escrow fields runLedgerEntryTest( - env, jss::escrow, {{jss::owner, "malformedOwner"}, {jss::seq, "malformedSeq"}}); + env, + jss::escrow, + {{.fieldName = jss::owner, .malformedErrorMsg = "malformedOwner"}, + {.fieldName = jss::seq, .malformedErrorMsg = "malformedSeq"}}); } } @@ -1667,7 +1672,8 @@ class LedgerEntry_test : public beast::unit_test::Suite runLedgerEntryTest( env, jss::offer, - {{jss::account, "malformedAddress"}, {jss::seq, "malformedRequest"}}); + {{.fieldName = jss::account, .malformedErrorMsg = "malformedAddress"}, + {.fieldName = jss::seq, .malformedErrorMsg = "malformedRequest"}}); } } @@ -1774,8 +1780,8 @@ class LedgerEntry_test : public beast::unit_test::Suite env, fieldName, { - {jss::accounts, "malformedRequest"}, - {jss::currency, "malformedCurrency"}, + {.fieldName = jss::accounts, .malformedErrorMsg = "malformedRequest"}, + {.fieldName = jss::currency, .malformedErrorMsg = "malformedCurrency"}, }); } { @@ -1955,8 +1961,8 @@ class LedgerEntry_test : public beast::unit_test::Suite env, jss::ticket, { - {jss::account, "malformedAddress"}, - {jss::ticket_seq, "malformedRequest"}, + {.fieldName = jss::account, .malformedErrorMsg = "malformedAddress"}, + {.fieldName = jss::ticket_seq, .malformedErrorMsg = "malformedRequest"}, }); } } @@ -2034,8 +2040,9 @@ class LedgerEntry_test : public beast::unit_test::Suite env, jss::oracle, { - {jss::account, "malformedAccount"}, - {jss::oracle_document_id, "malformedDocumentID"}, + {.fieldName = jss::account, .malformedErrorMsg = "malformedAccount"}, + {.fieldName = jss::oracle_document_id, + .malformedErrorMsg = "malformedDocumentID"}, }); } } @@ -2172,7 +2179,7 @@ class LedgerEntry_test : public beast::unit_test::Suite env.close(); auto const seq = env.seq(alice); - env(pdomain::setTx(alice, {{alice, "first credential"}})); + env(pdomain::setTx(alice, {{.issuer = alice, .credType = "first credential"}})); env.close(); auto const objects = pdomain::getObjects(alice, env); if (!BEAST_EXPECT(objects.size() == 1)) @@ -2221,8 +2228,8 @@ class LedgerEntry_test : public beast::unit_test::Suite env, jss::permissioned_domain, { - {jss::account, "malformedAddress"}, - {jss::seq, "malformedRequest"}, + {.fieldName = jss::account, .malformedErrorMsg = "malformedAddress"}, + {.fieldName = jss::seq, .malformedErrorMsg = "malformedRequest"}, }); } } diff --git a/src/xrpld/app/misc/detail/ValidatorList.cpp b/src/xrpld/app/misc/detail/ValidatorList.cpp index 57b65814e1..0981c32050 100644 --- a/src/xrpld/app/misc/detail/ValidatorList.cpp +++ b/src/xrpld/app/misc/detail/ValidatorList.cpp @@ -455,7 +455,7 @@ ValidatorList::parseBlobs(std::uint32_t version, json::Value const& body) std::vector ValidatorList::parseBlobs(protocol::TMValidatorList const& body) { - return {{body.blob(), body.signature(), {}}}; + return {{.blob = body.blob(), .signature = body.signature(), .manifest = {}}}; } // static diff --git a/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp b/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp index fcf30fa4f4..5d276dc9c5 100644 --- a/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp +++ b/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp @@ -18,7 +18,8 @@ Config::Config() : outPeers(calcOutPeers()) std::size_t Config::calcOutPeers() const { - return std::max((maxPeers * Tuning::kOutPercent + 50) / 100, std::size_t(Tuning::kMinOutCount)); + return std::max( + ((maxPeers * Tuning::kOutPercent) + 50) / 100, std::size_t(Tuning::kMinOutCount)); } void diff --git a/src/xrpld/rpc/detail/Pathfinder.cpp b/src/xrpld/rpc/detail/Pathfinder.cpp index daa50cfb07..25da86ef8f 100644 --- a/src/xrpld/rpc/detail/Pathfinder.cpp +++ b/src/xrpld/rpc/detail/Pathfinder.cpp @@ -568,7 +568,11 @@ Pathfinder::rankPaths( JLOG(j_.debug()) << "findPaths: quality: " << uQuality << ": " << currentPath.getJson(JsonOptions::Values::None); - rankedPaths.push_back({uQuality, currentPath.size(), liquidity, i}); + rankedPaths.push_back( + {.quality = uQuality, + .length = currentPath.size(), + .liquidity = liquidity, + .index = i}); } } } @@ -1373,7 +1377,7 @@ fillPaths(Pathfinder::PaymentType type, PathCostList const& costs) auto& list = gPathTable[type]; XRPL_ASSERT(list.empty(), "xrpl::fillPaths : empty paths"); for (auto& cost : costs) - list.push_back({cost.cost, makePath(cost.path)}); + list.push_back({.searchLevel = cost.cost, .type = makePath(cost.path)}); } } // namespace @@ -1396,58 +1400,58 @@ Pathfinder::initPathTable() fillPaths( PaymentType::XrpToNonXrp, - {{1, "sfd"}, // source -> book -> gateway - {3, "sfad"}, // source -> book -> account -> destination - {5, "sfaad"}, // source -> book -> account -> account -> destination - {6, "sbfd"}, // source -> book -> book -> destination - {8, "sbafd"}, // source -> book -> account -> book -> destination - {9, "sbfad"}, // source -> book -> book -> account -> destination - {10, "sbafad"}}); + {{.cost = 1, .path = "sfd"}, // source -> book -> gateway + {.cost = 3, .path = "sfad"}, // source -> book -> account -> destination + {.cost = 5, .path = "sfaad"}, // source -> book -> account -> account -> destination + {.cost = 6, .path = "sbfd"}, // source -> book -> book -> destination + {.cost = 8, .path = "sbafd"}, // source -> book -> account -> book -> destination + {.cost = 9, .path = "sbfad"}, // source -> book -> book -> account -> destination + {.cost = 10, .path = "sbafad"}}); fillPaths( PaymentType::NonXrpToXrp, - {{1, "sxd"}, // gateway buys XRP - {2, "saxd"}, // source -> gateway -> book(XRP) -> dest - {6, "saaxd"}, - {7, "sbxd"}, - {8, "sabxd"}, - {9, "sabaxd"}}); + {{.cost = 1, .path = "sxd"}, // gateway buys XRP + {.cost = 2, .path = "saxd"}, // source -> gateway -> book(XRP) -> dest + {.cost = 6, .path = "saaxd"}, + {.cost = 7, .path = "sbxd"}, + {.cost = 8, .path = "sabxd"}, + {.cost = 9, .path = "sabaxd"}}); // non-XRP to non-XRP (same currency) fillPaths( PaymentType::NonXrpToSame, { - {1, "sad"}, // source -> gateway -> destination - {1, "sfd"}, // source -> book -> destination - {4, "safd"}, // source -> gateway -> book -> destination - {4, "sfad"}, - {5, "saad"}, - {5, "sbfd"}, - {6, "sxfad"}, - {6, "safad"}, - {6, "saxfd"}, // source -> gateway -> book to XRP -> book -> - // destination - {6, "saxfad"}, - {6, "sabfd"}, // source -> gateway -> book -> book -> destination - {7, "saaad"}, + {.cost = 1, .path = "sad"}, // source -> gateway -> destination + {.cost = 1, .path = "sfd"}, // source -> book -> destination + {.cost = 4, .path = "safd"}, // source -> gateway -> book -> destination + {.cost = 4, .path = "sfad"}, + {.cost = 5, .path = "saad"}, + {.cost = 5, .path = "sbfd"}, + {.cost = 6, .path = "sxfad"}, + {.cost = 6, .path = "safad"}, + {.cost = 6, .path = "saxfd"}, // source -> gateway -> book to XRP -> book -> + // destination + {.cost = 6, .path = "saxfad"}, + {.cost = 6, .path = "sabfd"}, // source -> gateway -> book -> book -> destination + {.cost = 7, .path = "saaad"}, }); // non-XRP to non-XRP (different currency) fillPaths( PaymentType::NonXrpToNonXrp, { - {1, "sfad"}, - {1, "safd"}, - {3, "safad"}, - {4, "sxfd"}, - {5, "saxfd"}, - {5, "sxfad"}, - {5, "sbfd"}, - {6, "saxfad"}, - {6, "sabfd"}, - {7, "saafd"}, - {8, "saafad"}, - {9, "safaad"}, + {.cost = 1, .path = "sfad"}, + {.cost = 1, .path = "safd"}, + {.cost = 3, .path = "safad"}, + {.cost = 4, .path = "sxfd"}, + {.cost = 5, .path = "saxfd"}, + {.cost = 5, .path = "sxfad"}, + {.cost = 5, .path = "sbfd"}, + {.cost = 6, .path = "saxfad"}, + {.cost = 6, .path = "sabfd"}, + {.cost = 7, .path = "saafd"}, + {.cost = 8, .path = "saafad"}, + {.cost = 9, .path = "safaad"}, }); /* cspell: enable */ } diff --git a/src/xrpld/rpc/detail/ServerHandler.cpp b/src/xrpld/rpc/detail/ServerHandler.cpp index 0bdece3ec3..c73e474e18 100644 --- a/src/xrpld/rpc/detail/ServerHandler.cpp +++ b/src/xrpld/rpc/detail/ServerHandler.cpp @@ -165,10 +165,10 @@ ServerHandler::setup(Setup const& setup, beast::Journal journal) port.port = endpointPort; if ((setup_.client.port == 0u) && - (port.protocol.count("http") > 0 || port.protocol.count("https") > 0)) + (port.protocol.contains("http") || port.protocol.contains("https"))) setup_.client.port = endpointPort; - if ((setup_.overlay.port() == 0u) && (port.protocol.count("peer") > 0)) + if ((setup_.overlay.port() == 0u) && (port.protocol.contains("peer"))) setup_.overlay.port(endpointPort); } } @@ -217,7 +217,7 @@ ServerHandler::onHandoff( using namespace boost::beast; auto const& p{session.port().protocol}; bool const isWs{ - p.count("ws") > 0 || p.count("ws2") > 0 || p.count("wss") > 0 || p.count("wss2") > 0}; + p.contains("ws") || p.contains("ws2") || p.contains("wss") || p.contains("wss2")}; if (websocket::is_upgrade(request)) { @@ -251,7 +251,7 @@ ServerHandler::onHandoff( return handoff; } - if (bundle && p.count("peer") > 0) + if (bundle && p.contains("peer")) return app_.getOverlay().onHandoff(std::move(bundle), std::move(request), remoteAddress); if (isWs && isStatusRequest(request)) @@ -301,7 +301,7 @@ void ServerHandler::onRequest(Session& session) { // Make sure RPC is enabled on the port - if (session.port().protocol.count("http") == 0 && session.port().protocol.count("https") == 0) + if (!session.port().protocol.contains("http") && !session.port().protocol.contains("https")) { httpReply(403, "Forbidden", makeOutput(session), app_.getJournal("RPC")); session.close(true); @@ -1180,7 +1180,7 @@ parsePorts(Config const& config, std::ostream& log) else { auto const count = std::count_if(result.cbegin(), result.cend(), [](Port const& p) { - return p.protocol.count("peer") != 0; + return p.protocol.contains("peer"); }); if (count > 1) @@ -1203,12 +1203,12 @@ setupClient(ServerHandler::Setup& setup) decltype(setup.ports)::const_iterator iter; for (iter = setup.ports.cbegin(); iter != setup.ports.cend(); ++iter) { - if (iter->protocol.count("http") > 0 || iter->protocol.count("https") > 0) + if (iter->protocol.contains("http") || iter->protocol.contains("https")) break; } if (iter == setup.ports.cend()) return; - setup.client.secure = iter->protocol.count("https") > 0; + setup.client.secure = iter->protocol.contains("https"); if (beast::IP::isUnspecified(iter->ip)) { // VFALCO HACK! to make localhost work @@ -1230,7 +1230,7 @@ static void setupOverlay(ServerHandler::Setup& setup) { auto const iter = std::ranges::find_if( - setup.ports, [](Port const& port) { return port.protocol.count("peer") != 0; }); + setup.ports, [](Port const& port) { return port.protocol.contains("peer"); }); if (iter == setup.ports.cend()) { setup.overlay = {}; From 6571f75d399e3e237fbcb1ddfe23adbe3d98286c Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 5 Jun 2026 15:36:05 +0100 Subject: [PATCH 064/135] ci: Use multiple directories in dependabot config (#7413) --- .github/dependabot.yml | 40 ++++++---------------------------------- 1 file changed, 6 insertions(+), 34 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0e6b840fe7..da7a30dc77 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,40 +1,12 @@ version: 2 updates: - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - day: monday - time: "04:00" - timezone: Etc/GMT - commit-message: - prefix: "ci: [DEPENDABOT] " - target-branch: develop - - - package-ecosystem: github-actions - directory: .github/actions/build-deps/ - schedule: - interval: weekly - day: monday - time: "04:00" - timezone: Etc/GMT - commit-message: - prefix: "ci: [DEPENDABOT] " - target-branch: develop - - - package-ecosystem: github-actions - directory: .github/actions/generate-version/ - schedule: - interval: weekly - day: monday - time: "04:00" - timezone: Etc/GMT - commit-message: - prefix: "ci: [DEPENDABOT] " - target-branch: develop - - - package-ecosystem: github-actions - directory: .github/actions/setup-conan/ + directories: + - / + - .github/actions/build-deps/ + - .github/actions/generate-version/ + - .github/actions/set-compiler-env/ + - .github/actions/setup-conan/ schedule: interval: weekly day: monday From 63ffdc39dc6bc1565b7e6437ed6686721c9379c2 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 5 Jun 2026 18:05:19 +0100 Subject: [PATCH 065/135] ci: Refactor build-related nix / docker / workflows (#7408) --- .github/scripts/strategy-matrix/linux.json | 2 +- .github/workflows/build-nix-image.yml | 109 ------------------ .github/workflows/build-nix-images.yml | 56 +++++++++ .github/workflows/build-packaging-images.yml | 48 ++++++++ .github/workflows/publish-docs.yml | 18 +-- .../workflows/reusable-build-docker-image.yml | 2 +- .../reusable-build-merge-docker-images.yml | 89 ++++++++++++++ .github/workflows/reusable-upload-recipe.yml | 2 +- .../nix.Dockerfile => nix/docker/Dockerfile | 12 +- {docker => nix/docker}/check-tools.sh | 0 .../docker}/install-sanitizer-libs.sh | 0 {docker => nix/docker}/loader-path.sh | 0 .../docker}/test_files/compile-cpp-sources.sh | 0 .../docker}/test_files/cpp_sources/asan.cpp | 0 .../test_files/cpp_sources/regular.cpp | 0 .../docker}/test_files/cpp_sources/tsan.cpp | 0 .../docker}/test_files/cpp_sources/ubsan.cpp | 0 .../docker}/test_files/run-test-binaries.sh | 0 package/Dockerfile | 14 +++ package/README.md | 13 +-- package/install-packaging-tools.sh | 68 +++++++++++ 21 files changed, 295 insertions(+), 138 deletions(-) delete mode 100644 .github/workflows/build-nix-image.yml create mode 100644 .github/workflows/build-nix-images.yml create mode 100644 .github/workflows/build-packaging-images.yml create mode 100644 .github/workflows/reusable-build-merge-docker-images.yml rename docker/nix.Dockerfile => nix/docker/Dockerfile (90%) rename {docker => nix/docker}/check-tools.sh (100%) rename {docker => nix/docker}/install-sanitizer-libs.sh (100%) rename {docker => nix/docker}/loader-path.sh (100%) rename {docker => nix/docker}/test_files/compile-cpp-sources.sh (100%) rename {docker => nix/docker}/test_files/cpp_sources/asan.cpp (100%) rename {docker => nix/docker}/test_files/cpp_sources/regular.cpp (100%) rename {docker => nix/docker}/test_files/cpp_sources/tsan.cpp (100%) rename {docker => nix/docker}/test_files/cpp_sources/ubsan.cpp (100%) rename {docker => nix/docker}/test_files/run-test-binaries.sh (100%) create mode 100644 package/Dockerfile create mode 100755 package/install-packaging-tools.sh diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index 3070b8d9f4..7da48a6a25 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -1,5 +1,5 @@ { - "image_tag": "sha-6c54342", + "image_tag": "sha-8abe82e", "configs": { "ubuntu": [ { diff --git a/.github/workflows/build-nix-image.yml b/.github/workflows/build-nix-image.yml deleted file mode 100644 index bae4cfd437..0000000000 --- a/.github/workflows/build-nix-image.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: Build Nix Docker image - -on: - push: - branches: - - develop - paths: - - ".github/workflows/build-nix-image.yml" - - ".github/workflows/reusable-build-docker-image.yml" - - "docker/**" - - "flake.nix" - - "flake.lock" - - "nix/**" - pull_request: - paths: - - ".github/workflows/build-nix-image.yml" - - ".github/workflows/reusable-build-docker-image.yml" - - "docker/**" - - "flake.nix" - - "flake.lock" - - "nix/**" - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -defaults: - run: - shell: bash - -jobs: - build: - name: Build ${{ matrix.distro.name }} (${{ matrix.target.platform }}) - permissions: - contents: read - packages: write - strategy: - fail-fast: false - matrix: - # The base images are the oldest supported version of each distro - # that we want to build images for. - distro: - - name: nixos - base_image: nixos/nix:latest - - name: ubuntu - base_image: ubuntu:20.04 - - name: rhel - base_image: registry.access.redhat.com/ubi9/ubi:latest - - name: debian - base_image: debian:bookworm - target: - - platform: linux/amd64 - runner: ubuntu-latest - - platform: linux/arm64 - runner: ubuntu-24.04-arm - uses: ./.github/workflows/reusable-build-docker-image.yml - with: - image_name: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro.name }} - dockerfile: docker/nix.Dockerfile - base_image: ${{ matrix.distro.base_image }} - platform: ${{ matrix.target.platform }} - runner: ${{ matrix.target.runner }} - push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} - - merge: - name: Merge ${{ matrix.distro }} manifest - needs: build - if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - strategy: - fail-fast: false - matrix: - distro: [nixos, ubuntu, rhel, debian] - env: - IMAGE_NAME: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro }} - - steps: - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - - - name: Docker metadata - id: meta - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 - with: - images: ${{ env.IMAGE_NAME }} - tags: | - type=sha,prefix=sha-,format=short - type=raw,value=latest - - - name: Login to GitHub Container Registry - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Create multi-arch manifests - run: | - for tag in $(jq -cr '.tags[]' <<<"$DOCKER_METADATA_OUTPUT_JSON"); do - docker buildx imagetools create -t "$tag" "${tag}-amd64" "${tag}-arm64" - done - - - name: Inspect image - run: | - docker buildx imagetools inspect "${IMAGE_NAME}:${{ steps.meta.outputs.version }}" diff --git a/.github/workflows/build-nix-images.yml b/.github/workflows/build-nix-images.yml new file mode 100644 index 0000000000..dc02f84e0f --- /dev/null +++ b/.github/workflows/build-nix-images.yml @@ -0,0 +1,56 @@ +name: Build Nix Docker images + +on: + push: + branches: + - develop + paths: + - ".github/workflows/build-nix-images.yml" + - ".github/workflows/reusable-build-docker-image.yml" + - ".github/workflows/reusable-build-merge-docker-images.yml" + - "flake.nix" + - "flake.lock" + - "nix/**" + pull_request: + paths: + - ".github/workflows/build-nix-images.yml" + - ".github/workflows/reusable-build-docker-image.yml" + - ".github/workflows/reusable-build-merge-docker-images.yml" + - "flake.nix" + - "flake.lock" + - "nix/**" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + build-merge: + name: Build and push nix-${{ matrix.distro.name }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + # The base images are the oldest supported version of each distro + # that we want to build images for. + distro: + - name: nixos + base_image: nixos/nix:latest + - name: ubuntu + base_image: ubuntu:20.04 + - name: debian + base_image: debian:bookworm + - name: rhel + base_image: registry.access.redhat.com/ubi9/ubi:latest + uses: ./.github/workflows/reusable-build-merge-docker-images.yml + with: + image_name: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro.name }} + dockerfile: nix/docker/Dockerfile + base_image: ${{ matrix.distro.base_image }} diff --git a/.github/workflows/build-packaging-images.yml b/.github/workflows/build-packaging-images.yml new file mode 100644 index 0000000000..a11a16f298 --- /dev/null +++ b/.github/workflows/build-packaging-images.yml @@ -0,0 +1,48 @@ +name: Build packaging Docker images + +on: + push: + branches: + - develop + paths: + - ".github/workflows/build-packaging-images.yml" + - ".github/workflows/reusable-build-docker-image.yml" + - ".github/workflows/reusable-build-merge-docker-images.yml" + - "package/Dockerfile" + - "package/install-packaging-tools.sh" + pull_request: + paths: + - ".github/workflows/build-packaging-images.yml" + - ".github/workflows/reusable-build-docker-image.yml" + - ".github/workflows/reusable-build-merge-docker-images.yml" + - "package/Dockerfile" + - "package/install-packaging-tools.sh" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + build-merge: + name: Build and push packaging-${{ matrix.distro.name }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + distro: + - name: debian + base_image: debian:bookworm + - name: rhel + base_image: registry.access.redhat.com/ubi9/ubi:latest + uses: ./.github/workflows/reusable-build-merge-docker-images.yml + with: + image_name: ghcr.io/xrplf/xrpld/packaging-${{ matrix.distro.name }} + dockerfile: package/Dockerfile + base_image: ${{ matrix.distro.base_image }} diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index d619be5543..bbe6ec4592 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -41,7 +41,7 @@ env: jobs: build: runs-on: ubuntu-latest - container: ghcr.io/xrplf/ci/tools-rippled-documentation:sha-a8c7be1 + container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-8abe82e steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -57,19 +57,11 @@ jobs: with: subtract: ${{ env.NPROC_SUBTRACT }} - - name: Check configuration - run: | - echo 'Checking path.' - echo ${PATH} | tr ':' '\n' + - name: Print build environment + uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574 - echo 'Checking environment variables.' - env | sort - - echo 'Checking CMake version.' - cmake --version - - echo 'Checking Doxygen version.' - doxygen --version + - name: Check Doxygen version + run: doxygen --version - name: Build documentation env: diff --git a/.github/workflows/reusable-build-docker-image.yml b/.github/workflows/reusable-build-docker-image.yml index c3795e56fa..5830ef9ece 100644 --- a/.github/workflows/reusable-build-docker-image.yml +++ b/.github/workflows/reusable-build-docker-image.yml @@ -38,7 +38,7 @@ defaults: jobs: build: - name: Build (${{ inputs.platform }}) + name: Build ${{ inputs.platform }} runs-on: ${{ inputs.runner }} permissions: contents: read diff --git a/.github/workflows/reusable-build-merge-docker-images.yml b/.github/workflows/reusable-build-merge-docker-images.yml new file mode 100644 index 0000000000..98deb6ea3f --- /dev/null +++ b/.github/workflows/reusable-build-merge-docker-images.yml @@ -0,0 +1,89 @@ +name: Reusable build and merge Docker image (multi-arch) + +on: + workflow_call: + inputs: + image_name: + description: "Full image name without tag (e.g. 'ghcr.io/xrplf/xrpld/nix-ubuntu')" + required: true + type: string + dockerfile: + description: "Path to the Dockerfile, relative to the repository root" + required: true + type: string + base_image: + description: "Value passed to the Dockerfile as the BASE_IMAGE build arg" + required: true + type: string + +defaults: + run: + shell: bash + +jobs: + build: + name: Build ${{ inputs.image_name }} + permissions: + contents: read + packages: write + + strategy: + fail-fast: false + matrix: + target: + - platform: linux/amd64 + runner: ubuntu-latest + - platform: linux/arm64 + runner: ubuntu-24.04-arm + + uses: ./.github/workflows/reusable-build-docker-image.yml + with: + image_name: ${{ inputs.image_name }} + dockerfile: ${{ inputs.dockerfile }} + base_image: ${{ inputs.base_image }} + platform: ${{ matrix.target.platform }} + runner: ${{ matrix.target.runner }} + push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} + + merge: + name: Merge ${{ inputs.image_name }} + needs: build + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + + - name: Docker metadata + id: meta + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 + with: + images: ${{ inputs.image_name }} + tags: | + type=sha,prefix=sha-,format=short + type=raw,value=latest + + - name: Login to GitHub Container Registry + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Create multi-arch manifests + if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} + run: | + for tag in $(jq -cr '.tags[]' <<<"$DOCKER_METADATA_OUTPUT_JSON"); do + docker buildx imagetools create -t "$tag" "${tag}-amd64" "${tag}-arm64" + done + + - name: Inspect image + if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} + env: + IMAGE_NAME: ${{ inputs.image_name }} + IMAGE_VERSION: ${{ steps.meta.outputs.version }} + run: | + docker buildx imagetools inspect "${IMAGE_NAME}:${IMAGE_VERSION}" diff --git a/.github/workflows/reusable-upload-recipe.yml b/.github/workflows/reusable-upload-recipe.yml index d3fe0f356b..b7ec5f9ef7 100644 --- a/.github/workflows/reusable-upload-recipe.yml +++ b/.github/workflows/reusable-upload-recipe.yml @@ -40,7 +40,7 @@ defaults: jobs: upload: runs-on: ubuntu-latest - container: ghcr.io/xrplf/ci/ubuntu-noble:gcc-13-sha-5dd7158 + container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-8abe82e steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/docker/nix.Dockerfile b/nix/docker/Dockerfile similarity index 90% rename from docker/nix.Dockerfile rename to nix/docker/Dockerfile index 6248708417..e6df48e18c 100644 --- a/docker/nix.Dockerfile +++ b/nix/docker/Dockerfile @@ -56,7 +56,7 @@ ENV GIT_SSL_CAINFO="/nix/ci-env/etc/ssl/certs/ca-bundle.crt" # Externally-built dynamically-linked ELF binaries hard-code the loader path # (e.g. /lib64/ld-linux-x86-64.so.2) in their PT_INTERP header. Install it # from the Nix store when the base image doesn't already provide one. -COPY docker/loader-path.sh /tmp/loader-path.sh +COPY nix/docker/loader-path.sh /tmp/loader-path.sh RUN < deb, `dnf`/`yum` -> rpm). The image tag is composed as -`ghcr.io/xrplf/ci/{distro}-{version}:{compiler}-{cver}-sha-{image_sha}` — +`ghcr.io/xrplf/xrpld/packaging-:sha-` — the same scheme used by `reusable-build-test.yml`. Bump `image_sha` in `linux.json` and both CI and local builds pick up the new image with no workflow edits. | Package type | Image (derived from `linux.json`) | Tool required | | ------------ | ---------------------------------------------------- | --------------------------------------------------------------- | -| RPM | `ghcr.io/xrplf/ci/rhel-9:gcc-12-sha-` | `rpmbuild` | -| DEB | `ghcr.io/xrplf/ci/ubuntu-jammy:gcc-12-sha-` | `dpkg-buildpackage`, `debhelper (>= 13)`, `dh-sequence-systemd` | +| RPM | `ghcr.io/xrplf/xrpld/packaging-rhel:sha-` | `rpmbuild` | +| DEB | `ghcr.io/xrplf/xrpld/packaging-debian:sha-` | `dpkg-buildpackage`, `debhelper (>= 13)`, `dh-sequence-systemd` | To print the exact image tags for the current `linux.json`: ```bash -./.github/scripts/strategy-matrix/generate.py --packaging --config=.github/scripts/strategy-matrix/linux.json +./.github/scripts/strategy-matrix/generate.py --packaging ``` ## Building packages diff --git a/package/install-packaging-tools.sh b/package/install-packaging-tools.sh new file mode 100755 index 0000000000..a26159a204 --- /dev/null +++ b/package/install-packaging-tools.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +set -euo pipefail + +if [ ! -f /etc/os-release ]; then + echo "ERROR: /etc/os-release not found; cannot detect OS" >&2 + exit 1 +fi + +# shellcheck source=/dev/null +. /etc/os-release + +echo "Detected OS: ${ID} ${VERSION_ID:-}" + +case "${ID}" in + ubuntu | debian | rhel | centos | rocky | almalinux) + echo "Supported OS detected: ${ID}" + ;; + *) + echo "ERROR: unsupported OS '${ID}'. Supported: debian, ubuntu, rhel-family" >&2 + exit 1 + ;; +esac + +function install() { + case "${ID}" in + debian | ubuntu) + apt-get update -y + apt-get install -y --no-install-recommends \ + ca-certificates \ + debhelper \ + debhelper-compat \ + dpkg-dev \ + git + ;; + + rhel | centos | rocky | almalinux) + dnf install -y --setopt=install_weak_deps=False \ + git \ + rpm-build \ + redhat-rpm-config \ + systemd-rpm-macros + ;; + esac +} + +function postinstall() { + # Don't clear cache in non-CI environments + if [ -z "${CI:-}" ]; then + echo "Not running in CI environment; skipping cache cleanup" + return + fi + + case "${ID}" in + debian | ubuntu) + apt-get clean + rm -rf /var/lib/apt/lists/* + ;; + + rhel | centos | rocky | almalinux) + dnf clean -y all + rm -rf /var/cache/dnf/* + ;; + esac +} + +install +postinstall From fc57dab78bd7dc4a9a022c16fd557b18f7a5e571 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 17:17:47 +0000 Subject: [PATCH 066/135] ci: [DEPENDABOT] bump actions/checkout from 6.0.2 to 6.0.3 (#7414) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-pr-description.yml | 2 +- .github/workflows/on-pr.yml | 2 +- .github/workflows/publish-docs.yml | 2 +- .github/workflows/reusable-build-docker-image.yml | 2 +- .github/workflows/reusable-build-test-config.yml | 2 +- .github/workflows/reusable-check-levelization.yml | 2 +- .github/workflows/reusable-check-rename.yml | 2 +- .github/workflows/reusable-clang-tidy.yml | 2 +- .github/workflows/reusable-package.yml | 6 +++--- .github/workflows/reusable-strategy-matrix.yml | 2 +- .github/workflows/reusable-upload-recipe.yml | 2 +- .github/workflows/upload-conan-deps.yml | 2 +- 12 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/check-pr-description.yml b/.github/workflows/check-pr-description.yml index ff28220171..a60b83738a 100644 --- a/.github/workflows/check-pr-description.yml +++ b/.github/workflows/check-pr-description.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Write PR body to file env: diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index db3c8667e5..4b2edeb93d 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Determine changed files # This step checks whether any files have changed that should # cause the next jobs to run. We do it this way rather than diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index bbe6ec4592..35f33b6446 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -44,7 +44,7 @@ jobs: container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-8abe82e steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Prepare runner uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab diff --git a/.github/workflows/reusable-build-docker-image.yml b/.github/workflows/reusable-build-docker-image.yml index 5830ef9ece..253563c6a5 100644 --- a/.github/workflows/reusable-build-docker-image.yml +++ b/.github/workflows/reusable-build-docker-image.yml @@ -46,7 +46,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Determine arch id: vars diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index e1154f74be..6f0750e3f7 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -110,7 +110,7 @@ jobs: uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4 - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Prepare runner uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab diff --git a/.github/workflows/reusable-check-levelization.yml b/.github/workflows/reusable-check-levelization.yml index b5d57a177a..813c0e1e36 100644 --- a/.github/workflows/reusable-check-levelization.yml +++ b/.github/workflows/reusable-check-levelization.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Check levelization run: python .github/scripts/levelization/generate.py - name: Check for differences diff --git a/.github/workflows/reusable-check-rename.yml b/.github/workflows/reusable-check-rename.yml index 7aa5b80594..5002cc7f40 100644 --- a/.github/workflows/reusable-check-rename.yml +++ b/.github/workflows/reusable-check-rename.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Check definitions run: .github/scripts/rename/definitions.sh . - name: Check copyright notices diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index cfbd8af963..31e06d05eb 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -42,7 +42,7 @@ jobs: issues: write steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Prepare runner uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab diff --git a/.github/workflows/reusable-package.yml b/.github/workflows/reusable-package.yml index 670c01733e..890277d184 100644 --- a/.github/workflows/reusable-package.yml +++ b/.github/workflows/reusable-package.yml @@ -27,7 +27,7 @@ jobs: matrix: ${{ steps.generate.outputs.matrix }} steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 @@ -45,7 +45,7 @@ jobs: version: ${{ steps.version.outputs.version }} steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: sparse-checkout: | .github/actions/generate-version @@ -94,7 +94,7 @@ jobs: systemd-rpm-macros - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Download pre-built binary uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 diff --git a/.github/workflows/reusable-strategy-matrix.yml b/.github/workflows/reusable-strategy-matrix.yml index 16a2b4e336..ea134b43b2 100644 --- a/.github/workflows/reusable-strategy-matrix.yml +++ b/.github/workflows/reusable-strategy-matrix.yml @@ -23,7 +23,7 @@ jobs: matrix: ${{ steps.generate.outputs.matrix }} steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/reusable-upload-recipe.yml b/.github/workflows/reusable-upload-recipe.yml index b7ec5f9ef7..6e1ea943ca 100644 --- a/.github/workflows/reusable-upload-recipe.yml +++ b/.github/workflows/reusable-upload-recipe.yml @@ -43,7 +43,7 @@ jobs: container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-8abe82e steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Generate build version number id: version diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 87465b4d3d..6310c90899 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -64,7 +64,7 @@ jobs: uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4 - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Prepare runner uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab From 949887feb9f32b49829e9c29712697f567b23916 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 5 Jun 2026 20:24:32 +0100 Subject: [PATCH 067/135] build: Create single test binary xrpl_tests (#7327) --- .gersemi/definitions.cmake | 3 - .github/scripts/rename/cmake.sh | 4 - .../workflows/reusable-build-test-config.yml | 21 ++--- cmake/XrplAddTest.cmake | 22 ----- cmake/XrplCov.cmake | 2 +- src/tests/libxrpl/CMakeLists.txt | 89 ++++++++++--------- src/tests/libxrpl/crypto/main.cpp | 8 -- src/tests/libxrpl/json/main.cpp | 8 -- src/tests/libxrpl/{basics => }/main.cpp | 0 src/tests/libxrpl/net/main.cpp | 8 -- src/tests/libxrpl/tx/main.cpp | 8 -- 11 files changed, 59 insertions(+), 114 deletions(-) delete mode 100644 cmake/XrplAddTest.cmake delete mode 100644 src/tests/libxrpl/crypto/main.cpp delete mode 100644 src/tests/libxrpl/json/main.cpp rename src/tests/libxrpl/{basics => }/main.cpp (100%) delete mode 100644 src/tests/libxrpl/net/main.cpp delete mode 100644 src/tests/libxrpl/tx/main.cpp diff --git a/.gersemi/definitions.cmake b/.gersemi/definitions.cmake index a16e330ffa..245f827f90 100644 --- a/.gersemi/definitions.cmake +++ b/.gersemi/definitions.cmake @@ -11,9 +11,6 @@ endfunction() function(create_symbolic_link target link) endfunction() -function(xrpl_add_test name) -endfunction() - macro(exclude_from_default target_) endmacro() diff --git a/.github/scripts/rename/cmake.sh b/.github/scripts/rename/cmake.sh index 28bf777fed..3539f563e0 100755 --- a/.github/scripts/rename/cmake.sh +++ b/.github/scripts/rename/cmake.sh @@ -43,9 +43,6 @@ pushd "${DIRECTORY}" # Rename the files. find cmake -type f -name 'Rippled*.cmake' -exec bash -c 'mv "${1}" "${1/Rippled/Xrpl}"' - {} \; find cmake -type f -name 'Ripple*.cmake' -exec bash -c 'mv "${1}" "${1/Ripple/Xrpl}"' - {} \; -if [ -e cmake/xrpl_add_test.cmake ]; then - mv cmake/xrpl_add_test.cmake cmake/XrplAddTest.cmake -fi if [ -e include/xrpl/proto/ripple.proto ]; then mv include/xrpl/proto/ripple.proto include/xrpl/proto/xrpl.proto fi @@ -60,7 +57,6 @@ find cmake -type f -name '*.cmake' | while read -r FILE; do done ${SED_COMMAND} -i -E 's/Rippled?/Xrpl/g' CMakeLists.txt ${SED_COMMAND} -i 's/ripple/xrpl/g' CMakeLists.txt -${SED_COMMAND} -i 's/include(xrpl_add_test)/include(XrplAddTest)/' src/tests/libxrpl/CMakeLists.txt ${SED_COMMAND} -i 's/ripple.pb.h/xrpl.pb.h/' include/xrpl/protocol/messages.h ${SED_COMMAND} -i 's/ripple.pb.h/xrpl.pb.h/' BUILD.md ${SED_COMMAND} -i 's/ripple.pb.h/xrpl.pb.h/' BUILD.md diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 6f0750e3f7..c215540b2e 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -236,6 +236,15 @@ jobs: retention-days: 3 if-no-files-found: error + - name: Upload the test binary (Linux) + if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: xrpl_tests-${{ inputs.config_name }} + path: ${{ env.BUILD_DIR }}/xrpl_tests + retention-days: 3 + if-no-files-found: error + - name: Export server definitions if: ${{ runner.os != 'Windows' && !inputs.build_only && env.VOIDSTAR_ENABLED != 'true' }} working-directory: ${{ env.BUILD_DIR }} @@ -286,16 +295,8 @@ jobs: - name: Run the separate tests if: ${{ !inputs.build_only }} - working-directory: ${{ env.BUILD_DIR }} - # Windows locks some of the build files while running tests, and parallel jobs can collide - env: - BUILD_TYPE: ${{ inputs.build_type }} - PARALLELISM: ${{ runner.os == 'Windows' && '1' || steps.nproc.outputs.nproc }} - run: | - ctest \ - --output-on-failure \ - -C "${BUILD_TYPE}" \ - -j "${PARALLELISM}" + working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }} + run: ./xrpl_tests - name: Run the embedded tests if: ${{ !inputs.build_only }} diff --git a/cmake/XrplAddTest.cmake b/cmake/XrplAddTest.cmake deleted file mode 100644 index 2f1209e03c..0000000000 --- a/cmake/XrplAddTest.cmake +++ /dev/null @@ -1,22 +0,0 @@ -include(isolate_headers) - -function(xrpl_add_test name) - set(target ${PROJECT_NAME}.test.${name}) - - file( - GLOB_RECURSE sources - CONFIGURE_DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/${name}.cpp" - ) - add_executable(${target} ${ARGN} ${sources}) - - isolate_headers( - ${target} - "${CMAKE_SOURCE_DIR}" - "${CMAKE_SOURCE_DIR}/tests/${name}" - PRIVATE - ) - - add_test(NAME ${target} COMMAND ${target}) -endfunction() diff --git a/cmake/XrplCov.cmake b/cmake/XrplCov.cmake index d81d7e689f..86ba534a88 100644 --- a/cmake/XrplCov.cmake +++ b/cmake/XrplCov.cmake @@ -47,7 +47,7 @@ setup_target_for_coverage_gcovr( "include/xrpl/beast/test" "include/xrpl/beast/unit_test" "${CMAKE_BINARY_DIR}/pb-xrpl.libpb" - DEPENDENCIES xrpld xrpl.tests + DEPENDENCIES xrpld xrpl_tests ) add_code_coverage_to_target(opts INTERFACE) diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index ee07698519..60288e5f20 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -1,51 +1,56 @@ -include(XrplAddTest) +include(GoogleTest) +include(isolate_headers) # Test requirements. find_package(GTest REQUIRED) -# Custom target for all tests defined in this file -add_custom_target(xrpl.tests) - -# Test helpers -add_library(xrpl.helpers.test STATIC) -target_sources( - xrpl.helpers.test - PRIVATE helpers/Account.cpp helpers/TestSink.cpp helpers/TxTest.cpp +# Single combined gtest binary built from the shared test helpers and all test +# modules below. +add_executable( + xrpl_tests + main.cpp + helpers/Account.cpp + helpers/TestSink.cpp + helpers/TxTest.cpp ) -target_include_directories(xrpl.helpers.test PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(xrpl.helpers.test PUBLIC xrpl.libxrpl gtest::gtest) - -# Common library dependencies for the rest of the tests. -add_library(xrpl.imports.test INTERFACE) -target_link_libraries( - xrpl.imports.test - INTERFACE gtest::gtest xrpl.libxrpl xrpl.helpers.test +set_target_properties( + xrpl_tests + PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" ) +# Lets test sources include the shared helpers as . +target_include_directories(xrpl_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(xrpl_tests PRIVATE GTest::gtest xrpl.libxrpl) -# One test for each module. -xrpl_add_test(basics) -target_link_libraries(xrpl.test.basics PRIVATE xrpl.imports.test) -add_dependencies(xrpl.tests xrpl.test.basics) - -xrpl_add_test(crypto) -target_link_libraries(xrpl.test.crypto PRIVATE xrpl.imports.test) -add_dependencies(xrpl.tests xrpl.test.crypto) - -xrpl_add_test(json) -target_link_libraries(xrpl.test.json PRIVATE xrpl.imports.test) -add_dependencies(xrpl.tests xrpl.test.json) - -xrpl_add_test(tx) -target_link_libraries(xrpl.test.tx PRIVATE xrpl.imports.test) -add_dependencies(xrpl.tests xrpl.test.tx) - -xrpl_add_test(protocol_autogen) -target_link_libraries(xrpl.test.protocol_autogen PRIVATE xrpl.imports.test) -add_dependencies(xrpl.tests xrpl.test.protocol_autogen) - -# Network unit tests are currently not supported on Windows +# One source subdirectory per module. Network unit tests are currently not +# supported on Windows. +set(test_modules + basics + crypto + json + tx + protocol_autogen +) if(NOT WIN32) - xrpl_add_test(net) - target_link_libraries(xrpl.test.net PRIVATE xrpl.imports.test) - add_dependencies(xrpl.tests xrpl.test.net) + list(APPEND test_modules net) endif() + +foreach(module IN LISTS test_modules) + # Append the module's sources (${module}/*.cpp and ${module}.cpp, if any). + file( + GLOB_RECURSE sources + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/${module}/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/${module}.cpp" + ) + target_sources(xrpl_tests PRIVATE ${sources}) + + # Expose the module's private headers under their canonical include path. + isolate_headers( + xrpl_tests + "${CMAKE_SOURCE_DIR}" + "${CMAKE_SOURCE_DIR}/tests/${module}" + PRIVATE + ) +endforeach() + +gtest_discover_tests(xrpl_tests) diff --git a/src/tests/libxrpl/crypto/main.cpp b/src/tests/libxrpl/crypto/main.cpp deleted file mode 100644 index 5142bbe08a..0000000000 --- a/src/tests/libxrpl/crypto/main.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include - -int -main(int argc, char** argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/src/tests/libxrpl/json/main.cpp b/src/tests/libxrpl/json/main.cpp deleted file mode 100644 index 5142bbe08a..0000000000 --- a/src/tests/libxrpl/json/main.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include - -int -main(int argc, char** argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/src/tests/libxrpl/basics/main.cpp b/src/tests/libxrpl/main.cpp similarity index 100% rename from src/tests/libxrpl/basics/main.cpp rename to src/tests/libxrpl/main.cpp diff --git a/src/tests/libxrpl/net/main.cpp b/src/tests/libxrpl/net/main.cpp deleted file mode 100644 index 5142bbe08a..0000000000 --- a/src/tests/libxrpl/net/main.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include - -int -main(int argc, char** argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/src/tests/libxrpl/tx/main.cpp b/src/tests/libxrpl/tx/main.cpp deleted file mode 100644 index 5142bbe08a..0000000000 --- a/src/tests/libxrpl/tx/main.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include - -int -main(int argc, char** argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} From 79f4ddc4a684d84701a1d97304c18285a7640cf8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 05:37:50 -0400 Subject: [PATCH 068/135] ci: [DEPENDABOT] bump codecov/codecov-action from 6.0.1 to 7.0.0 (#7426) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/reusable-build-test-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index c215540b2e..dc3336dd2a 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -370,7 +370,7 @@ jobs: - name: Upload coverage report if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }} - uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: disable_search: true disable_telem: true From a389f922ddfacb5e28be461252ad1a57f5c4a265 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 8 Jun 2026 14:41:08 +0100 Subject: [PATCH 069/135] ci: Use new packaging images and don't cancel develop builds (#7417) Co-authored-by: Bart --- .github/scripts/strategy-matrix/linux.json | 6 ++--- .github/workflows/build-nix-images.yml | 3 ++- .github/workflows/build-packaging-images.yml | 3 ++- .github/workflows/pre-commit.yml | 2 +- .github/workflows/publish-docs.yml | 4 +-- .../workflows/reusable-build-test-config.yml | 2 +- .github/workflows/reusable-clang-tidy.yml | 6 ++--- .github/workflows/reusable-package.yml | 25 ------------------- .github/workflows/reusable-upload-recipe.yml | 2 +- .github/workflows/upload-conan-deps.yml | 2 +- src/tests/libxrpl/CMakeLists.txt | 2 +- 11 files changed, 17 insertions(+), 40 deletions(-) diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index 7da48a6a25..edacdbde4c 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -1,5 +1,5 @@ { - "image_tag": "sha-8abe82e", + "image_tag": "sha-63ffdc3", "configs": { "ubuntu": [ { @@ -67,7 +67,7 @@ "compiler": ["gcc"], "build_type": ["Release"], "arch": ["amd64"], - "image": "debian:bookworm" + "image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-63ffdc3" } ], @@ -76,7 +76,7 @@ "compiler": ["gcc"], "build_type": ["Release"], "arch": ["amd64"], - "image": "registry.access.redhat.com/ubi9/ubi:latest" + "image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-63ffdc3" } ] } diff --git a/.github/workflows/build-nix-images.yml b/.github/workflows/build-nix-images.yml index dc02f84e0f..4e38ca7c57 100644 --- a/.github/workflows/build-nix-images.yml +++ b/.github/workflows/build-nix-images.yml @@ -22,7 +22,8 @@ on: workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + # Read `on-trigger.yml` for the rationale behind this concurrency group name. + group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' && github.sha || github.ref }} cancel-in-progress: true defaults: diff --git a/.github/workflows/build-packaging-images.yml b/.github/workflows/build-packaging-images.yml index a11a16f298..c445dbf726 100644 --- a/.github/workflows/build-packaging-images.yml +++ b/.github/workflows/build-packaging-images.yml @@ -20,7 +20,8 @@ on: workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + # Read `on-trigger.yml` for the rationale behind this concurrency group name. + group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' && github.sha || github.ref }} cancel-in-progress: true defaults: diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index de6a4f40b4..aecf0c2a8b 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -14,7 +14,7 @@ on: jobs: # Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks. run-hooks: - uses: XRPLF/actions/.github/workflows/pre-commit.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81 + uses: XRPLF/actions/.github/workflows/pre-commit.yml@312aaab296060ff89d7f798dcab59f019bea6e02 with: runs_on: ubuntu-latest container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }' diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 35f33b6446..bcf5968384 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -41,13 +41,13 @@ env: jobs: build: runs-on: ubuntu-latest - container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-8abe82e + container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-63ffdc3 steps: - name: Checkout repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab + uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8 with: enable_ccache: false diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index dc3336dd2a..d53cf97a39 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -113,7 +113,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab + uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8 with: enable_ccache: ${{ inputs.ccache_enabled }} diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index 31e06d05eb..9f10711b6f 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -29,14 +29,14 @@ jobs: if: ${{ inputs.check_only_changed }} permissions: contents: read - uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@224f3c48d3014d082a1129237b8291ff0b0a331f + uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@312aaab296060ff89d7f798dcab59f019bea6e02 run-clang-tidy: name: Run clang tidy needs: [determine-files] if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }} runs-on: ["self-hosted", "Linux", "X64", "heavy"] - container: "ghcr.io/xrplf/xrpld/nix-debian:sha-8abe82e" + container: "ghcr.io/xrplf/xrpld/nix-debian:sha-63ffdc3" permissions: contents: read issues: write @@ -45,7 +45,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab + uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8 with: enable_ccache: false diff --git a/.github/workflows/reusable-package.yml b/.github/workflows/reusable-package.yml index 890277d184..0e3f657006 100644 --- a/.github/workflows/reusable-package.yml +++ b/.github/workflows/reusable-package.yml @@ -68,31 +68,6 @@ jobs: timeout-minutes: 30 steps: - # Packaging runs in a vanilla distro image, so the tooling has to come - # from the distro's archive: debhelper for deb, rpm-build (and the - # systemd / find-debuginfo macros it depends on) for rpm. Run this - # before actions/checkout so the latter can use git (real history) for - # build_pkg.sh's SOURCE_DATE_EPOCH; otherwise it falls back to a tarball - # download and the timestamp comes from wall-clock time. - - name: Install packaging tooling (deb) - if: ${{ matrix.distro == 'debian' }} - run: | - export DEBIAN_FRONTEND=noninteractive - apt-get update - apt-get install -y --no-install-recommends \ - ca-certificates \ - debhelper \ - git - - - name: Install packaging tooling (rpm) - if: ${{ matrix.distro == 'rhel' }} - run: | - dnf install -y --setopt=install_weak_deps=False \ - git \ - rpm-build \ - redhat-rpm-config \ - systemd-rpm-macros - - name: Checkout repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 diff --git a/.github/workflows/reusable-upload-recipe.yml b/.github/workflows/reusable-upload-recipe.yml index 6e1ea943ca..1c90fb0e72 100644 --- a/.github/workflows/reusable-upload-recipe.yml +++ b/.github/workflows/reusable-upload-recipe.yml @@ -40,7 +40,7 @@ defaults: jobs: upload: runs-on: ubuntu-latest - container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-8abe82e + container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-63ffdc3 steps: - name: Checkout repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 6310c90899..1a52ceee63 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -67,7 +67,7 @@ jobs: uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab + uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8 with: enable_ccache: false diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index 60288e5f20..2dae6fccb9 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -53,4 +53,4 @@ foreach(module IN LISTS test_modules) ) endforeach() -gtest_discover_tests(xrpl_tests) +gtest_discover_tests(xrpl_tests DISCOVERY_TIMEOUT 60) From 577d7457f1e8e9389eb24d49e34ed7cb3b00d28f Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 8 Jun 2026 18:10:05 +0100 Subject: [PATCH 070/135] ci: Use XRPLF/actions build-multiarch-image workflow (#7428) --- .github/workflows/build-nix-images.yml | 7 +- .github/workflows/build-packaging-images.yml | 7 +- .../workflows/reusable-build-docker-image.yml | 89 ------------------- .../reusable-build-merge-docker-images.yml | 89 ------------------- nix/docker/check-tools.sh | 1 + nix/packages.nix | 1 + 6 files changed, 6 insertions(+), 188 deletions(-) delete mode 100644 .github/workflows/reusable-build-docker-image.yml delete mode 100644 .github/workflows/reusable-build-merge-docker-images.yml diff --git a/.github/workflows/build-nix-images.yml b/.github/workflows/build-nix-images.yml index 4e38ca7c57..24f069902d 100644 --- a/.github/workflows/build-nix-images.yml +++ b/.github/workflows/build-nix-images.yml @@ -6,16 +6,12 @@ on: - develop paths: - ".github/workflows/build-nix-images.yml" - - ".github/workflows/reusable-build-docker-image.yml" - - ".github/workflows/reusable-build-merge-docker-images.yml" - "flake.nix" - "flake.lock" - "nix/**" pull_request: paths: - ".github/workflows/build-nix-images.yml" - - ".github/workflows/reusable-build-docker-image.yml" - - ".github/workflows/reusable-build-merge-docker-images.yml" - "flake.nix" - "flake.lock" - "nix/**" @@ -50,8 +46,9 @@ jobs: base_image: debian:bookworm - name: rhel base_image: registry.access.redhat.com/ubi9/ubi:latest - uses: ./.github/workflows/reusable-build-merge-docker-images.yml + uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@c1b480188519e0cad040e6aa70db1cbc5a797e07 with: image_name: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro.name }} dockerfile: nix/docker/Dockerfile base_image: ${{ matrix.distro.base_image }} + push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} diff --git a/.github/workflows/build-packaging-images.yml b/.github/workflows/build-packaging-images.yml index c445dbf726..d6dabb0f95 100644 --- a/.github/workflows/build-packaging-images.yml +++ b/.github/workflows/build-packaging-images.yml @@ -6,15 +6,11 @@ on: - develop paths: - ".github/workflows/build-packaging-images.yml" - - ".github/workflows/reusable-build-docker-image.yml" - - ".github/workflows/reusable-build-merge-docker-images.yml" - "package/Dockerfile" - "package/install-packaging-tools.sh" pull_request: paths: - ".github/workflows/build-packaging-images.yml" - - ".github/workflows/reusable-build-docker-image.yml" - - ".github/workflows/reusable-build-merge-docker-images.yml" - "package/Dockerfile" - "package/install-packaging-tools.sh" workflow_dispatch: @@ -42,8 +38,9 @@ jobs: base_image: debian:bookworm - name: rhel base_image: registry.access.redhat.com/ubi9/ubi:latest - uses: ./.github/workflows/reusable-build-merge-docker-images.yml + uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@c1b480188519e0cad040e6aa70db1cbc5a797e07 with: image_name: ghcr.io/xrplf/xrpld/packaging-${{ matrix.distro.name }} dockerfile: package/Dockerfile base_image: ${{ matrix.distro.base_image }} + push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} diff --git a/.github/workflows/reusable-build-docker-image.yml b/.github/workflows/reusable-build-docker-image.yml deleted file mode 100644 index 253563c6a5..0000000000 --- a/.github/workflows/reusable-build-docker-image.yml +++ /dev/null @@ -1,89 +0,0 @@ -# Build a single-platform Docker image. On push, the image is pushed to -# GHCR with arch-suffixed tags (e.g. `:latest-amd64`, `:sha-abc-amd64`) -# so the calling workflow can stitch per-arch builds into a multi-arch -# manifest without needing to pass digests around. -name: Reusable build Docker image (single platform) - -on: - workflow_call: - inputs: - image_name: - description: "Full image name without tag (e.g. 'ghcr.io/xrplf/xrpld/nix-ubuntu')" - required: true - type: string - dockerfile: - description: "Path to the Dockerfile, relative to the repository root" - required: true - type: string - base_image: - description: "Value passed to the Dockerfile as the BASE_IMAGE build arg" - required: true - type: string - platform: - description: "Docker platform string, e.g. linux/amd64" - required: true - type: string - runner: - description: "GitHub Actions runner label to build on" - required: true - type: string - push: - description: "Whether to push the image to GHCR" - required: true - type: boolean - -defaults: - run: - shell: bash - -jobs: - build: - name: Build ${{ inputs.platform }} - runs-on: ${{ inputs.runner }} - permissions: - contents: read - packages: write - - steps: - - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - - name: Determine arch - id: vars - env: - PLATFORM: ${{ inputs.platform }} - run: | - echo "arch=${PLATFORM##*/}" >>$GITHUB_OUTPUT - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - - - name: Login to GitHub Container Registry - if: inputs.push - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Docker metadata - id: meta - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 - with: - images: ${{ inputs.image_name }} - tags: | - type=sha,prefix=sha-,format=short - type=raw,value=latest - flavor: | - suffix=-${{ steps.vars.outputs.arch }},onlatest=true - - - name: Build and push - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 - with: - context: . - file: ${{ inputs.dockerfile }} - platforms: ${{ inputs.platform }} - push: ${{ inputs.push }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: BASE_IMAGE=${{ inputs.base_image }} diff --git a/.github/workflows/reusable-build-merge-docker-images.yml b/.github/workflows/reusable-build-merge-docker-images.yml deleted file mode 100644 index 98deb6ea3f..0000000000 --- a/.github/workflows/reusable-build-merge-docker-images.yml +++ /dev/null @@ -1,89 +0,0 @@ -name: Reusable build and merge Docker image (multi-arch) - -on: - workflow_call: - inputs: - image_name: - description: "Full image name without tag (e.g. 'ghcr.io/xrplf/xrpld/nix-ubuntu')" - required: true - type: string - dockerfile: - description: "Path to the Dockerfile, relative to the repository root" - required: true - type: string - base_image: - description: "Value passed to the Dockerfile as the BASE_IMAGE build arg" - required: true - type: string - -defaults: - run: - shell: bash - -jobs: - build: - name: Build ${{ inputs.image_name }} - permissions: - contents: read - packages: write - - strategy: - fail-fast: false - matrix: - target: - - platform: linux/amd64 - runner: ubuntu-latest - - platform: linux/arm64 - runner: ubuntu-24.04-arm - - uses: ./.github/workflows/reusable-build-docker-image.yml - with: - image_name: ${{ inputs.image_name }} - dockerfile: ${{ inputs.dockerfile }} - base_image: ${{ inputs.base_image }} - platform: ${{ matrix.target.platform }} - runner: ${{ matrix.target.runner }} - push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} - - merge: - name: Merge ${{ inputs.image_name }} - needs: build - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - - - name: Docker metadata - id: meta - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 - with: - images: ${{ inputs.image_name }} - tags: | - type=sha,prefix=sha-,format=short - type=raw,value=latest - - - name: Login to GitHub Container Registry - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Create multi-arch manifests - if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} - run: | - for tag in $(jq -cr '.tags[]' <<<"$DOCKER_METADATA_OUTPUT_JSON"); do - docker buildx imagetools create -t "$tag" "${tag}-amd64" "${tag}-arm64" - done - - - name: Inspect image - if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} - env: - IMAGE_NAME: ${{ inputs.image_name }} - IMAGE_VERSION: ${{ steps.meta.outputs.version }} - run: | - docker buildx imagetools inspect "${IMAGE_NAME}:${IMAGE_VERSION}" diff --git a/nix/docker/check-tools.sh b/nix/docker/check-tools.sh index faa6520678..67bcdff8a9 100755 --- a/nix/docker/check-tools.sh +++ b/nix/docker/check-tools.sh @@ -15,6 +15,7 @@ gcc --version gcov --version gcovr --version git --version +git-cliff --version gpg --version less --version make --version diff --git a/nix/packages.nix b/nix/packages.nix index 6a83446d88..d40472634b 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -15,6 +15,7 @@ in doxygen gcovr git + git-cliff gnumake gnupg # needed for signing commits & codecov/codecov-action llvmPackages_22.clang-tools From ee9fbc4e08cd52afed4bf3abd3e2c46169331896 Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 9 Jun 2026 06:04:09 -0400 Subject: [PATCH 071/135] refactor: Use const function arguments where possible (#7423) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- include/xrpl/net/HTTPClient.h | 6 ++--- include/xrpl/shamap/SHAMap.h | 24 +++++++++++-------- src/libxrpl/net/HTTPClient.cpp | 8 +++---- src/libxrpl/shamap/SHAMap.cpp | 10 ++++---- src/libxrpl/shamap/SHAMapSync.cpp | 6 ++--- src/xrpld/app/ledger/InboundLedger.h | 8 +++---- src/xrpld/app/ledger/detail/InboundLedger.cpp | 10 ++++---- src/xrpld/app/main/Application.cpp | 4 ++-- src/xrpld/app/main/Application.h | 2 +- src/xrpld/consensus/Validations.h | 2 +- src/xrpld/overlay/Overlay.h | 8 +++---- src/xrpld/overlay/detail/OverlayImpl.cpp | 10 ++++---- src/xrpld/overlay/detail/OverlayImpl.h | 10 ++++---- 13 files changed, 56 insertions(+), 52 deletions(-) diff --git a/include/xrpl/net/HTTPClient.h b/include/xrpl/net/HTTPClient.h index f059b19047..456f769922 100644 --- a/include/xrpl/net/HTTPClient.h +++ b/include/xrpl/net/HTTPClient.h @@ -53,7 +53,7 @@ public: boost::system::error_code const& ecResult, int iStatus, std::string const& strData)> complete, - beast::Journal& j); + beast::Journal const& j); static void get(bool bSSL, @@ -67,7 +67,7 @@ public: boost::system::error_code const& ecResult, int iStatus, std::string const& strData)> complete, - beast::Journal& j); + beast::Journal const& j); static void request( @@ -82,7 +82,7 @@ public: boost::system::error_code const& ecResult, int iStatus, std::string const& strData)> complete, - beast::Journal& j); + beast::Journal const& j); }; } // namespace xrpl diff --git a/include/xrpl/shamap/SHAMap.h b/include/xrpl/shamap/SHAMap.h index 32e87b64c6..3d08318cf6 100644 --- a/include/xrpl/shamap/SHAMap.h +++ b/include/xrpl/shamap/SHAMap.h @@ -161,7 +161,7 @@ public: setLedgerSeq(std::uint32_t lseq); bool - fetchRoot(SHAMapHash const& hash, SHAMapSyncFilter* filter); + fetchRoot(SHAMapHash const& hash, SHAMapSyncFilter const* filter); // normal hash access functions @@ -248,7 +248,7 @@ public: @param return The nodes known to be missing */ std::vector> - getMissingNodes(int maxNodes, SHAMapSyncFilter* filter); + getMissingNodes(int maxNodes, SHAMapSyncFilter const* filter); bool getNodeFat( @@ -281,9 +281,9 @@ public: serializeRoot(Serializer& s) const; SHAMapAddNode - addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFilter* filter); + addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFilter const* filter); SHAMapAddNode - addKnownNode(SHAMapNodeID const& nodeID, Slice const& rawNode, SHAMapSyncFilter* filter); + addKnownNode(SHAMapNodeID const& nodeID, Slice const& rawNode, SHAMapSyncFilter const* filter); // status functions void @@ -343,11 +343,11 @@ private: SHAMapTreeNodePtr fetchNodeNT(SHAMapHash const& hash) const; SHAMapTreeNodePtr - fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter* filter) const; + fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter const* filter) const; SHAMapTreeNodePtr fetchNode(SHAMapHash const& hash) const; SHAMapTreeNodePtr - checkFilter(SHAMapHash const& hash, SHAMapSyncFilter* filter) const; + checkFilter(SHAMapHash const& hash, SHAMapSyncFilter const* filter) const; /** Update hashes up to the root */ void @@ -411,7 +411,7 @@ private: descendAsync( SHAMapInnerNode* parent, int branch, - SHAMapSyncFilter* filter, + SHAMapSyncFilter const* filter, bool& pending, descendCallback&&) const; @@ -420,7 +420,7 @@ private: SHAMapInnerNode* parent, SHAMapNodeID const& parentID, int branch, - SHAMapSyncFilter* filter) const; + SHAMapSyncFilter const* filter) const; // Non-storing // Does not hook the returned node to its parent @@ -461,7 +461,7 @@ private: // basic parameters int max; - SHAMapSyncFilter* filter; + SHAMapSyncFilter const* filter; int const maxDefer; std::uint32_t generation; @@ -500,7 +500,11 @@ private: // reads std::map resumes; - MissingNodes(int max, SHAMapSyncFilter* filter, int maxDefer, std::uint32_t generation) + MissingNodes( + int max, + SHAMapSyncFilter const* filter, + int maxDefer, + std::uint32_t generation) : max(max), filter(filter), maxDefer(maxDefer), generation(generation), deferred(0) { missingNodes.reserve(max); diff --git a/src/libxrpl/net/HTTPClient.cpp b/src/libxrpl/net/HTTPClient.cpp index 78ee5eb577..4b9cc9d6e6 100644 --- a/src/libxrpl/net/HTTPClient.cpp +++ b/src/libxrpl/net/HTTPClient.cpp @@ -64,7 +64,7 @@ public: boost::asio::io_context& ioContext, unsigned short const port, std::size_t maxResponseSize, - beast::Journal& j) + beast::Journal const& j) : socket_( ioContext, gHttpClientSslContext->context()) // NOLINT(bugprone-unchecked-optional-access) @@ -552,7 +552,7 @@ HTTPClient::get( std::function< bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)> complete, - beast::Journal& j) + beast::Journal const& j) { auto client = std::make_shared(ioContext, port, responseMax, j); client->get(bSSL, deqSites, strPath, timeout, complete); @@ -570,7 +570,7 @@ HTTPClient::get( std::function< bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)> complete, - beast::Journal& j) + beast::Journal const& j) { std::deque const deqSites(1, strSite); @@ -590,7 +590,7 @@ HTTPClient::request( std::function< bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)> complete, - beast::Journal& j) + beast::Journal const& j) { std::deque const deqSites(1, strSite); diff --git a/src/libxrpl/shamap/SHAMap.cpp b/src/libxrpl/shamap/SHAMap.cpp index 4aad255d81..8a521f6a47 100644 --- a/src/libxrpl/shamap/SHAMap.cpp +++ b/src/libxrpl/shamap/SHAMap.cpp @@ -206,7 +206,7 @@ SHAMap::finishFetch(SHAMapHash const& hash, std::shared_ptr const& o // See if a sync filter has a node SHAMapTreeNodePtr -SHAMap::checkFilter(SHAMapHash const& hash, SHAMapSyncFilter* filter) const +SHAMap::checkFilter(SHAMapHash const& hash, SHAMapSyncFilter const* filter) const { if (auto nodeData = filter->getNode(hash)) { @@ -232,7 +232,7 @@ SHAMap::checkFilter(SHAMapHash const& hash, SHAMapSyncFilter* filter) const // Get a node without throwing // Used on maps where missing nodes are expected SHAMapTreeNodePtr -SHAMap::fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter* filter) const +SHAMap::fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter const* filter) const { auto node = cacheLookup(hash); if (node) @@ -345,7 +345,7 @@ SHAMap::descend( SHAMapInnerNode* parent, SHAMapNodeID const& parentID, int branch, - SHAMapSyncFilter* filter) const + SHAMapSyncFilter const* filter) const { XRPL_ASSERT(parent->isInner(), "xrpl::SHAMap::descend : valid parent input"); XRPL_ASSERT( @@ -374,7 +374,7 @@ SHAMapTreeNode* SHAMap::descendAsync( SHAMapInnerNode* parent, int branch, - SHAMapSyncFilter* filter, + SHAMapSyncFilter const* filter, bool& pending, descendCallback&& callback) const { @@ -885,7 +885,7 @@ SHAMap::updateGiveItem(SHAMapNodeType type, boost::intrusive_ptrgetHash()) return true; diff --git a/src/libxrpl/shamap/SHAMapSync.cpp b/src/libxrpl/shamap/SHAMapSync.cpp index 0601bfefda..1f38049abe 100644 --- a/src/libxrpl/shamap/SHAMapSync.cpp +++ b/src/libxrpl/shamap/SHAMapSync.cpp @@ -305,7 +305,7 @@ SHAMap::gmnProcessDeferredReads(MissingNodes& mn) nodes that are not permanently stored locally */ std::vector> -SHAMap::getMissingNodes(int max, SHAMapSyncFilter* filter) +SHAMap::getMissingNodes(int max, SHAMapSyncFilter const* filter) { XRPL_ASSERT(root_->getHash().isNonZero(), "xrpl::SHAMap::getMissingNodes : nonzero root hash"); XRPL_ASSERT(max > 0, "xrpl::SHAMap::getMissingNodes : valid max input"); @@ -507,7 +507,7 @@ SHAMap::serializeRoot(Serializer& s) const } SHAMapAddNode -SHAMap::addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFilter* filter) +SHAMap::addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFilter const* filter) { // we already have a root_ node if (root_->getHash().isNonZero()) @@ -542,7 +542,7 @@ SHAMap::addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFil } SHAMapAddNode -SHAMap::addKnownNode(SHAMapNodeID const& node, Slice const& rawNode, SHAMapSyncFilter* filter) +SHAMap::addKnownNode(SHAMapNodeID const& node, Slice const& rawNode, SHAMapSyncFilter const* filter) { XRPL_ASSERT(!node.isRoot(), "xrpl::SHAMap::addKnownNode : valid node input"); diff --git a/src/xrpld/app/ledger/InboundLedger.h b/src/xrpld/app/ledger/InboundLedger.h index d155c5902c..b82e2f69cd 100644 --- a/src/xrpld/app/ledger/InboundLedger.h +++ b/src/xrpld/app/ledger/InboundLedger.h @@ -128,13 +128,13 @@ private: pmDowncast() override; int - processData(std::shared_ptr peer, protocol::TMLedgerData& data); + processData(std::shared_ptr peer, protocol::TMLedgerData const& data); bool takeHeader(std::string const& data); void - receiveNode(protocol::TMLedgerData& packet, SHAMapAddNode&); + receiveNode(protocol::TMLedgerData const& packet, SHAMapAddNode&); bool takeTxRootNode(Slice const& data, SHAMapAddNode&); @@ -143,10 +143,10 @@ private: takeAsRootNode(Slice const& data, SHAMapAddNode&); std::vector - neededTxHashes(int max, SHAMapSyncFilter* filter) const; + neededTxHashes(int max, SHAMapSyncFilter const* filter) const; std::vector - neededStateHashes(int max, SHAMapSyncFilter* filter) const; + neededStateHashes(int max, SHAMapSyncFilter const* filter) const; clock_type& clock_; clock_type::time_point lastAction_; diff --git a/src/xrpld/app/ledger/detail/InboundLedger.cpp b/src/xrpld/app/ledger/detail/InboundLedger.cpp index 9ba7bdf22e..5a9f24cc2e 100644 --- a/src/xrpld/app/ledger/detail/InboundLedger.cpp +++ b/src/xrpld/app/ledger/detail/InboundLedger.cpp @@ -188,7 +188,7 @@ InboundLedger::~InboundLedger() } static std::vector -neededHashes(uint256 const& root, SHAMap& map, int max, SHAMapSyncFilter* filter) +neededHashes(uint256 const& root, SHAMap& map, int max, SHAMapSyncFilter const* filter) { std::vector ret; @@ -211,13 +211,13 @@ neededHashes(uint256 const& root, SHAMap& map, int max, SHAMapSyncFilter* filter } std::vector -InboundLedger::neededTxHashes(int max, SHAMapSyncFilter* filter) const +InboundLedger::neededTxHashes(int max, SHAMapSyncFilter const* filter) const { return neededHashes(ledger_->header().txHash, ledger_->txMap(), max, filter); } std::vector -InboundLedger::neededStateHashes(int max, SHAMapSyncFilter* filter) const +InboundLedger::neededStateHashes(int max, SHAMapSyncFilter const* filter) const { return neededHashes(ledger_->header().accountHash, ledger_->stateMap(), max, filter); } @@ -820,7 +820,7 @@ InboundLedger::takeHeader(std::string const& data) Call with a lock */ void -InboundLedger::receiveNode(protocol::TMLedgerData& packet, SHAMapAddNode& san) +InboundLedger::receiveNode(protocol::TMLedgerData const& packet, SHAMapAddNode& san) { if (!haveHeader_) { @@ -1026,7 +1026,7 @@ InboundLedger::gotData( // TODO Change peer to Consumer // int -InboundLedger::processData(std::shared_ptr peer, protocol::TMLedgerData& packet) +InboundLedger::processData(std::shared_ptr peer, protocol::TMLedgerData const& packet) { if (packet.type() == protocol::liBASE) { diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index 508dfc8590..af5d51289d 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -492,7 +492,7 @@ public: void run() override; void - signalStop(std::string msg) override; + signalStop(std::string const& msg) override; bool checkSigs() const override; void @@ -1602,7 +1602,7 @@ ApplicationImp::run() } void -ApplicationImp::signalStop(std::string msg) +ApplicationImp::signalStop(std::string const& msg) { if (!isTimeToStop.test_and_set(std::memory_order_acquire)) { diff --git a/src/xrpld/app/main/Application.h b/src/xrpld/app/main/Application.h index 200fed7cf9..08e41e2c4c 100644 --- a/src/xrpld/app/main/Application.h +++ b/src/xrpld/app/main/Application.h @@ -111,7 +111,7 @@ public: virtual void run() = 0; virtual void - signalStop(std::string msg) = 0; + signalStop(std::string const& msg) = 0; [[nodiscard]] virtual bool checkSigs() const = 0; virtual void diff --git a/src/xrpld/consensus/Validations.h b/src/xrpld/consensus/Validations.h index 7be578060e..2f5762ce83 100644 --- a/src/xrpld/consensus/Validations.h +++ b/src/xrpld/consensus/Validations.h @@ -693,7 +693,7 @@ public: validationSET_EXPIRES ago and were not asked to keep. */ void - expire(beast::Journal& j) + expire(beast::Journal const& j) { auto const start = std::chrono::steady_clock::now(); { diff --git a/src/xrpld/overlay/Overlay.h b/src/xrpld/overlay/Overlay.h index ef97ea7f24..87c6ff132a 100644 --- a/src/xrpld/overlay/Overlay.h +++ b/src/xrpld/overlay/Overlay.h @@ -117,11 +117,11 @@ public: /** Broadcast a proposal. */ virtual void - broadcast(protocol::TMProposeSet& m) = 0; + broadcast(protocol::TMProposeSet const& m) = 0; /** Broadcast a validation. */ virtual void - broadcast(protocol::TMValidation& m) = 0; + broadcast(protocol::TMValidation const& m) = 0; /** Relay a proposal. * @param m the serialized proposal @@ -130,7 +130,7 @@ public: * @return the set of peers which have already sent us this proposal */ virtual std::set - relay(protocol::TMProposeSet& m, uint256 const& uid, PublicKey const& validator) = 0; + relay(protocol::TMProposeSet const& m, uint256 const& uid, PublicKey const& validator) = 0; /** Relay a validation. * @param m the serialized validation @@ -139,7 +139,7 @@ public: * @return the set of peers which have already sent us this validation */ virtual std::set - relay(protocol::TMValidation& m, uint256 const& uid, PublicKey const& validator) = 0; + relay(protocol::TMValidation const& m, uint256 const& uid, PublicKey const& validator) = 0; /** Relay a transaction. If the tx reduce-relay feature is enabled then * randomly select peers to relay to and queue transaction's hash diff --git a/src/xrpld/overlay/detail/OverlayImpl.cpp b/src/xrpld/overlay/detail/OverlayImpl.cpp index b31f54058a..b71cef6719 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.cpp +++ b/src/xrpld/overlay/detail/OverlayImpl.cpp @@ -407,7 +407,7 @@ OverlayImpl::makeErrorResponse( std::shared_ptr const& slot, http_request_type const& request, address_type remoteAddress, - std::string text) + std::string const& text) { boost::beast::http::response msg; msg.version(request.version()); @@ -1157,14 +1157,14 @@ OverlayImpl::findPeerByPublicKey(PublicKey const& pubKey) } void -OverlayImpl::broadcast(protocol::TMProposeSet& m) +OverlayImpl::broadcast(protocol::TMProposeSet const& m) { auto const sm = std::make_shared(m, protocol::mtPROPOSE_LEDGER); forEach([&](std::shared_ptr const& p) { p->send(sm); }); } std::set -OverlayImpl::relay(protocol::TMProposeSet& m, uint256 const& uid, PublicKey const& validator) +OverlayImpl::relay(protocol::TMProposeSet const& m, uint256 const& uid, PublicKey const& validator) { if (auto const toSkip = app_.getHashRouter().shouldRelay(uid)) { @@ -1179,14 +1179,14 @@ OverlayImpl::relay(protocol::TMProposeSet& m, uint256 const& uid, PublicKey cons } void -OverlayImpl::broadcast(protocol::TMValidation& m) +OverlayImpl::broadcast(protocol::TMValidation const& m) { auto const sm = std::make_shared(m, protocol::mtVALIDATION); forEach([sm](std::shared_ptr const& p) { p->send(sm); }); } std::set -OverlayImpl::relay(protocol::TMValidation& m, uint256 const& uid, PublicKey const& validator) +OverlayImpl::relay(protocol::TMValidation const& m, uint256 const& uid, PublicKey const& validator) { if (auto const toSkip = app_.getHashRouter().shouldRelay(uid)) { diff --git a/src/xrpld/overlay/detail/OverlayImpl.h b/src/xrpld/overlay/detail/OverlayImpl.h index 6fcc2df854..545d9eb75c 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.h +++ b/src/xrpld/overlay/detail/OverlayImpl.h @@ -202,16 +202,16 @@ public: findPeerByPublicKey(PublicKey const& pubKey) override; void - broadcast(protocol::TMProposeSet& m) override; + broadcast(protocol::TMProposeSet const& m) override; void - broadcast(protocol::TMValidation& m) override; + broadcast(protocol::TMValidation const& m) override; std::set - relay(protocol::TMProposeSet& m, uint256 const& uid, PublicKey const& validator) override; + relay(protocol::TMProposeSet const& m, uint256 const& uid, PublicKey const& validator) override; std::set - relay(protocol::TMValidation& m, uint256 const& uid, PublicKey const& validator) override; + relay(protocol::TMValidation const& m, uint256 const& uid, PublicKey const& validator) override; void relay( @@ -433,7 +433,7 @@ private: std::shared_ptr const& slot, http_request_type const& request, address_type remoteAddress, - std::string msg); + std::string const& msg); /** Handles crawl requests. Crawl returns information about the node and its peers so crawlers can map the network. From c9769d1add290e4fb53add23d1e9859237134b73 Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 9 Jun 2026 09:56:32 -0400 Subject: [PATCH 072/135] refactor: Use `std::move` and `std::string_view` where possible (#7424) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- include/xrpl/basics/StringUtilities.h | 9 ++---- include/xrpl/basics/base64.h | 3 +- include/xrpl/basics/join.h | 3 +- include/xrpl/beast/insight/Counter.h | 3 +- include/xrpl/beast/insight/Event.h | 3 +- include/xrpl/beast/insight/Gauge.h | 3 +- include/xrpl/beast/insight/Hook.h | 3 +- include/xrpl/beast/insight/Meter.h | 3 +- include/xrpl/beast/unit_test/match.h | 6 ++-- include/xrpl/crypto/RFC1751.h | 3 +- include/xrpl/ledger/CanonicalTXSet.h | 2 +- include/xrpl/protocol/STVector256.h | 10 +++--- include/xrpl/server/Session.h | 5 +-- include/xrpl/shamap/SHAMapNodeID.h | 3 +- src/libxrpl/beast/insight/StatsDCollector.cpp | 32 +++++++++---------- src/libxrpl/crypto/RFC1751.cpp | 3 +- src/libxrpl/ledger/CanonicalTXSet.cpp | 10 ++---- src/test/jtx/credentials.h | 2 +- src/test/server/Server_test.cpp | 4 ++- src/xrpld/app/consensus/RCLCxLedger.h | 2 +- src/xrpld/app/consensus/RCLValidations.h | 2 +- src/xrpld/app/ledger/AcceptedLedger.cpp | 19 ++++------- src/xrpld/app/ledger/AcceptedLedger.h | 2 +- src/xrpld/app/ledger/OpenLedger.h | 3 +- src/xrpld/app/ledger/detail/OpenLedger.cpp | 3 +- src/xrpld/app/ledger/detail/SkipListAcquire.h | 4 +-- src/xrpld/app/misc/detail/ValidatorList.cpp | 2 +- src/xrpld/overlay/detail/Cluster.cpp | 3 +- src/xrpld/peerfinder/PeerfinderManager.h | 2 +- src/xrpld/peerfinder/detail/Logic.h | 4 +-- .../peerfinder/detail/PeerfinderManager.cpp | 4 +-- src/xrpld/rpc/detail/AssetCache.cpp | 4 +-- src/xrpld/rpc/detail/AssetCache.h | 2 +- src/xrpld/rpc/detail/PathRequest.cpp | 4 +-- src/xrpld/rpc/detail/PathRequest.h | 2 +- 35 files changed, 86 insertions(+), 86 deletions(-) diff --git a/include/xrpl/basics/StringUtilities.h b/include/xrpl/basics/StringUtilities.h index 28421626aa..1d3434b7ed 100644 --- a/include/xrpl/basics/StringUtilities.h +++ b/include/xrpl/basics/StringUtilities.h @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace xrpl { @@ -95,13 +96,7 @@ strUnHex(std::size_t strSize, Iterator begin, Iterator end) } inline std::optional -strUnHex(std::string const& strSrc) -{ - return strUnHex(strSrc.size(), strSrc.cbegin(), strSrc.cend()); -} - -inline std::optional -strViewUnHex(std::string_view strSrc) +strUnHex(std::string_view strSrc) { return strUnHex(strSrc.size(), strSrc.cbegin(), strSrc.cend()); } diff --git a/include/xrpl/basics/base64.h b/include/xrpl/basics/base64.h index ed30e40a36..660958ce14 100644 --- a/include/xrpl/basics/base64.h +++ b/include/xrpl/basics/base64.h @@ -36,6 +36,7 @@ #include #include +#include namespace xrpl { @@ -43,7 +44,7 @@ std::string base64Encode(std::uint8_t const* data, std::size_t len); inline std::string -base64Encode(std::string const& s) +base64Encode(std::string_view s) { return base64Encode(reinterpret_cast(s.data()), s.size()); } diff --git a/include/xrpl/basics/join.h b/include/xrpl/basics/join.h index 0fb00aaf82..c214212473 100644 --- a/include/xrpl/basics/join.h +++ b/include/xrpl/basics/join.h @@ -1,12 +1,13 @@ #pragma once #include +#include namespace xrpl { template Stream& -join(Stream& s, Iter iter, Iter end, std::string const& delimiter) +join(Stream& s, Iter iter, Iter end, std::string_view delimiter) { if (iter == end) return s; diff --git a/include/xrpl/beast/insight/Counter.h b/include/xrpl/beast/insight/Counter.h index 71ace4bb4e..482808b2c7 100644 --- a/include/xrpl/beast/insight/Counter.h +++ b/include/xrpl/beast/insight/Counter.h @@ -3,6 +3,7 @@ #include #include +#include namespace beast::insight { @@ -29,7 +30,7 @@ public: factory function in the Collector interface. @see Collector. */ - explicit Counter(std::shared_ptr const& impl) : impl_(impl) + explicit Counter(std::shared_ptr impl) : impl_(std::move(impl)) { } diff --git a/include/xrpl/beast/insight/Event.h b/include/xrpl/beast/insight/Event.h index 5e424a0f9b..afccf9baba 100644 --- a/include/xrpl/beast/insight/Event.h +++ b/include/xrpl/beast/insight/Event.h @@ -4,6 +4,7 @@ #include #include +#include namespace beast::insight { @@ -31,7 +32,7 @@ public: factory function in the Collector interface. @see Collector. */ - explicit Event(std::shared_ptr const& impl) : impl_(impl) + explicit Event(std::shared_ptr impl) : impl_(std::move(impl)) { } diff --git a/include/xrpl/beast/insight/Gauge.h b/include/xrpl/beast/insight/Gauge.h index dd2c4bc6b6..b24c4366c3 100644 --- a/include/xrpl/beast/insight/Gauge.h +++ b/include/xrpl/beast/insight/Gauge.h @@ -3,6 +3,7 @@ #include #include +#include namespace beast::insight { @@ -31,7 +32,7 @@ public: factory function in the Collector interface. @see Collector. */ - explicit Gauge(std::shared_ptr const& impl) : impl_(impl) + explicit Gauge(std::shared_ptr impl) : impl_(std::move(impl)) { } diff --git a/include/xrpl/beast/insight/Hook.h b/include/xrpl/beast/insight/Hook.h index 1cb6cae5d9..8dbe5a4be0 100644 --- a/include/xrpl/beast/insight/Hook.h +++ b/include/xrpl/beast/insight/Hook.h @@ -3,6 +3,7 @@ #include #include +#include namespace beast::insight { @@ -20,7 +21,7 @@ public: factory function in the Collector interface. @see Collector. */ - explicit Hook(std::shared_ptr const& impl) : impl_(impl) + explicit Hook(std::shared_ptr impl) : impl_(std::move(impl)) { } diff --git a/include/xrpl/beast/insight/Meter.h b/include/xrpl/beast/insight/Meter.h index 03aa17c313..25ffabd928 100644 --- a/include/xrpl/beast/insight/Meter.h +++ b/include/xrpl/beast/insight/Meter.h @@ -3,6 +3,7 @@ #include #include +#include namespace beast::insight { @@ -28,7 +29,7 @@ public: factory function in the Collector interface. @see Collector. */ - explicit Meter(std::shared_ptr const& impl) : impl_(impl) + explicit Meter(std::shared_ptr impl) : impl_(std::move(impl)) { } diff --git a/include/xrpl/beast/unit_test/match.h b/include/xrpl/beast/unit_test/match.h index 222c4ea656..da466ab228 100644 --- a/include/xrpl/beast/unit_test/match.h +++ b/include/xrpl/beast/unit_test/match.h @@ -41,7 +41,7 @@ private: public: template - explicit Selector(ModeT mode, std::string const& pattern = ""); + explicit Selector(ModeT mode, std::string pattern = ""); template bool @@ -51,9 +51,9 @@ public: //------------------------------------------------------------------------------ template -Selector::Selector(ModeT mode, std::string const& pattern) : mode_(mode), pat_(pattern) +Selector::Selector(ModeT mode, std::string pattern) : mode_(mode), pat_(std::move(pattern)) { - if (mode_ == ModeT::Automatch && pattern.empty()) + if (mode_ == ModeT::Automatch && pat_.empty()) mode_ = ModeT::All; } diff --git a/include/xrpl/crypto/RFC1751.h b/include/xrpl/crypto/RFC1751.h index c99c691ba0..19b636b9dc 100644 --- a/include/xrpl/crypto/RFC1751.h +++ b/include/xrpl/crypto/RFC1751.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace xrpl { @@ -34,7 +35,7 @@ private: static void standard(std::string& strWord); static int - wsrch(std::string const& strWord, int iMin, int iMax); + wsrch(std::string_view strWord, int iMin, int iMax); static int etob(std::string& strData, std::vector vsHuman); diff --git a/include/xrpl/ledger/CanonicalTXSet.h b/include/xrpl/ledger/CanonicalTXSet.h index 8653816eee..4dffadd52f 100644 --- a/include/xrpl/ledger/CanonicalTXSet.h +++ b/include/xrpl/ledger/CanonicalTXSet.h @@ -93,7 +93,7 @@ public: } void - insert(std::shared_ptr const& txn); + insert(std::shared_ptr txn); // Pops the next transaction on account that follows seqProx in the // sort order. Normally called when a transaction is successfully diff --git a/include/xrpl/protocol/STVector256.h b/include/xrpl/protocol/STVector256.h index ab3a2f99e3..5c454b6be0 100644 --- a/include/xrpl/protocol/STVector256.h +++ b/include/xrpl/protocol/STVector256.h @@ -17,8 +17,8 @@ public: STVector256() = default; explicit STVector256(SField const& n); - explicit STVector256(std::vector const& vector); - STVector256(SField const& n, std::vector const& vector); + explicit STVector256(std::vector vector); + STVector256(SField const& n, std::vector vector); STVector256(SerialIter& sit, SField const& name); [[nodiscard]] SerializedTypeID @@ -103,12 +103,12 @@ inline STVector256::STVector256(SField const& n) : STBase(n) { } -inline STVector256::STVector256(std::vector const& vector) : value_(vector) +inline STVector256::STVector256(std::vector vector) : value_(std::move(vector)) { } -inline STVector256::STVector256(SField const& n, std::vector const& vector) - : STBase(n), value_(vector) +inline STVector256::STVector256(SField const& n, std::vector vector) + : STBase(n), value_(std::move(vector)) { } diff --git a/include/xrpl/server/Session.h b/include/xrpl/server/Session.h index 2cb991e130..151e57e7f2 100644 --- a/include/xrpl/server/Session.h +++ b/include/xrpl/server/Session.h @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace xrpl { @@ -53,10 +54,10 @@ public: /** Send a copy of data asynchronously. */ /** @{ */ void - write(std::string const& s) + write(std::string_view s) { if (!s.empty()) - write(&s[0], std::distance(s.begin(), s.end())); + write(s.data(), s.size()); } template diff --git a/include/xrpl/shamap/SHAMapNodeID.h b/include/xrpl/shamap/SHAMapNodeID.h index dbc087b356..248c9cb80b 100644 --- a/include/xrpl/shamap/SHAMapNodeID.h +++ b/include/xrpl/shamap/SHAMapNodeID.h @@ -5,6 +5,7 @@ #include #include +#include #include namespace xrpl { @@ -127,7 +128,7 @@ operator<<(std::ostream& out, SHAMapNodeID const& node) deserializeSHAMapNodeID(void const* data, std::size_t size); [[nodiscard]] inline std::optional -deserializeSHAMapNodeID(std::string const& s) +deserializeSHAMapNodeID(std::string_view s) { return deserializeSHAMapNodeID(s.data(), s.size()); } diff --git a/src/libxrpl/beast/insight/StatsDCollector.cpp b/src/libxrpl/beast/insight/StatsDCollector.cpp index 88f52c26de..0d0e013274 100644 --- a/src/libxrpl/beast/insight/StatsDCollector.cpp +++ b/src/libxrpl/beast/insight/StatsDCollector.cpp @@ -66,7 +66,7 @@ public: class StatsDHookImpl : public HookImpl, public StatsDMetricBase { public: - StatsDHookImpl(HandlerType handler, std::shared_ptr const& impl); + StatsDHookImpl(HandlerType handler, std::shared_ptr impl); ~StatsDHookImpl() override; @@ -86,7 +86,7 @@ private: class StatsDCounterImpl : public CounterImpl, public StatsDMetricBase { public: - StatsDCounterImpl(std::string name, std::shared_ptr const& impl); + StatsDCounterImpl(std::string name, std::shared_ptr impl); ~StatsDCounterImpl() override; @@ -115,7 +115,7 @@ private: class StatsDEventImpl : public EventImpl { public: - StatsDEventImpl(std::string name, std::shared_ptr const& impl); + StatsDEventImpl(std::string name, std::shared_ptr impl); ~StatsDEventImpl() override = default; @@ -140,7 +140,7 @@ private: class StatsDGaugeImpl : public GaugeImpl, public StatsDMetricBase { public: - StatsDGaugeImpl(std::string name, std::shared_ptr const& impl); + StatsDGaugeImpl(std::string name, std::shared_ptr impl); ~StatsDGaugeImpl() override; @@ -174,7 +174,7 @@ private: class StatsDMeterImpl : public MeterImpl, public StatsDMetricBase { public: - explicit StatsDMeterImpl(std::string name, std::shared_ptr const& impl); + explicit StatsDMeterImpl(std::string name, std::shared_ptr impl); ~StatsDMeterImpl() override; @@ -478,8 +478,8 @@ public: //------------------------------------------------------------------------------ -StatsDHookImpl::StatsDHookImpl(HandlerType handler, std::shared_ptr const& impl) - : impl_(impl), handler_(std::move(handler)) +StatsDHookImpl::StatsDHookImpl(HandlerType handler, std::shared_ptr impl) + : impl_(std::move(impl)), handler_(std::move(handler)) { impl_->add(*this); } @@ -497,10 +497,8 @@ StatsDHookImpl::doProcess() //------------------------------------------------------------------------------ -StatsDCounterImpl::StatsDCounterImpl( - std::string name, - std::shared_ptr const& impl) - : impl_(impl), name_(std::move(name)) +StatsDCounterImpl::StatsDCounterImpl(std::string name, std::shared_ptr impl) + : impl_(std::move(impl)), name_(std::move(name)) { impl_->add(*this); } @@ -550,8 +548,8 @@ StatsDCounterImpl::doProcess() //------------------------------------------------------------------------------ -StatsDEventImpl::StatsDEventImpl(std::string name, std::shared_ptr const& impl) - : impl_(impl), name_(std::move(name)) +StatsDEventImpl::StatsDEventImpl(std::string name, std::shared_ptr impl) + : impl_(std::move(impl)), name_(std::move(name)) { } @@ -577,8 +575,8 @@ StatsDEventImpl::doNotify(EventImpl::value_type const& value) //------------------------------------------------------------------------------ -StatsDGaugeImpl::StatsDGaugeImpl(std::string name, std::shared_ptr const& impl) - : impl_(impl), name_(std::move(name)) +StatsDGaugeImpl::StatsDGaugeImpl(std::string name, std::shared_ptr impl) + : impl_(std::move(impl)), name_(std::move(name)) { impl_->add(*this); } @@ -664,8 +662,8 @@ StatsDGaugeImpl::doProcess() //------------------------------------------------------------------------------ -StatsDMeterImpl::StatsDMeterImpl(std::string name, std::shared_ptr const& impl) - : impl_(impl), name_(std::move(name)) +StatsDMeterImpl::StatsDMeterImpl(std::string name, std::shared_ptr impl) + : impl_(std::move(impl)), name_(std::move(name)) { impl_->add(*this); } diff --git a/src/libxrpl/crypto/RFC1751.cpp b/src/libxrpl/crypto/RFC1751.cpp index 30f2c3a5b8..16482945d2 100644 --- a/src/libxrpl/crypto/RFC1751.cpp +++ b/src/libxrpl/crypto/RFC1751.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace xrpl { @@ -306,7 +307,7 @@ RFC1751::standard(std::string& strWord) // Binary search of dictionary. int -RFC1751::wsrch(std::string const& strWord, int iMin, int iMax) +RFC1751::wsrch(std::string_view strWord, int iMin, int iMax) { int iResult = -1; diff --git a/src/libxrpl/ledger/CanonicalTXSet.cpp b/src/libxrpl/ledger/CanonicalTXSet.cpp index a06576342a..df4e88e346 100644 --- a/src/libxrpl/ledger/CanonicalTXSet.cpp +++ b/src/libxrpl/ledger/CanonicalTXSet.cpp @@ -40,14 +40,10 @@ CanonicalTXSet::accountKey(AccountID const& account) } void -CanonicalTXSet::insert(std::shared_ptr const& txn) +CanonicalTXSet::insert(std::shared_ptr txn) { - map_.insert( - std::make_pair( - Key(accountKey(txn->getAccountID(sfAccount)), - txn->getSeqProxy(), - txn->getTransactionID()), - txn)); + Key key(accountKey(txn->getAccountID(sfAccount)), txn->getSeqProxy(), txn->getTransactionID()); + map_.emplace(key, std::move(txn)); } std::shared_ptr diff --git a/src/test/jtx/credentials.h b/src/test/jtx/credentials.h index c2719bf897..4bdd716918 100644 --- a/src/test/jtx/credentials.h +++ b/src/test/jtx/credentials.h @@ -40,7 +40,7 @@ private: std::vector const credentials_; public: - explicit Ids(std::vector const& creds) : credentials_(creds) + explicit Ids(std::vector creds) : credentials_(std::move(creds)) { } diff --git a/src/test/server/Server_test.cpp b/src/test/server/Server_test.cpp index 263fb9451f..1b0107167c 100644 --- a/src/test/server/Server_test.cpp +++ b/src/test/server/Server_test.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -133,7 +134,8 @@ public: static void onRequest(Session& session) { - session.write(std::string("Hello, world!\n")); + using namespace std::string_view_literals; + session.write("Hello, world!\n"sv); if (beast::rfc2616::isKeepAlive(session.request())) { session.complete(); diff --git a/src/xrpld/app/consensus/RCLCxLedger.h b/src/xrpld/app/consensus/RCLCxLedger.h index 3d6ed4912b..9f7984aaa6 100644 --- a/src/xrpld/app/consensus/RCLCxLedger.h +++ b/src/xrpld/app/consensus/RCLCxLedger.h @@ -32,7 +32,7 @@ public: @param l The ledger to wrap. */ - RCLCxLedger(std::shared_ptr const& l) : ledger{l} + RCLCxLedger(std::shared_ptr l) : ledger{std::move(l)} { } diff --git a/src/xrpld/app/consensus/RCLValidations.h b/src/xrpld/app/consensus/RCLValidations.h index 16f6e15d4d..37a6f6c743 100644 --- a/src/xrpld/app/consensus/RCLValidations.h +++ b/src/xrpld/app/consensus/RCLValidations.h @@ -32,7 +32,7 @@ public: @param v The validation to wrap. */ - RCLValidation(std::shared_ptr const& v) : val_{v} + RCLValidation(std::shared_ptr v) : val_{std::move(v)} { } diff --git a/src/xrpld/app/ledger/AcceptedLedger.cpp b/src/xrpld/app/ledger/AcceptedLedger.cpp index ea594308bd..6da869198b 100644 --- a/src/xrpld/app/ledger/AcceptedLedger.cpp +++ b/src/xrpld/app/ledger/AcceptedLedger.cpp @@ -5,23 +5,18 @@ #include #include +#include namespace xrpl { -AcceptedLedger::AcceptedLedger(std::shared_ptr const& ledger) : ledger_(ledger) +AcceptedLedger::AcceptedLedger(std::shared_ptr ledger) : ledger_(std::move(ledger)) { transactions_.reserve(256); - - auto insertAll = [&](auto const& txns) { - for (auto const& item : txns) - { - transactions_.emplace_back( - std::make_unique(ledger, item.first, item.second)); - } - }; - - transactions_.reserve(256); - insertAll(ledger->txs); + for (auto const& item : ledger_->txs) + { + transactions_.emplace_back( + std::make_unique(ledger_, item.first, item.second)); + } std::ranges::sort(transactions_, [](auto const& a, auto const& b) { return a->getTxnSeq() < b->getTxnSeq(); diff --git a/src/xrpld/app/ledger/AcceptedLedger.h b/src/xrpld/app/ledger/AcceptedLedger.h index 621bea9e0d..b05af1f18a 100644 --- a/src/xrpld/app/ledger/AcceptedLedger.h +++ b/src/xrpld/app/ledger/AcceptedLedger.h @@ -25,7 +25,7 @@ namespace xrpl { class AcceptedLedger : public CountedObject { public: - AcceptedLedger(std::shared_ptr const& ledger); + AcceptedLedger(std::shared_ptr ledger); [[nodiscard]] std::shared_ptr const& getLedger() const diff --git a/src/xrpld/app/ledger/OpenLedger.h b/src/xrpld/app/ledger/OpenLedger.h index 02e073bc9a..554002d6af 100644 --- a/src/xrpld/app/ledger/OpenLedger.h +++ b/src/xrpld/app/ledger/OpenLedger.h @@ -12,6 +12,7 @@ #include #include +#include namespace xrpl { @@ -149,7 +150,7 @@ public: bool retriesFirst, OrderedTxs& retries, ApplyFlags flags, - std::string const& suffix = "", + std::string_view suffix = "", modify_type const& f = {}); private: diff --git a/src/xrpld/app/ledger/detail/OpenLedger.cpp b/src/xrpld/app/ledger/detail/OpenLedger.cpp index 3bee4b9d13..60599c80d3 100644 --- a/src/xrpld/app/ledger/detail/OpenLedger.cpp +++ b/src/xrpld/app/ledger/detail/OpenLedger.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -82,7 +83,7 @@ OpenLedger::accept( bool retriesFirst, OrderedTxs& retries, ApplyFlags flags, - std::string const& suffix, + std::string_view suffix, modify_type const& f) { JLOG(j_.trace()) << "accept ledger " << ledger->seq() << " " << suffix; diff --git a/src/xrpld/app/ledger/detail/SkipListAcquire.h b/src/xrpld/app/ledger/detail/SkipListAcquire.h index da62578d41..6600b495c9 100644 --- a/src/xrpld/app/ledger/detail/SkipListAcquire.h +++ b/src/xrpld/app/ledger/detail/SkipListAcquire.h @@ -35,8 +35,8 @@ public: std::uint32_t const ledgerSeq; std::vector const skipList; - SkipListData(std::uint32_t const ledgerSeq, std::vector const& skipList) - : ledgerSeq(ledgerSeq), skipList(skipList) + SkipListData(std::uint32_t const ledgerSeq, std::vector skipList) + : ledgerSeq(ledgerSeq), skipList(std::move(skipList)) { } }; diff --git a/src/xrpld/app/misc/detail/ValidatorList.cpp b/src/xrpld/app/misc/detail/ValidatorList.cpp index 0981c32050..a9e7156158 100644 --- a/src/xrpld/app/misc/detail/ValidatorList.cpp +++ b/src/xrpld/app/misc/detail/ValidatorList.cpp @@ -1777,7 +1777,7 @@ ValidatorList::getAvailable( { std::shared_lock const readLock{mutex_}; - auto const keyBlob = strViewUnHex(pubKey); + auto const keyBlob = strUnHex(pubKey); if (!keyBlob || !publicKeyType(makeSlice(*keyBlob))) { diff --git a/src/xrpld/overlay/detail/Cluster.cpp b/src/xrpld/overlay/detail/Cluster.cpp index dcb40a54f5..7855c9647d 100644 --- a/src/xrpld/overlay/detail/Cluster.cpp +++ b/src/xrpld/overlay/detail/Cluster.cpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace xrpl { @@ -67,7 +68,7 @@ Cluster::update( iter = nodes_.erase(iter); } - nodes_.emplace_hint(iter, identity, name, loadFee, reportTime); + nodes_.emplace_hint(iter, identity, std::move(name), loadFee, reportTime); return true; } diff --git a/src/xrpld/peerfinder/PeerfinderManager.h b/src/xrpld/peerfinder/PeerfinderManager.h index f88b1b637c..ec4beb2db4 100644 --- a/src/xrpld/peerfinder/PeerfinderManager.h +++ b/src/xrpld/peerfinder/PeerfinderManager.h @@ -201,7 +201,7 @@ public: file, along with the set of corresponding IP addresses. */ virtual void - addFixedPeer(std::string const& name, std::vector const& addresses) = 0; + addFixedPeer(std::string_view name, std::vector const& addresses) = 0; /** Add a set of strings as fallback IP::Endpoint sources. @param name A label used for diagnostics. diff --git a/src/xrpld/peerfinder/detail/Logic.h b/src/xrpld/peerfinder/detail/Logic.h index 3ebe0cf2f4..815858cf00 100644 --- a/src/xrpld/peerfinder/detail/Logic.h +++ b/src/xrpld/peerfinder/detail/Logic.h @@ -148,13 +148,13 @@ public: } void - addFixedPeer(std::string const& name, beast::IP::Endpoint const& ep) + addFixedPeer(std::string_view name, beast::IP::Endpoint const& ep) { addFixedPeer(name, std::vector{ep}); } void - addFixedPeer(std::string const& name, std::vector const& addresses) + addFixedPeer(std::string_view name, std::vector const& addresses) { std::scoped_lock const _(lock); diff --git a/src/xrpld/peerfinder/detail/PeerfinderManager.cpp b/src/xrpld/peerfinder/detail/PeerfinderManager.cpp index 873c18aad9..f51f3630eb 100644 --- a/src/xrpld/peerfinder/detail/PeerfinderManager.cpp +++ b/src/xrpld/peerfinder/detail/PeerfinderManager.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -99,8 +100,7 @@ public: } void - addFixedPeer(std::string const& name, std::vector const& addresses) - override + addFixedPeer(std::string_view name, std::vector const& addresses) override { logic_.addFixedPeer(name, addresses); } diff --git a/src/xrpld/rpc/detail/AssetCache.cpp b/src/xrpld/rpc/detail/AssetCache.cpp index 0976290d4c..23cc31252d 100644 --- a/src/xrpld/rpc/detail/AssetCache.cpp +++ b/src/xrpld/rpc/detail/AssetCache.cpp @@ -22,8 +22,8 @@ namespace xrpl { -AssetCache::AssetCache(std::shared_ptr const& ledger, beast::Journal j) - : ledger_(ledger), journal_(j) +AssetCache::AssetCache(std::shared_ptr ledger, beast::Journal j) + : ledger_(std::move(ledger)), journal_(j) { JLOG(journal_.debug()) << "created for ledger " << ledger_->header().seq; } diff --git a/src/xrpld/rpc/detail/AssetCache.h b/src/xrpld/rpc/detail/AssetCache.h index dd53620cdf..e53bc3ff94 100644 --- a/src/xrpld/rpc/detail/AssetCache.h +++ b/src/xrpld/rpc/detail/AssetCache.h @@ -17,7 +17,7 @@ namespace xrpl { class AssetCache final : public CountedObject { public: - explicit AssetCache(std::shared_ptr const& l, beast::Journal j); + explicit AssetCache(std::shared_ptr l, beast::Journal j); ~AssetCache(); [[nodiscard]] std::shared_ptr const& diff --git a/src/xrpld/rpc/detail/PathRequest.cpp b/src/xrpld/rpc/detail/PathRequest.cpp index 06507319f7..3c09917dad 100644 --- a/src/xrpld/rpc/detail/PathRequest.cpp +++ b/src/xrpld/rpc/detail/PathRequest.cpp @@ -72,7 +72,7 @@ PathRequest::PathRequest( PathRequest::PathRequest( Application& app, - std::function const& completion, + std::function completion, Resource::Consumer& consumer, int id, PathRequestManager& owner, @@ -80,7 +80,7 @@ PathRequest::PathRequest( : app_(app) , journal_(journal) , owner_(owner) - , fCompletion_(completion) + , fCompletion_(std::move(completion)) , consumer_(consumer) , jvStatus_(json::ValueType::Object) , lastIndex_(0) diff --git a/src/xrpld/rpc/detail/PathRequest.h b/src/xrpld/rpc/detail/PathRequest.h index de8c10de0e..372223e99f 100644 --- a/src/xrpld/rpc/detail/PathRequest.h +++ b/src/xrpld/rpc/detail/PathRequest.h @@ -51,7 +51,7 @@ public: // Completion function is called after path update is complete PathRequest( Application& app, - std::function const& completion, + std::function completion, Resource::Consumer& consumer, int id, PathRequestManager&, From c552eb333f5625aaee67698af2e6771ba729d23c Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 9 Jun 2026 10:58:21 -0400 Subject: [PATCH 073/135] refactor: Change config section and key string literals into constants (#7095) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- .../scripts/levelization/results/ordering.txt | 22 ++ cmake/XrplCore.cmake | 14 +- include/xrpl/{basics => config}/BasicConfig.h | 0 include/xrpl/config/Constants.h | 180 ++++++++++ include/xrpl/core/PerfLog.h | 2 +- include/xrpl/nodestore/Database.h | 5 +- include/xrpl/nodestore/Factory.h | 5 +- .../xrpl/nodestore/detail/DatabaseNodeImp.h | 10 +- include/xrpl/server/Port.h | 4 +- .../{basics => config}/BasicConfig.cpp | 2 +- src/libxrpl/nodestore/Database.cpp | 7 +- src/libxrpl/nodestore/DatabaseRotatingImp.cpp | 2 +- src/libxrpl/nodestore/ManagerImp.cpp | 5 +- .../nodestore/backend/MemoryFactory.cpp | 5 +- src/libxrpl/nodestore/backend/NuDBFactory.cpp | 9 +- src/libxrpl/nodestore/backend/NullFactory.cpp | 2 +- .../nodestore/backend/RocksDBFactory.cpp | 49 +-- src/libxrpl/rdb/SociDB.cpp | 9 +- src/libxrpl/server/Port.cpp | 48 +-- src/libxrpl/server/State.cpp | 2 +- src/test/app/AmendmentTable_test.cpp | 8 +- src/test/app/Batch_test.cpp | 20 +- src/test/app/FeeVote_test.cpp | 2 +- src/test/app/GRPCServerTLS_test.cpp | 141 ++++---- src/test/app/HashRouter_test.cpp | 37 +- src/test/app/Manifest_test.cpp | 3 +- src/test/app/MultiSign_test.cpp | 6 +- src/test/app/Regression_test.cpp | 13 +- src/test/app/SHAMapStore_test.cpp | 23 +- src/test/app/TxQ_test.cpp | 152 ++++---- src/test/app/ValidatorKeys_test.cpp | 16 +- src/test/app/ValidatorList_test.cpp | 53 +-- src/test/core/Config_test.cpp | 182 +++++----- src/test/core/SociDB_test.cpp | 7 +- src/test/jtx/Env_test.cpp | 3 +- src/test/jtx/envconfig.h | 5 - src/test/jtx/impl/JSONRPCClient.cpp | 7 +- src/test/jtx/impl/WSClient.cpp | 7 +- src/test/jtx/impl/envconfig.cpp | 119 +++---- src/test/nodestore/Backend_test.cpp | 7 +- src/test/nodestore/Database_test.cpp | 106 +++--- src/test/nodestore/NuDBFactory_test.cpp | 9 +- src/test/nodestore/Timing_test.cpp | 8 +- src/test/overlay/cluster_test.cpp | 2 +- src/test/rpc/AmendmentBlocked_test.cpp | 5 +- src/test/rpc/Feature_test.cpp | 5 +- src/test/rpc/JSONRPC_test.cpp | 7 +- src/test/rpc/LedgerRPC_test.cpp | 7 +- src/test/rpc/ManifestRPC_test.cpp | 4 +- src/test/rpc/RPCOverload_test.cpp | 5 +- src/test/rpc/ServerInfo_test.cpp | 8 +- src/test/rpc/Simulate_test.cpp | 3 +- src/test/rpc/Subscribe_test.cpp | 7 +- src/test/rpc/ValidatorInfo_test.cpp | 4 +- src/test/rpc/ValidatorRPC_test.cpp | 20 +- src/test/server/ServerStatus_test.cpp | 128 +++---- src/test/server/Server_test.cpp | 68 ++-- src/test/shamap/common.h | 6 +- src/tests/libxrpl/helpers/TestFamily.h | 6 +- src/xrpld/app/main/Application.cpp | 41 +-- src/xrpld/app/main/CollectorManager.cpp | 9 +- src/xrpld/app/main/CollectorManager.h | 2 +- src/xrpld/app/main/GRPCServer.cpp | 22 +- src/xrpld/app/main/Main.cpp | 6 +- src/xrpld/app/main/NodeIdentity.cpp | 11 +- src/xrpld/app/misc/NetworkOPs.cpp | 13 +- src/xrpld/app/misc/SHAMapStoreImp.cpp | 57 +-- src/xrpld/app/misc/ValidatorList.h | 4 +- src/xrpld/app/misc/detail/AmendmentTable.cpp | 2 +- src/xrpld/app/misc/detail/TxQ.cpp | 29 +- src/xrpld/app/misc/detail/ValidatorKeys.cpp | 23 +- .../app/misc/detail/setup_HashRouter.cpp | 9 +- src/xrpld/app/rdb/backend/detail/Node.cpp | 3 +- src/xrpld/app/rdb/detail/PeerFinder.cpp | 2 +- src/xrpld/core/Config.h | 2 +- src/xrpld/core/ConfigSections.h | 80 ----- src/xrpld/core/detail/Config.cpp | 329 ++++++++++-------- src/xrpld/overlay/Cluster.h | 2 +- src/xrpld/overlay/detail/Cluster.cpp | 2 +- src/xrpld/overlay/detail/OverlayImpl.cpp | 25 +- .../peerfinder/detail/PeerfinderManager.cpp | 2 +- src/xrpld/perflog/detail/PerfLogImp.cpp | 5 +- src/xrpld/rpc/detail/ServerHandler.cpp | 10 +- 83 files changed, 1262 insertions(+), 1029 deletions(-) rename include/xrpl/{basics => config}/BasicConfig.h (100%) create mode 100644 include/xrpl/config/Constants.h rename src/libxrpl/{basics => config}/BasicConfig.cpp (99%) delete mode 100644 src/xrpld/core/ConfigSections.h diff --git a/.github/scripts/levelization/results/ordering.txt b/.github/scripts/levelization/results/ordering.txt index c2000d1768..12176ec0d4 100644 --- a/.github/scripts/levelization/results/ordering.txt +++ b/.github/scripts/levelization/results/ordering.txt @@ -1,6 +1,8 @@ libxrpl.basics > xrpl.basics libxrpl.conditions > xrpl.basics libxrpl.conditions > xrpl.conditions +libxrpl.config > xrpl.basics +libxrpl.config > xrpl.config libxrpl.core > xrpl.basics libxrpl.core > xrpl.core libxrpl.core > xrpl.json @@ -17,6 +19,7 @@ libxrpl.ledger > xrpl.shamap libxrpl.net > xrpl.basics libxrpl.net > xrpl.net libxrpl.nodestore > xrpl.basics +libxrpl.nodestore > xrpl.config libxrpl.nodestore > xrpl.json libxrpl.nodestore > xrpl.nodestore libxrpl.nodestore > xrpl.protocol @@ -24,6 +27,7 @@ libxrpl.protocol > xrpl.basics libxrpl.protocol > xrpl.json libxrpl.protocol > xrpl.protocol libxrpl.rdb > xrpl.basics +libxrpl.rdb > xrpl.config libxrpl.rdb > xrpl.core libxrpl.rdb > xrpl.rdb libxrpl.resource > xrpl.basics @@ -31,6 +35,7 @@ libxrpl.resource > xrpl.json libxrpl.resource > xrpl.protocol libxrpl.resource > xrpl.resource libxrpl.server > xrpl.basics +libxrpl.server > xrpl.config libxrpl.server > xrpl.core libxrpl.server > xrpl.json libxrpl.server > xrpl.protocol @@ -52,6 +57,7 @@ libxrpl.tx > xrpl.tx test.app > test.jtx test.app > test.unit_test test.app > xrpl.basics +test.app > xrpl.config test.app > xrpl.core test.app > xrpld.app test.app > xrpld.consensus @@ -90,6 +96,7 @@ test.consensus > xrpl.tx test.core > test.jtx test.core > test.unit_test test.core > xrpl.basics +test.core > xrpl.config test.core > xrpl.core test.core > xrpld.core test.core > xrpl.json @@ -104,6 +111,7 @@ test.csf > xrpl.protocol test.json > test.jtx test.json > xrpl.json test.jtx > xrpl.basics +test.jtx > xrpl.config test.jtx > xrpl.core test.jtx > xrpld.app test.jtx > xrpld.core @@ -126,6 +134,7 @@ test.ledger > xrpl.protocol test.nodestore > test.jtx test.nodestore > test.unit_test test.nodestore > xrpl.basics +test.nodestore > xrpl.config test.nodestore > xrpld.core test.nodestore > xrpl.nodestore test.nodestore > xrpl.protocol @@ -133,6 +142,7 @@ test.nodestore > xrpl.rdb test.overlay > test.jtx test.overlay > test.unit_test test.overlay > xrpl.basics +test.overlay > xrpl.config test.overlay > xrpld.app test.overlay > xrpld.core test.overlay > xrpld.overlay @@ -159,6 +169,7 @@ test.resource > xrpl.basics test.resource > xrpl.resource test.rpc > test.jtx test.rpc > xrpl.basics +test.rpc > xrpl.config test.rpc > xrpl.core test.rpc > xrpld.app test.rpc > xrpld.core @@ -173,6 +184,7 @@ test.rpc > xrpl.tx test.server > test.jtx test.server > test.unit_test test.server > xrpl.basics +test.server > xrpl.config test.server > xrpld.app test.server > xrpld.core test.server > xrpl.json @@ -180,6 +192,7 @@ test.server > xrpl.protocol test.server > xrpl.server test.shamap > test.unit_test test.shamap > xrpl.basics +test.shamap > xrpl.config test.shamap > xrpl.nodestore test.shamap > xrpl.protocol test.shamap > xrpl.shamap @@ -188,6 +201,7 @@ test.toplevel > xrpl.json test.unit_test > xrpl.basics test.unit_test > xrpl.protocol tests.libxrpl > xrpl.basics +tests.libxrpl > xrpl.config tests.libxrpl > xrpl.core tests.libxrpl > xrpl.json tests.libxrpl > xrpl.ledger @@ -200,6 +214,7 @@ tests.libxrpl > xrpl.shamap tests.libxrpl > xrpl.tx xrpl.conditions > xrpl.basics xrpl.conditions > xrpl.protocol +xrpl.config > xrpl.basics xrpl.core > xrpl.basics xrpl.core > xrpl.json xrpl.core > xrpl.protocol @@ -210,6 +225,7 @@ xrpl.ledger > xrpl.server xrpl.ledger > xrpl.shamap xrpl.net > xrpl.basics xrpl.nodestore > xrpl.basics +xrpl.nodestore > xrpl.config xrpl.nodestore > xrpl.protocol xrpl.protocol > xrpl.basics xrpl.protocol > xrpl.json @@ -237,6 +253,7 @@ xrpl.tx > xrpl.ledger xrpl.tx > xrpl.protocol xrpld.app > test.unit_test xrpld.app > xrpl.basics +xrpld.app > xrpl.config xrpld.app > xrpl.core xrpld.app > xrpld.consensus xrpld.app > xrpld.core @@ -255,11 +272,13 @@ xrpld.consensus > xrpl.json xrpld.consensus > xrpl.ledger xrpld.consensus > xrpl.protocol xrpld.core > xrpl.basics +xrpld.core > xrpl.config xrpld.core > xrpl.core xrpld.core > xrpl.net xrpld.core > xrpl.protocol xrpld.core > xrpl.rdb xrpld.overlay > xrpl.basics +xrpld.overlay > xrpl.config xrpld.overlay > xrpl.core xrpld.overlay > xrpld.consensus xrpld.overlay > xrpld.core @@ -272,15 +291,18 @@ xrpld.overlay > xrpl.server xrpld.overlay > xrpl.shamap xrpld.overlay > xrpl.tx xrpld.peerfinder > xrpl.basics +xrpld.peerfinder > xrpl.config xrpld.peerfinder > xrpld.core xrpld.peerfinder > xrpl.protocol xrpld.peerfinder > xrpl.rdb xrpld.perflog > xrpl.basics +xrpld.perflog > xrpl.config xrpld.perflog > xrpl.core xrpld.perflog > xrpld.rpc xrpld.perflog > xrpl.json xrpld.perflog > xrpl.protocol xrpld.rpc > xrpl.basics +xrpld.rpc > xrpl.config xrpld.rpc > xrpl.core xrpld.rpc > xrpld.core xrpld.rpc > xrpl.json diff --git a/cmake/XrplCore.cmake b/cmake/XrplCore.cmake index 9b1dc74049..52d7714a99 100644 --- a/cmake/XrplCore.cmake +++ b/cmake/XrplCore.cmake @@ -94,6 +94,9 @@ add_module(xrpl basics) target_link_libraries(xrpl.libxrpl.basics PUBLIC xrpl.libxrpl.beast) # Level 03 +add_module(xrpl config) +target_link_libraries(xrpl.libxrpl.config PUBLIC xrpl.libxrpl.basics) + add_module(xrpl json) target_link_libraries(xrpl.libxrpl.json PUBLIC xrpl.libxrpl.basics) @@ -120,6 +123,7 @@ target_link_libraries( xrpl.libxrpl.core PUBLIC xrpl.libxrpl.basics + xrpl.libxrpl.config xrpl.libxrpl.json xrpl.libxrpl.protocol xrpl.libxrpl.protocol_autogen @@ -143,7 +147,11 @@ target_link_libraries( add_module(xrpl nodestore) target_link_libraries( xrpl.libxrpl.nodestore - PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json xrpl.libxrpl.protocol + PUBLIC + xrpl.libxrpl.basics + xrpl.libxrpl.config + xrpl.libxrpl.json + xrpl.libxrpl.protocol ) add_module(xrpl shamap) @@ -159,13 +167,14 @@ target_link_libraries( add_module(xrpl rdb) target_link_libraries( xrpl.libxrpl.rdb - PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.core + PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.config xrpl.libxrpl.core ) add_module(xrpl server) target_link_libraries( xrpl.libxrpl.server PUBLIC + xrpl.libxrpl.config xrpl.libxrpl.protocol xrpl.libxrpl.core xrpl.libxrpl.rdb @@ -210,6 +219,7 @@ target_link_modules( basics beast conditions + config core crypto git diff --git a/include/xrpl/basics/BasicConfig.h b/include/xrpl/config/BasicConfig.h similarity index 100% rename from include/xrpl/basics/BasicConfig.h rename to include/xrpl/config/BasicConfig.h diff --git a/include/xrpl/config/Constants.h b/include/xrpl/config/Constants.h new file mode 100644 index 0000000000..5514e0e77b --- /dev/null +++ b/include/xrpl/config/Constants.h @@ -0,0 +1,180 @@ +#pragma once + +namespace xrpl { + +struct Sections +{ + static constexpr auto kAmendments = "amendments"; + static constexpr auto kAmendmentMajorityTime = "amendment_majority_time"; + static constexpr auto kBetaRpcApi = "beta_rpc_api"; + static constexpr auto kClusterNodes = "cluster_nodes"; + static constexpr auto kCompression = "compression"; + static constexpr auto kCrawl = "crawl"; + static constexpr auto kDatabasePath = "database_path"; + static constexpr auto kDebugLogfile = "debug_logfile"; + static constexpr auto kElbSupport = "elb_support"; + static constexpr auto kFeatures = "features"; + static constexpr auto kFeeDefault = "fee_default"; + static constexpr auto kFetchDepth = "fetch_depth"; + static constexpr auto kHashrouter = "hashrouter"; + static constexpr auto kImportNodeDatabase = "import_db"; + static constexpr auto kInsight = "insight"; + static constexpr auto kIoWorkers = "io_workers"; + static constexpr auto kIps = "ips"; + static constexpr auto kIpsFixed = "ips_fixed"; + static constexpr auto kLedgerHistory = "ledger_history"; + static constexpr auto kLedgerReplay = "ledger_replay"; + static constexpr auto kLedgerTxTables = "ledger_tx_tables"; + static constexpr auto kMaxTransactions = "max_transactions"; + static constexpr auto kNetworkId = "network_id"; + static constexpr auto kNetworkQuorum = "network_quorum"; + static constexpr auto kNodeDatabase = "node_db"; + static constexpr auto kNodeSeed = "node_seed"; + static constexpr auto kNodeSize = "node_size"; + static constexpr auto kOverlay = "overlay"; + static constexpr auto kPathSearch = "path_search"; + static constexpr auto kPathSearchFast = "path_search_fast"; + static constexpr auto kPathSearchMax = "path_search_max"; + static constexpr auto kPathSearchOld = "path_search_old"; + static constexpr auto kPeerPrivate = "peer_private"; + static constexpr auto kPeersInMax = "peers_in_max"; + static constexpr auto kPeersMax = "peers_max"; + static constexpr auto kPeersOutMax = "peers_out_max"; + static constexpr auto kPerf = "perf"; + static constexpr auto kPortGrpc = "port_grpc"; + static constexpr auto kPortPeer = "port_peer"; + static constexpr auto kPortRpc = "port_rpc"; + static constexpr auto kPortWs = "port_ws"; + static constexpr auto kPortWssAdmin = "port_wss_admin"; + static constexpr auto kPrefetchWorkers = "prefetch_workers"; + static constexpr auto kReduceRelay = "reduce_relay"; + static constexpr auto kRelationalDb = "relational_db"; + static constexpr auto kRelayProposals = "relay_proposals"; + static constexpr auto kRelayValidations = "relay_validations"; + static constexpr auto kRpcStartup = "rpc_startup"; + static constexpr auto kServer = "server"; + static constexpr auto kServerDomain = "server_domain"; + static constexpr auto kSigningSupport = "signing_support"; + static constexpr auto kSntp = "sntp_servers"; + static constexpr auto kSqdb = "sqdb"; + static constexpr auto kSqlite = "sqlite"; + static constexpr auto kSslVerify = "ssl_verify"; + static constexpr auto kSslVerifyDir = "ssl_verify_dir"; + static constexpr auto kSslVerifyFile = "ssl_verify_file"; + static constexpr auto kSweepInterval = "sweep_interval"; + static constexpr auto kTransactionQueue = "transaction_queue"; + static constexpr auto kValidationSeed = "validation_seed"; + static constexpr auto kValidatorKeys = "validator_keys"; + static constexpr auto kValidatorKeyRevocation = "validator_key_revocation"; + static constexpr auto kValidatorListKeys = "validator_list_keys"; + static constexpr auto kValidatorListSites = "validator_list_sites"; + static constexpr auto kValidatorListThreshold = "validator_list_threshold"; + static constexpr auto kValidatorToken = "validator_token"; + static constexpr auto kValidators = "validators"; + static constexpr auto kValidatorsFile = "validators_file"; + static constexpr auto kVetoAmendments = "veto_amendments"; + static constexpr auto kVl = "vl"; + static constexpr auto kVoting = "voting"; + static constexpr auto kWorkers = "workers"; +}; + +struct Keys +{ + static constexpr auto kAccountReserve = "account_reserve"; + static constexpr auto kAddress = "address"; + static constexpr auto kAdmin = "admin"; + static constexpr auto kAdminPassword = "admin_password"; + static constexpr auto kAdminUser = "admin_user"; + static constexpr auto kAdvisoryDelete = "advisory_delete"; + static constexpr auto kAgeThresholdSeconds = "age_threshold_seconds"; + static constexpr auto kBackOff = "backOff"; + static constexpr auto kBackOffMilliseconds = "back_off_milliseconds"; + static constexpr auto kBackend = "backend"; + static constexpr auto kBbtOptions = "bbt_options"; + static constexpr auto kBgThreads = "bg_threads"; + static constexpr auto kBlockSize = "block_size"; + static constexpr auto kCacheAge = "cache_age"; + static constexpr auto kCacheMb = "cache_mb"; + static constexpr auto kCacheSize = "cache_size"; + static constexpr auto kClientMaxWindowBits = "client_max_window_bits"; + static constexpr auto kClientNoContextTakeover = "client_no_context_takeover"; + static constexpr auto kCompressLevel = "compress_level"; + static constexpr auto kCounts = "counts"; + static constexpr auto kDeleteBatch = "delete_batch"; + static constexpr auto kEarliestSeq = "earliest_seq"; + static constexpr auto kFastLoad = "fast_load"; + static constexpr auto kFileSizeMb = "file_size_mb"; + static constexpr auto kFileSizeMult = "file_size_mult"; + static constexpr auto kFilterBits = "filter_bits"; + static constexpr auto kFilterFull = "filter_full"; + static constexpr auto kHardSet = "hard_set"; + static constexpr auto kHighThreads = "high_threads"; + static constexpr auto kHoldTime = "hold_time"; + static constexpr auto kIp = "ip"; + static constexpr auto kJournalMode = "journal_mode"; + static constexpr auto kJournalSizeLimit = "journal_size_limit"; + static constexpr auto kLedgersInQueue = "ledgers_in_queue"; + static constexpr auto kLimit = "limit"; + static constexpr auto kLogInterval = "log_interval"; + static constexpr auto kMaxDivergedTime = "max_diverged_time"; + static constexpr auto kMaxLedgerCountsToStore = "max_ledger_counts_to_store"; + static constexpr auto kMaxUnknownTime = "max_unknown_time"; + static constexpr auto kMaximumTxnInLedger = "maximum_txn_in_ledger"; + static constexpr auto kMaximumTxnPerAccount = "maximum_txn_per_account"; + static constexpr auto kMemoryLevel = "memory_level"; + static constexpr auto kMinLedgersToComputeSizeLimit = "min_ledgers_to_compute_size_limit"; + static constexpr auto kMinimumEscalationMultiplier = "minimum_escalation_multiplier"; + static constexpr auto kMinimumLastLedgerBuffer = "minimum_last_ledger_buffer"; + static constexpr auto kMinimumQueueSize = "minimum_queue_size"; + static constexpr auto kMinimumTxnInLedger = "minimum_txn_in_ledger"; + static constexpr auto kMinimumTxnInLedgerStandalone = "minimum_txn_in_ledger_standalone"; + static constexpr auto kNormalConsensusIncreasePercent = "normal_consensus_increase_percent"; + static constexpr auto kNudbBlockSize = "nudb_block_size"; + static constexpr auto kOnlineDelete = "online_delete"; + static constexpr auto kOpenFiles = "open_files"; + static constexpr auto kOptions = "options"; + static constexpr auto kOverlay = "overlay"; + static constexpr auto kOwnerReserve = "owner_reserve"; + static constexpr auto kPageSize = "page_size"; + static constexpr auto kPassword = "password"; + static constexpr auto kPath = "path"; + static constexpr auto kPermessageDeflate = "permessage_deflate"; + static constexpr auto kPort = "port"; + static constexpr auto kPrefix = "prefix"; + static constexpr auto kProtocol = "protocol"; + static constexpr auto kRecoveryWaitSeconds = "recovery_wait_seconds"; + static constexpr auto kReferenceFee = "reference_fee"; + static constexpr auto kRelayTime = "relay_time"; + static constexpr auto kRetrySequencePercent = "retry_sequence_percent"; + static constexpr auto kRqBundle = "rq_bundle"; + static constexpr auto kSafetyLevel = "safety_level"; + static constexpr auto kSecureGateway = "secure_gateway"; + static constexpr auto kSendQueueLimit = "send_queue_limit"; + static constexpr auto kServer = "server"; + static constexpr auto kServerMaxWindowBits = "server_max_window_bits"; + static constexpr auto kServerNoContextTakeover = "server_no_context_takeover"; + static constexpr auto kSlowConsensusDecreasePercent = "slow_consensus_decrease_percent"; + static constexpr auto kSslCert = "ssl_cert"; + static constexpr auto kSslCertChain = "ssl_cert_chain"; + static constexpr auto kSslChain = "ssl_chain"; + static constexpr auto kSslCiphers = "ssl_ciphers"; + static constexpr auto kSslClientCa = "ssl_client_ca"; + static constexpr auto kSslKey = "ssl_key"; + static constexpr auto kSynchronous = "synchronous"; + static constexpr auto kTargetTxnInLedger = "target_txn_in_ledger"; + static constexpr auto kTempStore = "temp_store"; + static constexpr auto kTxEnable = "tx_enable"; + static constexpr auto kTxMetrics = "tx_metrics"; + static constexpr auto kTxMinPeers = "tx_min_peers"; + static constexpr auto kTxRelayPercentage = "tx_relay_percentage"; + static constexpr auto kType = "type"; + static constexpr auto kUniversalCompaction = "universal_compaction"; + static constexpr auto kUnl = "unl"; + static constexpr auto kUseTxTables = "use_tx_tables"; + static constexpr auto kUser = "user"; + static constexpr auto kVpBaseSquelchEnable = "vp_base_squelch_enable"; + static constexpr auto kVpBaseSquelchMaxSelectedPeers = "vp_base_squelch_max_selected_peers"; + static constexpr auto kVpEnable = "vp_enable"; +}; + +} // namespace xrpl diff --git a/include/xrpl/core/PerfLog.h b/include/xrpl/core/PerfLog.h index 38318c745d..ca0d9333a4 100644 --- a/include/xrpl/core/PerfLog.h +++ b/include/xrpl/core/PerfLog.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -18,6 +17,7 @@ class Journal; namespace xrpl { class Application; +class Section; namespace perf { /** diff --git a/include/xrpl/nodestore/Database.h b/include/xrpl/nodestore/Database.h index 438a3cc7fc..68c5dcefb6 100644 --- a/include/xrpl/nodestore/Database.h +++ b/include/xrpl/nodestore/Database.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -10,6 +9,10 @@ #include +namespace xrpl { +class Section; +} // namespace xrpl + namespace xrpl::NodeStore { /** Persistency layer for NodeObject diff --git a/include/xrpl/nodestore/Factory.h b/include/xrpl/nodestore/Factory.h index c40be62d21..3e6ba76a08 100644 --- a/include/xrpl/nodestore/Factory.h +++ b/include/xrpl/nodestore/Factory.h @@ -1,12 +1,15 @@ #pragma once -#include #include #include #include #include +namespace xrpl { +class Section; +} // namespace xrpl + namespace xrpl::NodeStore { /** Base class for backend factories. */ diff --git a/include/xrpl/nodestore/detail/DatabaseNodeImp.h b/include/xrpl/nodestore/detail/DatabaseNodeImp.h index 951c60c8c7..38b8763f31 100644 --- a/include/xrpl/nodestore/detail/DatabaseNodeImp.h +++ b/include/xrpl/nodestore/detail/DatabaseNodeImp.h @@ -2,6 +2,8 @@ #include #include +#include +#include #include namespace xrpl::NodeStore { @@ -24,16 +26,16 @@ public: { std::optional cacheSize, cacheAge; - if (config.exists("cache_size")) + if (config.exists(Keys::kCacheSize)) { - cacheSize = get(config, "cache_size"); + cacheSize = get(config, Keys::kCacheSize); if (cacheSize.value() < 0) Throw("Specified negative value for cache_size"); } - if (config.exists("cache_age")) + if (config.exists(Keys::kCacheAge)) { - cacheAge = get(config, "cache_age"); + cacheAge = get(config, Keys::kCacheAge); if (cacheAge.value() < 0) Throw("Specified negative value for cache_age"); } diff --git a/include/xrpl/server/Port.h b/include/xrpl/server/Port.h index ac9b855cf0..fd773c78fc 100644 --- a/include/xrpl/server/Port.h +++ b/include/xrpl/server/Port.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -14,6 +13,7 @@ #include #include #include +#include namespace boost::asio::ssl { class context; // NOLINT(readability-identifier-naming) -- external library name @@ -21,6 +21,8 @@ class context; // NOLINT(readability-identifier-naming) -- external library nam namespace xrpl { +class Section; + /** Configuration information for a Server listening port. */ struct Port { diff --git a/src/libxrpl/basics/BasicConfig.cpp b/src/libxrpl/config/BasicConfig.cpp similarity index 99% rename from src/libxrpl/basics/BasicConfig.cpp rename to src/libxrpl/config/BasicConfig.cpp index 9fe79bdf4e..6b94473392 100644 --- a/src/libxrpl/basics/BasicConfig.cpp +++ b/src/libxrpl/config/BasicConfig.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/src/libxrpl/nodestore/Database.cpp b/src/libxrpl/nodestore/Database.cpp index b584aca268..ac51dbfb2c 100644 --- a/src/libxrpl/nodestore/Database.cpp +++ b/src/libxrpl/nodestore/Database.cpp @@ -1,12 +1,13 @@ #include -#include #include #include #include #include #include #include +#include +#include #include #include #include @@ -38,8 +39,8 @@ Database::Database( beast::Journal journal) : j_(journal) , scheduler_(scheduler) - , earliestLedgerSeq_(get(config, "earliest_seq", kXrpLedgerEarliestSeq)) - , requestBundle_(get(config, "rq_bundle", 4)) + , earliestLedgerSeq_(get(config, Keys::kEarliestSeq, kXrpLedgerEarliestSeq)) + , requestBundle_(get(config, Keys::kRqBundle, 4)) , readThreads_(std::max(1, readThreads)) { XRPL_ASSERT(readThreads, "xrpl::NodeStore::Database::Database : nonzero threads input"); diff --git a/src/libxrpl/nodestore/DatabaseRotatingImp.cpp b/src/libxrpl/nodestore/DatabaseRotatingImp.cpp index 1de6a83b4c..7f4dca3ed1 100644 --- a/src/libxrpl/nodestore/DatabaseRotatingImp.cpp +++ b/src/libxrpl/nodestore/DatabaseRotatingImp.cpp @@ -1,11 +1,11 @@ #include -#include #include #include #include #include #include +#include #include #include #include diff --git a/src/libxrpl/nodestore/ManagerImp.cpp b/src/libxrpl/nodestore/ManagerImp.cpp index 5cefbfd357..5f366079f1 100644 --- a/src/libxrpl/nodestore/ManagerImp.cpp +++ b/src/libxrpl/nodestore/ManagerImp.cpp @@ -1,9 +1,10 @@ #include -#include #include #include #include +#include +#include #include #include #include @@ -66,7 +67,7 @@ ManagerImp::makeBackend( Scheduler& scheduler, beast::Journal journal) { - std::string const type{get(parameters, "type")}; + std::string const type{get(parameters, Keys::kType)}; if (type.empty()) missingBackend(); diff --git a/src/libxrpl/nodestore/backend/MemoryFactory.cpp b/src/libxrpl/nodestore/backend/MemoryFactory.cpp index 5bdf8e65b5..70578c8613 100644 --- a/src/libxrpl/nodestore/backend/MemoryFactory.cpp +++ b/src/libxrpl/nodestore/backend/MemoryFactory.cpp @@ -1,8 +1,9 @@ -#include #include #include #include #include +#include +#include #include #include #include @@ -90,7 +91,7 @@ private: public: MemoryBackend(size_t keyBytes, Section const& keyValues, beast::Journal journal) - : name_(get(keyValues, "path")), journal_(journal) + : name_(get(keyValues, Keys::kPath)), journal_(journal) { boost::ignore_unused(journal_); // Keep unused journal_ just in case. if (name_.empty()) diff --git a/src/libxrpl/nodestore/backend/NuDBFactory.cpp b/src/libxrpl/nodestore/backend/NuDBFactory.cpp index abf09e871d..749d4020b5 100644 --- a/src/libxrpl/nodestore/backend/NuDBFactory.cpp +++ b/src/libxrpl/nodestore/backend/NuDBFactory.cpp @@ -1,10 +1,11 @@ -#include #include #include #include #include #include #include +#include +#include #include #include #include @@ -72,7 +73,7 @@ public: : j(journal) , keyBytes(keyBytes) , burstSize(burstSize) - , name(get(keyValues, "path")) + , name(get(keyValues, Keys::kPath)) , blockSize(parseBlockSize(name, keyValues, journal)) , deletePath(false) , scheduler(scheduler) @@ -91,7 +92,7 @@ public: : j(journal) , keyBytes(keyBytes) , burstSize(burstSize) - , name(get(keyValues, "path")) + , name(get(keyValues, Keys::kPath)) , blockSize(parseBlockSize(name, keyValues, journal)) , db(context) , deletePath(false) @@ -359,7 +360,7 @@ private: std::size_t const blockSize = defaultSize; std::string blockSizeStr; - if (!getIfExists(keyValues, "nudb_block_size", blockSizeStr)) + if (!getIfExists(keyValues, Keys::kNudbBlockSize, blockSizeStr)) { return blockSize; // Early return with default } diff --git a/src/libxrpl/nodestore/backend/NullFactory.cpp b/src/libxrpl/nodestore/backend/NullFactory.cpp index 36b8139984..e36b13a2e1 100644 --- a/src/libxrpl/nodestore/backend/NullFactory.cpp +++ b/src/libxrpl/nodestore/backend/NullFactory.cpp @@ -1,6 +1,6 @@ -#include #include #include +#include #include #include #include diff --git a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp index d2c193888c..252ff32ccf 100644 --- a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp +++ b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp @@ -1,8 +1,9 @@ -#include #include #include #include #include +#include +#include #include #include #include @@ -111,17 +112,18 @@ public: RocksDBEnv* env) : deletePath_(false), journal(journal), keyBytes(keyBytes), batch(*this, scheduler) { - if (!getIfExists(keyValues, "path", name)) + if (!getIfExists(keyValues, Keys::kPath, name)) Throw("Missing path in RocksDBFactory backend"); rocksdb::BlockBasedTableOptions tableOptions; options.env = env; - bool const hardSet = keyValues.exists("hard_set") && get(keyValues, "hard_set"); + bool const hardSet = + keyValues.exists(Keys::kHardSet) && get(keyValues, Keys::kHardSet); - if (keyValues.exists("cache_mb")) + if (keyValues.exists(Keys::kCacheMb)) { - auto size = get(keyValues, "cache_mb"); + auto size = get(keyValues, Keys::kCacheMb); if (!hardSet && size == 256) size = 1024; @@ -129,14 +131,14 @@ public: tableOptions.block_cache = rocksdb::NewLRUCache(megabytes(size)); } - if (auto const v = get(keyValues, "filter_bits")) + if (auto const v = get(keyValues, Keys::kFilterBits)) { - bool const filterBlocks = - !keyValues.exists("filter_full") || (get(keyValues, "filter_full") == 0); + bool const filterBlocks = !keyValues.exists(Keys::kFilterFull) || + (get(keyValues, Keys::kFilterFull) == 0); tableOptions.filter_policy.reset(rocksdb::NewBloomFilterPolicy(v, filterBlocks)); } - if (getIfExists(keyValues, "open_files", options.max_open_files)) + if (getIfExists(keyValues, Keys::kOpenFiles, options.max_open_files)) { if (!hardSet && options.max_open_files == 2000) options.max_open_files = 8000; @@ -144,9 +146,9 @@ public: fdMinRequired = options.max_open_files + 128; } - if (keyValues.exists("file_size_mb")) + if (keyValues.exists(Keys::kFileSizeMb)) { - auto fileSizeMb = get(keyValues, "file_size_mb"); + auto fileSizeMb = get(keyValues, Keys::kFileSizeMb); if (!hardSet && fileSizeMb == 8) fileSizeMb = 256; @@ -156,16 +158,17 @@ public: options.write_buffer_size = 2 * options.target_file_size_base; } - getIfExists(keyValues, "file_size_mult", options.target_file_size_multiplier); + getIfExists(keyValues, Keys::kFileSizeMult, options.target_file_size_multiplier); - if (keyValues.exists("bg_threads")) + if (keyValues.exists(Keys::kBgThreads)) { - options.env->SetBackgroundThreads(get(keyValues, "bg_threads"), rocksdb::Env::LOW); + options.env->SetBackgroundThreads( + get(keyValues, Keys::kBgThreads), rocksdb::Env::LOW); } - if (keyValues.exists("high_threads")) + if (keyValues.exists(Keys::kHighThreads)) { - auto const highThreads = get(keyValues, "high_threads"); + auto const highThreads = get(keyValues, Keys::kHighThreads); options.env->SetBackgroundThreads(highThreads, rocksdb::Env::HIGH); // If we have high-priority threads, presumably we want to @@ -176,10 +179,10 @@ public: options.compression = rocksdb::kSnappyCompression; - getIfExists(keyValues, "block_size", tableOptions.block_size); + getIfExists(keyValues, Keys::kBlockSize, tableOptions.block_size); - if (keyValues.exists("universal_compaction") && - (get(keyValues, "universal_compaction") != 0)) + if (keyValues.exists(Keys::kUniversalCompaction) && + (get(keyValues, Keys::kUniversalCompaction) != 0)) { options.compaction_style = rocksdb::kCompactionStyleUniversal; options.min_write_buffer_number_to_merge = 2; @@ -187,11 +190,11 @@ public: options.write_buffer_size = 6 * options.target_file_size_base; } - if (keyValues.exists("bbt_options")) + if (keyValues.exists(Keys::kBbtOptions)) { rocksdb::ConfigOptions const configOptions; auto const s = rocksdb::GetBlockBasedTableOptionsFromString( - configOptions, tableOptions, get(keyValues, "bbt_options"), &tableOptions); + configOptions, tableOptions, get(keyValues, Keys::kBbtOptions), &tableOptions); if (!s.ok()) { Throw( @@ -201,10 +204,10 @@ public: options.table_factory.reset(NewBlockBasedTableFactory(tableOptions)); - if (keyValues.exists("options")) + if (keyValues.exists(Keys::kOptions)) { auto const s = - rocksdb::GetOptionsFromString(options, get(keyValues, "options"), &options); + rocksdb::GetOptionsFromString(options, get(keyValues, Keys::kOptions), &options); if (!s.ok()) { Throw( diff --git a/src/libxrpl/rdb/SociDB.cpp b/src/libxrpl/rdb/SociDB.cpp index 541933b3b0..06e58d373f 100644 --- a/src/libxrpl/rdb/SociDB.cpp +++ b/src/libxrpl/rdb/SociDB.cpp @@ -1,5 +1,6 @@ -#include #include +#include +#include #include #include #include @@ -53,13 +54,13 @@ getSociSqliteInit(std::string const& name, std::string const& dir, std::string c std::string getSociInit(BasicConfig const& config, std::string const& dbName) { - auto const& section = config.section("sqdb"); - auto const backendName = get(section, "backend", "sqlite"); + auto const& section = config.section(Sections::kSqdb); + auto const backendName = get(section, Keys::kBackend, "sqlite"); if (backendName != "sqlite") Throw("Unsupported soci backend: " + backendName); - auto const path = config.legacy("database_path"); + auto const path = config.legacy(Sections::kDatabasePath); auto const ext = dbName == "validators" || dbName == "peerfinder" ? ".sqlite" : ".db"; return detail::getSociSqliteInit(dbName, path, ext); } diff --git a/src/libxrpl/server/Port.cpp b/src/libxrpl/server/Port.cpp index b3fd7a1526..c1a79019af 100644 --- a/src/libxrpl/server/Port.cpp +++ b/src/libxrpl/server/Port.cpp @@ -1,11 +1,12 @@ #include -#include #include #include #include #include #include +#include +#include #include #include @@ -195,7 +196,7 @@ parsePort(ParsedPort& port, Section const& section, std::ostream& log) { port.name = section.name(); { - auto const optResult = section.get("ip"); + auto const optResult = section.get(Keys::kIp); if (optResult) { try @@ -212,7 +213,7 @@ parsePort(ParsedPort& port, Section const& section, std::ostream& log) } { - auto const optResult = section.get("port"); + auto const optResult = section.get(Keys::kPort); if (optResult) { try @@ -233,7 +234,7 @@ parsePort(ParsedPort& port, Section const& section, std::ostream& log) } { - auto const optResult = section.get("protocol"); + auto const optResult = section.get(Keys::kProtocol); if (optResult) { for (auto const& s : beast::rfc2616::splitCommas(optResult->begin(), optResult->end())) @@ -242,7 +243,7 @@ parsePort(ParsedPort& port, Section const& section, std::ostream& log) } { - auto const lim = get(section, "limit", "unlimited"); + auto const lim = get(section, Keys::kLimit, "unlimited"); if (!boost::iequals(lim, "unlimited")) { @@ -260,7 +261,7 @@ parsePort(ParsedPort& port, Section const& section, std::ostream& log) } { - auto const optResult = section.get("send_queue_limit"); + auto const optResult = section.get(Keys::kSendQueueLimit); if (optResult) { try @@ -285,27 +286,28 @@ parsePort(ParsedPort& port, Section const& section, std::ostream& log) } } - populate(section, "admin", log, port.adminNetsV4, port.adminNetsV6); - populate(section, "secure_gateway", log, port.secureGatewayNetsV4, port.secureGatewayNetsV6); + populate(section, Keys::kAdmin, log, port.adminNetsV4, port.adminNetsV6); + populate( + section, Keys::kSecureGateway, log, port.secureGatewayNetsV4, port.secureGatewayNetsV6); - set(port.user, "user", section); - set(port.password, "password", section); - set(port.adminUser, "admin_user", section); - set(port.adminPassword, "admin_password", section); - set(port.sslKey, "ssl_key", section); - set(port.sslCert, "ssl_cert", section); - set(port.sslChain, "ssl_chain", section); - set(port.sslCiphers, "ssl_ciphers", section); + set(port.user, Keys::kUser, section); + set(port.password, Keys::kPassword, section); + set(port.adminUser, Keys::kAdminUser, section); + set(port.adminPassword, Keys::kAdminPassword, section); + set(port.sslKey, Keys::kSslKey, section); + set(port.sslCert, Keys::kSslCert, section); + set(port.sslChain, Keys::kSslChain, section); + set(port.sslCiphers, Keys::kSslCiphers, section); - port.pmdOptions.server_enable = section.valueOr("permessage_deflate", true); - port.pmdOptions.client_max_window_bits = section.valueOr("client_max_window_bits", 15); - port.pmdOptions.server_max_window_bits = section.valueOr("server_max_window_bits", 15); + port.pmdOptions.server_enable = section.valueOr(Keys::kPermessageDeflate, true); + port.pmdOptions.client_max_window_bits = section.valueOr(Keys::kClientMaxWindowBits, 15); + port.pmdOptions.server_max_window_bits = section.valueOr(Keys::kServerMaxWindowBits, 15); port.pmdOptions.client_no_context_takeover = - section.valueOr("client_no_context_takeover", false); + section.valueOr(Keys::kClientNoContextTakeover, false); port.pmdOptions.server_no_context_takeover = - section.valueOr("server_no_context_takeover", false); - port.pmdOptions.compLevel = section.valueOr("compress_level", 8); - port.pmdOptions.memLevel = section.valueOr("memory_level", 4); + section.valueOr(Keys::kServerNoContextTakeover, false); + port.pmdOptions.compLevel = section.valueOr(Keys::kCompressLevel, 8); + port.pmdOptions.memLevel = section.valueOr(Keys::kMemoryLevel, 4); } } // namespace xrpl diff --git a/src/libxrpl/server/State.cpp b/src/libxrpl/server/State.cpp index b9cb7c6ff2..d9793f53d0 100644 --- a/src/libxrpl/server/State.cpp +++ b/src/libxrpl/server/State.cpp @@ -1,7 +1,7 @@ #include -#include #include +#include #include #include diff --git a/src/test/app/AmendmentTable_test.cpp b/src/test/app/AmendmentTable_test.cpp index 219aaabdda..7c2087bd4a 100644 --- a/src/test/app/AmendmentTable_test.cpp +++ b/src/test/app/AmendmentTable_test.cpp @@ -4,14 +4,14 @@ #include #include -#include -#include #include #include #include #include #include +#include +#include #include #include #include @@ -83,8 +83,8 @@ private: makeConfig() { auto cfg = test::jtx::envconfig(); - cfg->section(SECTION_AMENDMENTS) = makeSection(SECTION_AMENDMENTS, enabled_); - cfg->section(SECTION_VETO_AMENDMENTS) = makeSection(SECTION_VETO_AMENDMENTS, vetoed_); + cfg->section(Sections::kAmendments) = makeSection(Sections::kAmendments, enabled_); + cfg->section(Sections::kVetoAmendments) = makeSection(Sections::kVetoAmendments, vetoed_); return cfg; } diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 791bb5a4d6..47f9a84fb9 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include #include #include @@ -168,13 +170,13 @@ class Batch_test : public beast::unit_test::Suite std::map extraVoting = {}) { auto p = test::jtx::envconfig(); - auto& section = p->section("transaction_queue"); - section.set("ledgers_in_queue", "2"); - section.set("minimum_queue_size", "2"); - section.set("min_ledgers_to_compute_size_limit", "3"); - section.set("max_ledger_counts_to_store", "100"); - section.set("retry_sequence_percent", "25"); - section.set("normal_consensus_increase_percent", "0"); + auto& section = p->section(Sections::kTransactionQueue); + section.set(Keys::kLedgersInQueue, "2"); + section.set(Keys::kMinimumQueueSize, "2"); + section.set(Keys::kMinLedgersToComputeSizeLimit, "3"); + section.set(Keys::kMaxLedgerCountsToStore, "100"); + section.set(Keys::kRetrySequencePercent, "25"); + section.set(Keys::kNormalConsensusIncreasePercent, "0"); for (auto const& [k, v] : extraTxQ) section.set(k, v); @@ -4361,7 +4363,7 @@ class Batch_test : public beast::unit_test::Suite { test::jtx::Env env{ *this, - makeSmallQueueConfig({{"minimum_txn_in_ledger_standalone", "2"}}), + makeSmallQueueConfig({{Keys::kMinimumTxnInLedgerStandalone, "2"}}), features, nullptr, beast::Severity::Error}; @@ -4417,7 +4419,7 @@ class Batch_test : public beast::unit_test::Suite { test::jtx::Env env{ *this, - makeSmallQueueConfig({{"minimum_txn_in_ledger_standalone", "2"}}), + makeSmallQueueConfig({{Keys::kMinimumTxnInLedgerStandalone, "2"}}), features, nullptr, beast::Severity::Error}; diff --git a/src/test/app/FeeVote_test.cpp b/src/test/app/FeeVote_test.cpp index 22e8322bb5..bf42e762c6 100644 --- a/src/test/app/FeeVote_test.cpp +++ b/src/test/app/FeeVote_test.cpp @@ -4,9 +4,9 @@ #include #include -#include #include #include +#include #include #include #include diff --git a/src/test/app/GRPCServerTLS_test.cpp b/src/test/app/GRPCServerTLS_test.cpp index c7156fb3a2..ae0d839a6e 100644 --- a/src/test/app/GRPCServerTLS_test.cpp +++ b/src/test/app/GRPCServerTLS_test.cpp @@ -1,9 +1,8 @@ #include #include -#include - #include +#include #include #include @@ -368,7 +367,8 @@ public: Env env(*this, std::move(cfg)); // Verify the server actually started by checking the port - auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + auto const grpcPort = + env.app().config()[Sections::kPortGrpc].get(Keys::kPort); BEAST_EXPECT(grpcPort.has_value()); // NOLINTBEGIN(bugprone-unchecked-optional-access) grpcPort.has_value() checked above BEAST_EXPECT(*grpcPort > 0); @@ -394,7 +394,8 @@ public: Env env(*this, std::move(cfg)); // Verify the server actually started by checking the port - auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + auto const grpcPort = + env.app().config()[Sections::kPortGrpc].get(Keys::kPort); BEAST_EXPECT(grpcPort.has_value()); // NOLINTBEGIN(bugprone-unchecked-optional-access) grpcPort.has_value() checked above BEAST_EXPECT(*grpcPort > 0); @@ -431,7 +432,8 @@ public: Env env(*this, std::move(cfg)); // Verify the server actually started by checking the port - auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + auto const grpcPort = + env.app().config()[Sections::kPortGrpc].get(Keys::kPort); BEAST_EXPECT(grpcPort.has_value()); // NOLINTBEGIN(bugprone-unchecked-optional-access) grpcPort.has_value() checked above BEAST_EXPECT(*grpcPort > 0); @@ -465,9 +467,9 @@ public: // Create config with only cert (missing key) auto cfg = envconfig(); - (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1"); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string()); // Intentionally omit ssl_key try @@ -491,9 +493,9 @@ public: // Create config with only key (missing cert) auto cfg = envconfig(); - (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1"); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string()); // Intentionally omit ssl_cert try @@ -518,9 +520,9 @@ public: // Test 1: ssl_client_ca specified without any TLS config { auto cfg = envconfig(); - (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", getCACertPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1"); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, getCACertPath().string()); // Intentionally omit both ssl_cert and ssl_key try @@ -539,10 +541,10 @@ public: // Test 2: ssl_client_ca with only ssl_cert (missing ssl_key) { auto cfg = envconfig(); - (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); - (*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", getCACertPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1"); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, getCACertPath().string()); // Intentionally omit ssl_key try @@ -563,10 +565,10 @@ public: // Test 3: ssl_client_ca with only ssl_key (missing ssl_cert) { auto cfg = envconfig(); - (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string()); - (*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", getCACertPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1"); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, getCACertPath().string()); // Intentionally omit ssl_cert try @@ -595,9 +597,9 @@ public: // Test 1: ssl_cert_chain specified without any TLS config { auto cfg = envconfig(); - (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert_chain", getCACertPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1"); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCertChain, getCACertPath().string()); // Intentionally omit both ssl_cert and ssl_key try @@ -616,10 +618,10 @@ public: // Test 2: ssl_cert_chain with only ssl_cert (missing ssl_key) { auto cfg = envconfig(); - (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert_chain", getCACertPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1"); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCertChain, getCACertPath().string()); // Intentionally omit ssl_key try @@ -655,7 +657,8 @@ public: Env env(*this, std::move(cfg)); // Verify the server actually started by checking the port - auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + auto const grpcPort = + env.app().config()[Sections::kPortGrpc].get(Keys::kPort); BEAST_EXPECT(grpcPort.has_value()); // NOLINTBEGIN(bugprone-unchecked-optional-access) grpcPort.has_value() checked above BEAST_EXPECT(*grpcPort > 0); @@ -684,15 +687,16 @@ public: using namespace jtx; auto cfg = envconfig(); - (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", "/nonexistent/path/to/cert.pem"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1"); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, "/nonexistent/path/to/cert.pem"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string()); Env env(*this, std::move(cfg)); // Server should fail to start - verify port is 0 - auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + auto const grpcPort = + env.app().config()[Sections::kPortGrpc].get(Keys::kPort); BEAST_EXPECT(grpcPort.has_value()); BEAST_EXPECT(*grpcPort == 0); // NOLINT(bugprone-unchecked-optional-access) } @@ -705,15 +709,16 @@ public: using namespace jtx; auto cfg = envconfig(); - (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); - (*cfg)[SECTION_PORT_GRPC].set("ssl_key", "/nonexistent/path/to/key.pem"); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1"); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, "/nonexistent/path/to/key.pem"); Env env(*this, std::move(cfg)); // Server should fail to start - verify port is 0 - auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + auto const grpcPort = + env.app().config()[Sections::kPortGrpc].get(Keys::kPort); BEAST_EXPECT(grpcPort.has_value()); BEAST_EXPECT(*grpcPort == 0); // NOLINT(bugprone-unchecked-optional-access) } @@ -726,16 +731,17 @@ public: using namespace jtx; auto cfg = envconfig(); - (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); - (*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string()); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert_chain", "/nonexistent/path/to/chain.pem"); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1"); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCertChain, "/nonexistent/path/to/chain.pem"); Env env(*this, std::move(cfg)); // Server should fail to start - verify port is 0 - auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + auto const grpcPort = + env.app().config()[Sections::kPortGrpc].get(Keys::kPort); BEAST_EXPECT(grpcPort.has_value()); BEAST_EXPECT(*grpcPort == 0); // NOLINT(bugprone-unchecked-optional-access) } @@ -748,16 +754,17 @@ public: using namespace jtx; auto cfg = envconfig(); - (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); - (*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string()); - (*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", "/nonexistent/path/to/ca.pem"); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1"); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, "/nonexistent/path/to/ca.pem"); Env env(*this, std::move(cfg)); // Server should fail to start - verify port is 0 - auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + auto const grpcPort = + env.app().config()[Sections::kPortGrpc].get(Keys::kPort); BEAST_EXPECT(grpcPort.has_value()); BEAST_EXPECT(*grpcPort == 0); // NOLINT(bugprone-unchecked-optional-access) } @@ -775,16 +782,17 @@ public: emptyFile.close(); auto cfg = envconfig(); - (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); - (*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string()); - (*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", emptyCAPath.string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1"); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, emptyCAPath.string()); Env env(*this, std::move(cfg)); // Server should fail to start due to empty CA file - auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + auto const grpcPort = + env.app().config()[Sections::kPortGrpc].get(Keys::kPort); BEAST_EXPECT(grpcPort.has_value()); BEAST_EXPECT(*grpcPort == 0); // NOLINT(bugprone-unchecked-optional-access) } @@ -798,18 +806,19 @@ public: // Test with all TLS features enabled: cert, key, cert_chain, and client_ca auto cfg = envconfig(); - (*cfg)[SECTION_PORT_GRPC].set("ip", getEnvLocalhostAddr()); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); - (*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string()); - (*cfg)[SECTION_PORT_GRPC].set( - "ssl_cert_chain", getCACertPath().string()); // Using CA as intermediate - (*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", getCACertPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, getEnvLocalhostAddr()); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string()); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string()); + (*cfg)[Sections::kPortGrpc].set( + Keys::kSslCertChain, getCACertPath().string()); // Using CA as intermediate + (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, getCACertPath().string()); Env env(*this, std::move(cfg)); // Verify the server started successfully - auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + auto const grpcPort = + env.app().config()[Sections::kPortGrpc].get(Keys::kPort); BEAST_EXPECT(grpcPort.has_value()); // NOLINTBEGIN(bugprone-unchecked-optional-access) grpcPort.has_value() checked above BEAST_EXPECT(*grpcPort > 0); diff --git a/src/test/app/HashRouter_test.cpp b/src/test/app/HashRouter_test.cpp index 8f9cae351e..0266da2bc0 100644 --- a/src/test/app/HashRouter_test.cpp +++ b/src/test/app/HashRouter_test.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -274,9 +275,9 @@ class HashRouter_test : public beast::unit_test::Suite { Config cfg; // non-default - auto& h = cfg.section("hashrouter"); - h.set("hold_time", "600"); - h.set("relay_time", "15"); + auto& h = cfg.section(Sections::kHashrouter); + h.set(Keys::kHoldTime, "600"); + h.set(Keys::kRelayTime, "15"); auto const setup = setupHashRouter(cfg); BEAST_EXPECT(setup.holdTime == 600s); BEAST_EXPECT(setup.relayTime == 15s); @@ -284,9 +285,9 @@ class HashRouter_test : public beast::unit_test::Suite { Config cfg; // equal - auto& h = cfg.section("hashrouter"); - h.set("hold_time", "400"); - h.set("relay_time", "400"); + auto& h = cfg.section(Sections::kHashrouter); + h.set(Keys::kHoldTime, "400"); + h.set(Keys::kRelayTime, "400"); auto const setup = setupHashRouter(cfg); BEAST_EXPECT(setup.holdTime == 400s); BEAST_EXPECT(setup.relayTime == 400s); @@ -294,9 +295,9 @@ class HashRouter_test : public beast::unit_test::Suite { Config cfg; // wrong order - auto& h = cfg.section("hashrouter"); - h.set("hold_time", "60"); - h.set("relay_time", "120"); + auto& h = cfg.section(Sections::kHashrouter); + h.set(Keys::kHoldTime, "60"); + h.set(Keys::kRelayTime, "120"); try { setupHashRouter(cfg); @@ -313,9 +314,9 @@ class HashRouter_test : public beast::unit_test::Suite { Config cfg; // too small hold - auto& h = cfg.section("hashrouter"); - h.set("hold_time", "10"); - h.set("relay_time", "120"); + auto& h = cfg.section(Sections::kHashrouter); + h.set(Keys::kHoldTime, "10"); + h.set(Keys::kRelayTime, "120"); try { setupHashRouter(cfg); @@ -333,9 +334,9 @@ class HashRouter_test : public beast::unit_test::Suite { Config cfg; // too small relay - auto& h = cfg.section("hashrouter"); - h.set("hold_time", "500"); - h.set("relay_time", "6"); + auto& h = cfg.section(Sections::kHashrouter); + h.set(Keys::kHoldTime, "500"); + h.set(Keys::kRelayTime, "6"); try { setupHashRouter(cfg); @@ -352,9 +353,9 @@ class HashRouter_test : public beast::unit_test::Suite { Config cfg; // garbage - auto& h = cfg.section("hashrouter"); - h.set("hold_time", "alice"); - h.set("relay_time", "bob"); + auto& h = cfg.section(Sections::kHashrouter); + h.set(Keys::kHoldTime, "alice"); + h.set(Keys::kRelayTime, "bob"); auto const setup = setupHashRouter(cfg); // The set function ignores values that don't convert, so the // defaults are left unchanged diff --git a/src/test/app/Manifest_test.cpp b/src/test/app/Manifest_test.cpp index d559ecd7b5..0cf1155cf5 100644 --- a/src/test/app/Manifest_test.cpp +++ b/src/test/app/Manifest_test.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -246,7 +247,7 @@ public: auto& app = env.app(); auto unl = std::make_unique( - m, m, env.timeKeeper(), app.config().legacy("database_path"), env.journal); + m, m, env.timeKeeper(), app.config().legacy(Sections::kDatabasePath), env.journal); { // save should not store untrusted master keys to db diff --git a/src/test/app/MultiSign_test.cpp b/src/test/app/MultiSign_test.cpp index f21611df0e..5092cafef9 100644 --- a/src/test/app/MultiSign_test.cpp +++ b/src/test/app/MultiSign_test.cpp @@ -24,11 +24,11 @@ #include #include -#include #include #include #include +#include #include #include #include @@ -496,7 +496,7 @@ public: Env env( *this, envconfig([](std::unique_ptr cfg) { - cfg->loadFromString("[" SECTION_SIGNING_SUPPORT "]\ntrue"); + cfg->loadFromString(std::string("[") + Sections::kSigningSupport + "]\ntrue"); return cfg; }), features); @@ -1308,7 +1308,7 @@ public: Env env( *this, envconfig([](std::unique_ptr cfg) { - cfg->loadFromString("[" SECTION_SIGNING_SUPPORT "]\ntrue"); + cfg->loadFromString(std::string("[") + Sections::kSigningSupport + "]\ntrue"); return cfg; }), features); diff --git a/src/test/app/Regression_test.cpp b/src/test/app/Regression_test.cpp index 1c83e97e61..6ebde20176 100644 --- a/src/test/app/Regression_test.cpp +++ b/src/test/app/Regression_test.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -193,7 +194,7 @@ struct Regression_test : public beast::unit_test::Suite testcase("Autofilled fee should use the escalated fee"); using namespace jtx; Env env(*this, envconfig([](std::unique_ptr cfg) { - cfg->section("transaction_queue").set("minimum_txn_in_ledger_standalone", "3"); + cfg->section(Sections::kTransactionQueue).set(Keys::kMinimumTxnInLedgerStandalone, "3"); cfg->fees.referenceFee = 10; return cfg; })); @@ -233,11 +234,11 @@ struct Regression_test : public beast::unit_test::Suite using namespace std::chrono_literals; Env env(*this, envconfig([](std::unique_ptr cfg) { - auto& s = cfg->section("transaction_queue"); - s.set("minimum_txn_in_ledger_standalone", "4294967295"); - s.set("minimum_txn_in_ledger", "4294967295"); - s.set("target_txn_in_ledger", "4294967295"); - s.set("normal_consensus_increase_percent", "4294967295"); + auto& s = cfg->section(Sections::kTransactionQueue); + s.set(Keys::kMinimumTxnInLedgerStandalone, "4294967295"); + s.set(Keys::kMinimumTxnInLedger, "4294967295"); + s.set(Keys::kTargetTxnInLedger, "4294967295"); + s.set(Keys::kNormalConsensusIncreasePercent, "4294967295"); return cfg; })); diff --git a/src/test/app/SHAMapStore_test.cpp b/src/test/app/SHAMapStore_test.cpp index 6e279eadb2..7a149b2f64 100644 --- a/src/test/app/SHAMapStore_test.cpp +++ b/src/test/app/SHAMapStore_test.cpp @@ -7,11 +7,12 @@ #include #include #include -#include #include #include #include +#include +#include #include #include #include @@ -42,8 +43,8 @@ class SHAMapStore_test : public beast::unit_test::Suite onlineDelete(std::unique_ptr cfg) { cfg->ledgerHistory = kDeleteInterval; - auto& section = cfg->section(ConfigSection::nodeDatabase()); - section.set("online_delete", std::to_string(kDeleteInterval)); + auto& section = cfg->section(Sections::kNodeDatabase); + section.set(Keys::kOnlineDelete, std::to_string(kDeleteInterval)); return cfg; } @@ -51,7 +52,7 @@ class SHAMapStore_test : public beast::unit_test::Suite advisoryDelete(std::unique_ptr cfg) { cfg = onlineDelete(std::move(cfg)); - cfg->section(ConfigSection::nodeDatabase()).set("advisory_delete", "1"); + cfg->section(Sections::kNodeDatabase).set(Keys::kAdvisoryDelete, "1"); return cfg; } @@ -490,13 +491,13 @@ public: std::unique_ptr makeBackendRotating(jtx::Env& env, NodeStoreScheduler& scheduler, std::string path) { - Section section{env.app().config().section(ConfigSection::nodeDatabase())}; + Section section{env.app().config().section(Sections::kNodeDatabase)}; boost::filesystem::path newPath; if (!BEAST_EXPECT(path.size())) return {}; newPath = path; - section.set("path", newPath.string()); + section.set(Keys::kPath, newPath.string()); auto backend{NodeStore::Manager::instance().makeBackend( section, @@ -520,21 +521,21 @@ public: ///////////////////////////////////////////////////////////// // Create NodeStore with two backends to allow online deletion of data. // Normally, SHAMapStoreImp handles all these details. - auto nscfg = env.app().config().section(ConfigSection::nodeDatabase()); + auto nscfg = env.app().config().section(Sections::kNodeDatabase); // Provide default values. - if (!nscfg.exists("cache_size")) + if (!nscfg.exists(Keys::kCacheSize)) { nscfg.set( - "cache_size", + Keys::kCacheSize, std::to_string( env.app().config().getValueFor(SizedItem::TreeCacheSize, std::nullopt))); } - if (!nscfg.exists("cache_age")) + if (!nscfg.exists(Keys::kCacheAge)) { nscfg.set( - "cache_age", + Keys::kCacheAge, std::to_string( env.app().config().getValueFor(SizedItem::TreeCacheAge, std::nullopt))); } diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index dc28388de4..0ae6b4d80a 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -167,7 +168,7 @@ public: using namespace std::chrono; testcase("queue sequence"); - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}})); auto alice = Account("alice"); auto bob = Account("bob"); @@ -380,7 +381,7 @@ public: using namespace jtx; testcase("queue ticket"); - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}})); auto alice = Account("alice"); @@ -618,7 +619,7 @@ public: using namespace jtx; testcase("queue tec"); - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "2"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "2"}})); auto alice = Account("alice"); auto gw = Account("gw"); @@ -655,7 +656,7 @@ public: using namespace std::chrono; testcase("local tx retry"); - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "2"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "2"}})); auto alice = Account("alice"); auto bob = Account("bob"); @@ -708,7 +709,7 @@ public: using namespace std::chrono; testcase("last ledger sequence"); - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "2"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "2"}})); auto alice = Account("alice"); auto bob = Account("bob"); @@ -830,7 +831,7 @@ public: using namespace std::chrono; testcase("zero transaction fee"); - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "2"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "2"}})); auto alice = Account("alice"); auto bob = Account("bob"); @@ -957,7 +958,7 @@ public: using namespace jtx; testcase("queued tx fails"); - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "2"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "2"}})); auto alice = Account("alice"); auto bob = Account("bob"); @@ -1009,8 +1010,8 @@ public: Env env( *this, makeConfig( - {{"minimum_txn_in_ledger_standalone", "3"}}, - {{"account_reserve", "200"}, {"owner_reserve", "50"}})); + {{Keys::kMinimumTxnInLedgerStandalone, "3"}}, + {{Keys::kAccountReserve, "200"}, {Keys::kOwnerReserve, "50"}})); auto alice = Account("alice"); auto bob = Account("bob"); @@ -1258,7 +1259,7 @@ public: using namespace std::chrono; testcase("tie breaking"); - auto cfg = makeConfig({{"minimum_txn_in_ledger_standalone", "4"}}); + auto cfg = makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "4"}}); cfg->fees.referenceFee = 10; Env env(*this, std::move(cfg)); @@ -1471,7 +1472,7 @@ public: using namespace jtx; testcase("acct tx id"); - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "1"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "1"}})); auto alice = Account("alice"); @@ -1511,10 +1512,10 @@ public: Env env( *this, makeConfig( - {{"minimum_txn_in_ledger_standalone", "2"}, - {"minimum_txn_in_ledger", "5"}, - {"target_txn_in_ledger", "4"}, - {"maximum_txn_in_ledger", "5"}})); + {{Keys::kMinimumTxnInLedgerStandalone, "2"}, + {Keys::kMinimumTxnInLedger, "5"}, + {Keys::kTargetTxnInLedger, "4"}, + {Keys::kMaximumTxnInLedger, "5"}})); auto const baseFee = env.current()->fees().base.drops(); auto alice = Account("alice"); @@ -1555,10 +1556,10 @@ public: Env const env( *this, makeConfig( - {{"minimum_txn_in_ledger", "200"}, - {"minimum_txn_in_ledger_standalone", "200"}, - {"target_txn_in_ledger", "4"}, - {"maximum_txn_in_ledger", "5"}})); + {{Keys::kMinimumTxnInLedger, "200"}, + {Keys::kMinimumTxnInLedgerStandalone, "200"}, + {Keys::kTargetTxnInLedger, "4"}, + {Keys::kMaximumTxnInLedger, "5"}})); // should throw fail(); } @@ -1576,10 +1577,10 @@ public: Env const env( *this, makeConfig( - {{"minimum_txn_in_ledger", "200"}, - {"minimum_txn_in_ledger_standalone", "2"}, - {"target_txn_in_ledger", "4"}, - {"maximum_txn_in_ledger", "5"}})); + {{Keys::kMinimumTxnInLedger, "200"}, + {Keys::kMinimumTxnInLedgerStandalone, "2"}, + {Keys::kTargetTxnInLedger, "4"}, + {Keys::kMaximumTxnInLedger, "5"}})); // should throw fail(); } @@ -1597,10 +1598,10 @@ public: Env const env( *this, makeConfig( - {{"minimum_txn_in_ledger", "2"}, - {"minimum_txn_in_ledger_standalone", "200"}, - {"target_txn_in_ledger", "4"}, - {"maximum_txn_in_ledger", "5"}})); + {{Keys::kMinimumTxnInLedger, "2"}, + {Keys::kMinimumTxnInLedgerStandalone, "200"}, + {Keys::kTargetTxnInLedger, "4"}, + {Keys::kMaximumTxnInLedger, "5"}})); // should throw fail(); } @@ -1624,8 +1625,8 @@ public: Env env( *this, makeConfig( - {{"minimum_txn_in_ledger_standalone", "3"}}, - {{"account_reserve", "200"}, {"owner_reserve", "50"}})); + {{Keys::kMinimumTxnInLedgerStandalone, "3"}}, + {{Keys::kAccountReserve, "200"}, {Keys::kOwnerReserve, "50"}})); auto alice = Account("alice"); auto bob = Account("bob"); @@ -1716,7 +1717,7 @@ public: auto queued = Ter(terQUEUED); - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}})); auto const baseFee = env.current()->fees().base.drops(); checkMetrics(*this, env, 0, std::nullopt, 0, 3); @@ -1845,7 +1846,7 @@ public: auto queued = Ter(terQUEUED); - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}})); auto const baseFee = env.current()->fees().base.drops(); checkMetrics(*this, env, 0, std::nullopt, 0, 3); @@ -1996,8 +1997,8 @@ public: Env env( *this, makeConfig( - {{"minimum_txn_in_ledger_standalone", "3"}}, - {{"account_reserve", "200"}, {"owner_reserve", "50"}})); + {{Keys::kMinimumTxnInLedgerStandalone, "3"}}, + {{Keys::kAccountReserve, "200"}, {Keys::kOwnerReserve, "50"}})); auto alice = Account("alice"); auto charlie = Account("charlie"); @@ -2399,7 +2400,7 @@ public: auto queued = Ter(terQUEUED); - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}})); auto const baseFee = env.current()->fees().base.drops(); checkMetrics(*this, env, 0, std::nullopt, 0, 3); @@ -2568,9 +2569,9 @@ public: Env env( *this, makeConfig( - {{"minimum_txn_in_ledger_standalone", "1"}, - {"ledgers_in_queue", "10"}, - {"maximum_txn_per_account", "20"}})); + {{Keys::kMinimumTxnInLedgerStandalone, "1"}, + {Keys::kLedgersInQueue, "10"}, + {Keys::kMaximumTxnPerAccount, "20"}})); auto const baseFee = env.current()->fees().base.drops(); @@ -2650,9 +2651,9 @@ public: testcase("full queue gap handling"); auto cfg = makeConfig( - {{"minimum_txn_in_ledger_standalone", "1"}, - {"ledgers_in_queue", "10"}, - {"maximum_txn_per_account", "11"}}); + {{Keys::kMinimumTxnInLedgerStandalone, "1"}, + {Keys::kLedgersInQueue, "10"}, + {Keys::kMaximumTxnPerAccount, "11"}}); cfg->fees.referenceFee = 10; Env env(*this, std::move(cfg)); @@ -2777,7 +2778,7 @@ public: { testcase("Autofilled sequence should account for TxQ"); using namespace jtx; - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "6"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "6"}})); auto const baseFee = env.current()->fees().base.drops(); EnvSs envs(env); auto const& txQ = env.app().getTxQ(); @@ -2911,7 +2912,7 @@ public: using namespace jtx; testcase("account info"); - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}})); auto const baseFee = env.current()->fees().base.drops(); EnvSs envs(env); @@ -3181,7 +3182,7 @@ public: using namespace jtx; testcase("server info"); - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}})); auto const baseFee = env.current()->fees().base.drops(); EnvSs envs(env); @@ -3407,7 +3408,7 @@ public: using namespace jtx; testcase("server subscribe"); - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}})); auto const baseFee = env.current()->fees().base.drops(); json::Value stream; @@ -3546,7 +3547,7 @@ public: using namespace jtx; testcase("clear queued acct txs"); - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}})); auto const baseFee = env.current()->fees().base.drops(); auto alice = Account("alice"); auto bob = Account("bob"); @@ -3756,11 +3757,11 @@ public: Env env( *this, makeConfig( - {{"minimum_txn_in_ledger_standalone", "3"}, - {"normal_consensus_increase_percent", "25"}, - {"slow_consensus_decrease_percent", "50"}, - {"target_txn_in_ledger", "10"}, - {"maximum_txn_per_account", "200"}})); + {{Keys::kMinimumTxnInLedgerStandalone, "3"}, + {Keys::kNormalConsensusIncreasePercent, "25"}, + {Keys::kSlowConsensusDecreasePercent, "50"}, + {Keys::kTargetTxnInLedger, "10"}, + {Keys::kMaximumTxnPerAccount, "200"}})); auto alice = Account("alice"); checkMetrics(*this, env, 0, std::nullopt, 0, 3); @@ -3842,11 +3843,11 @@ public: Env env( *this, makeConfig( - {{"minimum_txn_in_ledger_standalone", "3"}, - {"normal_consensus_increase_percent", "150"}, - {"slow_consensus_decrease_percent", "150"}, - {"target_txn_in_ledger", "10"}, - {"maximum_txn_per_account", "200"}})); + {{Keys::kMinimumTxnInLedgerStandalone, "3"}, + {Keys::kNormalConsensusIncreasePercent, "150"}, + {Keys::kSlowConsensusDecreasePercent, "150"}, + {Keys::kTargetTxnInLedger, "10"}, + {Keys::kMaximumTxnPerAccount, "200"}})); auto alice = Account("alice"); checkMetrics(*this, env, 0, std::nullopt, 0, 3); @@ -3899,7 +3900,7 @@ public: testcase("Sequence in queue and open ledger"); using namespace jtx; - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}})); auto const alice = Account("alice"); @@ -3962,7 +3963,7 @@ public: testcase("Ticket in queue and open ledger"); using namespace jtx; - Env env(*this, makeConfig({{"minimum_txn_in_ledger_standalone", "3"}})); + Env env(*this, makeConfig({{Keys::kMinimumTxnInLedgerStandalone, "3"}})); auto alice = Account("alice"); @@ -4063,15 +4064,16 @@ public: static constexpr int kLedgersInQueue = 30; auto cfg = makeConfig( - {{"minimum_txn_in_ledger_standalone", "1"}, - {"ledgers_in_queue", std::to_string(kLedgersInQueue)}, - {"maximum_txn_per_account", "10"}}, - {{"account_reserve", "1000"}, {"owner_reserve", "50"}}); + {{Keys::kMinimumTxnInLedgerStandalone, "1"}, + {Keys::kLedgersInQueue, std::to_string(kLedgersInQueue)}, + {Keys::kMaximumTxnPerAccount, "10"}}, + {{Keys::kAccountReserve, "1000"}, {Keys::kOwnerReserve, "50"}}); - auto& votingSection = cfg->section("voting"); - votingSection.set("account_reserve", std::to_string(cfg->fees.referenceFee.drops() * 100)); + auto& votingSection = cfg->section(Sections::kVoting); + votingSection.set( + Keys::kAccountReserve, std::to_string(cfg->fees.referenceFee.drops() * 100)); - votingSection.set("reference_fee", std::to_string(cfg->fees.referenceFee.drops())); + votingSection.set(Keys::kReferenceFee, std::to_string(cfg->fees.referenceFee.drops())); Env env(*this, std::move(cfg)); @@ -4228,10 +4230,10 @@ public: Account const fiona("fiona"); auto cfg = makeConfig( - {{"minimum_txn_in_ledger_standalone", "5"}, - {"ledgers_in_queue", "5"}, - {"maximum_txn_per_account", "30"}, - {"minimum_queue_size", "50"}}); + {{Keys::kMinimumTxnInLedgerStandalone, "5"}, + {Keys::kLedgersInQueue, "5"}, + {Keys::kMaximumTxnPerAccount, "30"}, + {Keys::kMinimumQueueSize, "50"}}); Env env(*this, std::move(cfg)); auto const baseFee = env.current()->fees().base.drops(); @@ -4437,10 +4439,10 @@ public: auto usd = gw["USD"]; auto cfg = makeConfig( - {{"minimum_txn_in_ledger_standalone", "5"}, - {"ledgers_in_queue", "5"}, - {"maximum_txn_per_account", "30"}, - {"minimum_queue_size", "50"}}); + {{Keys::kMinimumTxnInLedgerStandalone, "5"}, + {Keys::kLedgersInQueue, "5"}, + {Keys::kMaximumTxnPerAccount, "30"}, + {Keys::kMinimumQueueSize, "50"}}); Env env(*this, std::move(cfg)); @@ -4537,8 +4539,10 @@ public: Env env( *this, makeConfig( - {{"minimum_txn_in_ledger_standalone", "3"}}, - {{"reference_fee", "0"}, {"account_reserve", "0"}, {"owner_reserve", "0"}})); + {{Keys::kMinimumTxnInLedgerStandalone, "3"}}, + {{Keys::kReferenceFee, "0"}, + {Keys::kAccountReserve, "0"}, + {Keys::kOwnerReserve, "0"}})); checkMetrics(*this, env, 0, std::nullopt, 0, 3); diff --git a/src/test/app/ValidatorKeys_test.cpp b/src/test/app/ValidatorKeys_test.cpp index 83267bf0a7..ca0e76c0b3 100644 --- a/src/test/app/ValidatorKeys_test.cpp +++ b/src/test/app/ValidatorKeys_test.cpp @@ -4,11 +4,11 @@ #include #include -#include #include #include #include +#include #include #include #include @@ -100,7 +100,7 @@ public: { // validation seed section -> empty manifest and valid seeds Config c; - c.section(SECTION_VALIDATION_SEED).append(seed_); + c.section(Sections::kValidationSeed).append(seed_); ValidatorKeys k{c, journal}; if (BEAST_EXPECT(k.keys); k.keys.has_value()) @@ -116,7 +116,7 @@ public: { // validation seed bad seed -> invalid Config c; - c.section(SECTION_VALIDATION_SEED).append("badseed"); + c.section(Sections::kValidationSeed).append("badseed"); ValidatorKeys const k{c, journal}; BEAST_EXPECT(k.configInvalid()); @@ -127,7 +127,7 @@ public: { // validator token Config c; - c.section(SECTION_VALIDATOR_TOKEN).append(tokenBlob_); + c.section(Sections::kValidatorToken).append(tokenBlob_); ValidatorKeys k{c, journal}; if (BEAST_EXPECT(k.keys); k.keys.has_value()) @@ -142,7 +142,7 @@ public: { // invalid validator token Config c; - c.section(SECTION_VALIDATOR_TOKEN).append("badtoken"); + c.section(Sections::kValidatorToken).append("badtoken"); ValidatorKeys const k{c, journal}; BEAST_EXPECT(k.configInvalid()); BEAST_EXPECT(!k.keys); @@ -152,8 +152,8 @@ public: { // Cannot specify both Config c; - c.section(SECTION_VALIDATION_SEED).append(seed_); - c.section(SECTION_VALIDATOR_TOKEN).append(tokenBlob_); + c.section(Sections::kValidationSeed).append(seed_); + c.section(Sections::kValidatorToken).append(tokenBlob_); ValidatorKeys const k{c, journal}; BEAST_EXPECT(k.configInvalid()); @@ -164,7 +164,7 @@ public: { // Token manifest and private key must match Config c; - c.section(SECTION_VALIDATOR_TOKEN).append(invalidTokenBlob_); + c.section(Sections::kValidatorToken).append(invalidTokenBlob_); ValidatorKeys const k{c, journal}; BEAST_EXPECT(k.configInvalid()); diff --git a/src/test/app/ValidatorList_test.cpp b/src/test/app/ValidatorList_test.cpp index 80483446a2..d71554f714 100644 --- a/src/test/app/ValidatorList_test.cpp +++ b/src/test/app/ValidatorList_test.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -198,7 +199,7 @@ private: manifests, manifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); BEAST_EXPECT(trustedKeys->quorum() == 1); } @@ -208,7 +209,7 @@ private: manifests, manifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal, minQuorum); BEAST_EXPECT(trustedKeys->quorum() == minQuorum); @@ -266,7 +267,7 @@ private: manifests, manifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); // Correct (empty) configuration @@ -292,7 +293,7 @@ private: manifests, manifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); BEAST_EXPECT(trustedKeys->load({}, cfgKeys, emptyCfgPublishers)); @@ -327,7 +328,7 @@ private: manifests, manifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); auto const localSigningPublic = @@ -347,7 +348,7 @@ private: manifests, manifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); auto const localSigningPublic = randomNode(); @@ -365,7 +366,7 @@ private: manifests, manifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) @@ -385,7 +386,7 @@ private: manifests, manifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); // load should reject invalid validator list signing keys @@ -421,7 +422,7 @@ private: manifests, manifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); std::vector const keys( @@ -446,7 +447,7 @@ private: valManifests, pubManifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); auto const pubRevokedSecret = randomSecretKey(); @@ -485,7 +486,7 @@ private: valManifests, pubManifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); auto const pubRevokedSecret = randomSecretKey(); @@ -571,7 +572,7 @@ private: manifests, manifests, env.app().getTimeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); auto expectTrusted = [this, &trustedKeys](std::vector const& list) { @@ -986,7 +987,7 @@ private: manifests, manifests, env.app().getTimeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); auto const publisherSecret = randomSecretKey(); @@ -1117,7 +1118,7 @@ private: manifestsOuter, manifestsOuter, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); std::vector const cfgPublishersOuter; @@ -1283,7 +1284,7 @@ private: manifestsOuter, manifestsOuter, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); auto const publisherSecret = randomSecretKey(); auto const publisherPublic = derivePublicKey(KeyType::Ed25519, publisherSecret); @@ -1310,7 +1311,7 @@ private: manifestsOuter, manifestsOuter, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); auto const masterPrivate = randomSecretKey(); auto const masterPublic = derivePublicKey(KeyType::Ed25519, masterPrivate); @@ -1344,7 +1345,7 @@ private: manifests, manifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal, minQuorum); @@ -1400,7 +1401,7 @@ private: manifestsOuter, manifestsOuter, env.app().getTimeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); std::vector const emptyCfgKeys; @@ -1499,7 +1500,7 @@ private: manifestsOuter, manifestsOuter, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); std::vector const cfgPublishers; @@ -1535,7 +1536,7 @@ private: manifestsOuter, manifestsOuter, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); auto const localKey = randomNode(); @@ -1582,7 +1583,7 @@ private: manifests, manifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); hash_set activeValidators; @@ -1670,7 +1671,7 @@ private: manifests, manifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); hash_set activeValidators; @@ -1877,7 +1878,7 @@ private: manifests, manifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); // Empty list has no expiration @@ -1899,7 +1900,7 @@ private: manifests, manifests, env.app().getTimeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); std::vector validators = {randomValidator()}; @@ -2040,7 +2041,7 @@ private: manifests, manifests, env.timeKeeper(), - env.app().config().legacy("database_path"), + env.app().config().legacy(Sections::kDatabasePath), env.journal, minimumQuorum); @@ -2636,7 +2637,7 @@ private: valManifests, pubManifests, env.timeKeeper(), - app.config().legacy("database_path"), + app.config().legacy(Sections::kDatabasePath), env.journal); std::vector cfgPublishers; diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index 92f59fe644..fdac1e450d 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -2,11 +2,11 @@ #include #include -#include -#include #include #include +#include +#include #include // IWYU pragma: keep #include @@ -295,9 +295,9 @@ port_wss_admin c.loadFromString(toLoad); - BEAST_EXPECT(c.legacy("ssl_verify") == "0"); + BEAST_EXPECT(c.legacy(Sections::kSslVerify) == "0"); expectException( - [&c] { [[maybe_unused]] auto _ = c.legacy("server"); }); // not a single line + [&c] { [[maybe_unused]] auto _ = c.legacy(Sections::kServer); }); // not a single line // set a legacy value BEAST_EXPECT(c.legacy("not_in_file").empty()); @@ -329,9 +329,9 @@ port_wss_admin // Load the config file from the current directory and verify it. Config c; c.setup("", true, false, true); - BEAST_EXPECT(c.section(SECTION_DEBUG_LOGFILE).values().size() == 1); + BEAST_EXPECT(c.section(Sections::kDebugLogfile).values().size() == 1); BEAST_EXPECT( - c.section(SECTION_DEBUG_LOGFILE).values()[0] == + c.section(Sections::kDebugLogfile).values()[0] == "/Users/dummy/xrpld/config/log/debug.log"); } @@ -368,9 +368,9 @@ port_wss_admin // Load the config file from the config directory and verify it. Config c; c.setup("", true, false, true); - BEAST_EXPECT(c.section(SECTION_DEBUG_LOGFILE).values().size() == 1); + BEAST_EXPECT(c.section(Sections::kDebugLogfile).values().size() == 1); BEAST_EXPECT( - c.section(SECTION_DEBUG_LOGFILE).values()[0] == + c.section(Sections::kDebugLogfile).values()[0] == "/Users/dummy/xrpld/config/log/debug.log"); // Restore the environment variables. @@ -404,9 +404,9 @@ port_wss_admin // Load the config file from the config directory and verify it. Config c; c.setup("", true, false, true); - BEAST_EXPECT(c.section(SECTION_DEBUG_LOGFILE).values().size() == 1); + BEAST_EXPECT(c.section(Sections::kDebugLogfile).values().size() == 1); BEAST_EXPECT( - c.section(SECTION_DEBUG_LOGFILE).values()[0] == + c.section(Sections::kDebugLogfile).values()[0] == "/Users/dummy/xrpld/config/log/debug.log"); // Restore the environment variables. @@ -436,13 +436,13 @@ port_wss_admin // Dummy test - do we get back what we put in Config c; c.loadFromString(boost::str(cc % dataDirAbs.string())); - BEAST_EXPECT(c.legacy("database_path") == dataDirAbs.string()); + BEAST_EXPECT(c.legacy(Sections::kDatabasePath) == dataDirAbs.string()); } { // Rel paths should convert to abs paths Config c; c.loadFromString(boost::str(cc % dataDirRel.string())); - BEAST_EXPECT(c.legacy("database_path") == dataDirAbs.string()); + BEAST_EXPECT(c.legacy(Sections::kDatabasePath) == dataDirAbs.string()); } { // No db section. @@ -450,7 +450,7 @@ port_wss_admin // load will not. Config c; c.loadFromString(""); - BEAST_EXPECT(c.legacy("database_path").empty()); + BEAST_EXPECT(c.legacy(Sections::kDatabasePath).empty()); } } { @@ -464,7 +464,7 @@ port_wss_admin auto const& c(g.config()); BEAST_EXPECT(g.dataDirExists()); BEAST_EXPECT(g.configFileExists()); - BEAST_EXPECT(c.legacy("database_path") == dataDirAbs.string()); + BEAST_EXPECT(c.legacy(Sections::kDatabasePath) == dataDirAbs.string()); } { // read from file relative path @@ -474,7 +474,7 @@ port_wss_admin std::string const nativeDbPath = absolute(path(dbPath)).string(); BEAST_EXPECT(g.dataDirExists()); BEAST_EXPECT(g.configFileExists()); - BEAST_EXPECT(c.legacy("database_path") == nativeDbPath); + BEAST_EXPECT(c.legacy(Sections::kDatabasePath) == nativeDbPath); } { // read from file no path @@ -484,7 +484,7 @@ port_wss_admin absolute(g.subdir() / path(Config::kDatabaseDirName)).string(); BEAST_EXPECT(g.dataDirExists()); BEAST_EXPECT(g.configFileExists()); - BEAST_EXPECT(c.legacy("database_path") == nativeDbPath); + BEAST_EXPECT(c.legacy(Sections::kDatabasePath) == nativeDbPath); } } @@ -653,8 +653,8 @@ nHUhG1PgAG8H8myUENypM35JgfqXAKNQvRVVAFDRzJrny5eZN8d5 nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8 )xrpldConfig"); c.loadFromString(toLoad); - BEAST_EXPECT(c.legacy("validators_file").empty()); - BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 5); + BEAST_EXPECT(c.legacy(Sections::kValidatorsFile).empty()); + BEAST_EXPECT(c.section(Sections::kValidators).values().size() == 5); BEAST_EXPECT(c.validatorListThreshold == std::nullopt); } { @@ -672,19 +672,19 @@ trust-these-validators.gov 1 )xrpldConfig"); c.loadFromString(toLoad); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); + BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 2); BEAST_EXPECT( - c.section(SECTION_VALIDATOR_LIST_SITES).values()[0] == "xrpl-validators.com"); + c.section(Sections::kValidatorListSites).values()[0] == "xrpl-validators.com"); BEAST_EXPECT( - c.section(SECTION_VALIDATOR_LIST_SITES).values()[1] == + c.section(Sections::kValidatorListSites).values()[1] == "trust-these-validators.gov"); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 1); + BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 1); BEAST_EXPECT( - c.section(SECTION_VALIDATOR_LIST_KEYS).values()[0] == + c.section(Sections::kValidatorListKeys).values()[0] == "021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801" "E566"); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values()[0] == "1"); + BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1); + BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values()[0] == "1"); BEAST_EXPECT(c.validatorListThreshold == std::size_t(1)); } { @@ -702,19 +702,19 @@ trust-these-validators.gov 0 )xrpldConfig"); c.loadFromString(toLoad); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); + BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 2); BEAST_EXPECT( - c.section(SECTION_VALIDATOR_LIST_SITES).values()[0] == "xrpl-validators.com"); + c.section(Sections::kValidatorListSites).values()[0] == "xrpl-validators.com"); BEAST_EXPECT( - c.section(SECTION_VALIDATOR_LIST_SITES).values()[1] == + c.section(Sections::kValidatorListSites).values()[1] == "trust-these-validators.gov"); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 1); + BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 1); BEAST_EXPECT( - c.section(SECTION_VALIDATOR_LIST_KEYS).values()[0] == + c.section(Sections::kValidatorListKeys).values()[0] == "021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801" "E566"); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values()[0] == "0"); + BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1); + BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values()[0] == "0"); BEAST_EXPECT(c.validatorListThreshold == std::nullopt); } { @@ -831,11 +831,11 @@ trust-these-validators.gov Config c; boost::format cc("[validators_file]\n%1%\n"); c.loadFromString(boost::str(cc % vtg.validatorsFile())); - BEAST_EXPECT(c.legacy("validators_file") == vtg.validatorsFile()); - BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 8); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); + BEAST_EXPECT(c.legacy(Sections::kValidatorsFile) == vtg.validatorsFile()); + BEAST_EXPECT(c.section(Sections::kValidators).values().size() == 8); + BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 2); + BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 2); + BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1); BEAST_EXPECT(c.validatorListThreshold == 2); } { @@ -848,11 +848,11 @@ trust-these-validators.gov BEAST_EXPECT(vtg.validatorsFileExists()); BEAST_EXPECT(rcg.configFileExists()); auto const& c(rcg.config()); - BEAST_EXPECT(c.legacy("validators_file") == valFileName); - BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 8); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); + BEAST_EXPECT(c.legacy(Sections::kValidatorsFile) == valFileName); + BEAST_EXPECT(c.section(Sections::kValidators).values().size() == 8); + BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 2); + BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 2); + BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1); BEAST_EXPECT(c.validatorListThreshold == 2); } { @@ -865,11 +865,11 @@ trust-these-validators.gov BEAST_EXPECT(vtg.validatorsFileExists()); BEAST_EXPECT(rcg.configFileExists()); auto const& c(rcg.config()); - BEAST_EXPECT(c.legacy("validators_file") == valFilePath); - BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 8); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); + BEAST_EXPECT(c.legacy(Sections::kValidatorsFile) == valFilePath); + BEAST_EXPECT(c.section(Sections::kValidators).values().size() == 8); + BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 2); + BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 2); + BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1); BEAST_EXPECT(c.validatorListThreshold == 2); } { @@ -880,11 +880,11 @@ trust-these-validators.gov BEAST_EXPECT(vtg.validatorsFileExists()); BEAST_EXPECT(rcg.configFileExists()); auto const& c(rcg.config()); - BEAST_EXPECT(c.legacy("validators_file").empty()); - BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 8); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); + BEAST_EXPECT(c.legacy(Sections::kValidatorsFile).empty()); + BEAST_EXPECT(c.section(Sections::kValidators).values().size() == 8); + BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 2); + BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 2); + BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1); BEAST_EXPECT(c.validatorListThreshold == 2); } { @@ -899,11 +899,11 @@ trust-these-validators.gov *this, vtg.subdir(), "", Config::kConfigFileName, vtg.validatorsFile(), false); BEAST_EXPECT(rcg.configFileExists()); auto const& c(rcg.config()); - BEAST_EXPECT(c.legacy("validators_file") == vtg.validatorsFile()); - BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 8); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 2); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); + BEAST_EXPECT(c.legacy(Sections::kValidatorsFile) == vtg.validatorsFile()); + BEAST_EXPECT(c.section(Sections::kValidators).values().size() == 8); + BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 2); + BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 2); + BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1); BEAST_EXPECT(c.validatorListThreshold == 2); } @@ -935,11 +935,11 @@ trust-these-validators.gov BEAST_EXPECT(vtg.validatorsFileExists()); Config c; c.loadFromString(boost::str(cc % vtg.validatorsFile())); - BEAST_EXPECT(c.legacy("validators_file") == vtg.validatorsFile()); - BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 15); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 4); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_KEYS).values().size() == 3); - BEAST_EXPECT(c.section(SECTION_VALIDATOR_LIST_THRESHOLD).values().size() == 1); + BEAST_EXPECT(c.legacy(Sections::kValidatorsFile) == vtg.validatorsFile()); + BEAST_EXPECT(c.section(Sections::kValidators).values().size() == 15); + BEAST_EXPECT(c.section(Sections::kValidatorListSites).values().size() == 4); + BEAST_EXPECT(c.section(Sections::kValidatorListKeys).values().size() == 3); + BEAST_EXPECT(c.section(Sections::kValidatorListThreshold).values().size() == 1); BEAST_EXPECT(c.validatorListThreshold == 2); } { @@ -1018,7 +1018,7 @@ trust-these-validators.gov BEAST_EXPECT(!config.silent()); BEAST_EXPECT(!config.standalone()); BEAST_EXPECT(config.ledgerHistory == 256); - BEAST_EXPECT(!config.legacy("database_path").empty()); + BEAST_EXPECT(!config.legacy(Sections::kDatabasePath).empty()); } { Config config; @@ -1031,7 +1031,7 @@ trust-these-validators.gov BEAST_EXPECT(!config.silent()); BEAST_EXPECT(!config.standalone()); BEAST_EXPECT(config.ledgerHistory == 256); - BEAST_EXPECT(!config.legacy("database_path").empty()); + BEAST_EXPECT(!config.legacy(Sections::kDatabasePath).empty()); } { Config config; @@ -1044,7 +1044,7 @@ trust-these-validators.gov BEAST_EXPECT(config.silent()); BEAST_EXPECT(!config.standalone()); BEAST_EXPECT(config.ledgerHistory == 256); - BEAST_EXPECT(!config.legacy("database_path").empty()); + BEAST_EXPECT(!config.legacy(Sections::kDatabasePath).empty()); } { Config config; @@ -1057,7 +1057,7 @@ trust-these-validators.gov BEAST_EXPECT(config.silent()); BEAST_EXPECT(!config.standalone()); BEAST_EXPECT(config.ledgerHistory == 256); - BEAST_EXPECT(!config.legacy("database_path").empty()); + BEAST_EXPECT(!config.legacy(Sections::kDatabasePath).empty()); } { Config config; @@ -1070,7 +1070,7 @@ trust-these-validators.gov BEAST_EXPECT(!config.silent()); BEAST_EXPECT(config.standalone()); BEAST_EXPECT(config.ledgerHistory == 0); - BEAST_EXPECT(config.legacy("database_path").empty() == !explicitPath); + BEAST_EXPECT(config.legacy(Sections::kDatabasePath).empty() == !explicitPath); } { Config config; @@ -1083,7 +1083,7 @@ trust-these-validators.gov BEAST_EXPECT(!config.silent()); BEAST_EXPECT(config.standalone()); BEAST_EXPECT(config.ledgerHistory == 0); - BEAST_EXPECT(config.legacy("database_path").empty() == !explicitPath); + BEAST_EXPECT(config.legacy(Sections::kDatabasePath).empty() == !explicitPath); } { Config config; @@ -1096,7 +1096,7 @@ trust-these-validators.gov BEAST_EXPECT(config.silent()); BEAST_EXPECT(config.standalone()); BEAST_EXPECT(config.ledgerHistory == 0); - BEAST_EXPECT(config.legacy("database_path").empty() == !explicitPath); + BEAST_EXPECT(config.legacy(Sections::kDatabasePath).empty() == !explicitPath); } { Config config; @@ -1109,7 +1109,7 @@ trust-these-validators.gov BEAST_EXPECT(config.silent()); BEAST_EXPECT(config.standalone()); BEAST_EXPECT(config.ledgerHistory == 0); - BEAST_EXPECT(config.legacy("database_path").empty() == !explicitPath); + BEAST_EXPECT(config.legacy(Sections::kDatabasePath).empty() == !explicitPath); } } @@ -1118,16 +1118,16 @@ trust-these-validators.gov { detail::FileCfgGuard const cfg(*this, "testPort", "", Config::kConfigFileName, ""); auto const& conf = cfg.config(); - if (!BEAST_EXPECT(conf.exists("port_rpc"))) + if (!BEAST_EXPECT(conf.exists(Sections::kPortRpc))) return; - if (!BEAST_EXPECT(conf.exists("port_wss_admin"))) + if (!BEAST_EXPECT(conf.exists(Sections::kPortWssAdmin))) return; ParsedPort rpc; - if (!unexcept([&]() { parsePort(rpc, conf["port_rpc"], log); })) + if (!unexcept([&]() { parsePort(rpc, conf[Sections::kPortRpc], log); })) return; BEAST_EXPECT(rpc.adminNetsV4.size() + rpc.adminNetsV6.size() == 2); ParsedPort wss; - if (!unexcept([&]() { parsePort(wss, conf["port_wss_admin"], log); })) + if (!unexcept([&]() { parsePort(wss, conf[Sections::kPortWssAdmin], log); })) return; BEAST_EXPECT(wss.adminNetsV4.size() + wss.adminNetsV6.size() == 1); } @@ -1182,14 +1182,15 @@ r.ripple.com 51235 )"); cfg.loadFromString(toLoad); BEAST_EXPECT( - cfg.exists("port_rpc") && cfg.section("port_rpc").lines().empty() && - cfg.section("port_rpc").values().empty()); + cfg.exists(Sections::kPortRpc) && cfg.section(Sections::kPortRpc).lines().empty() && + cfg.section(Sections::kPortRpc).values().empty()); BEAST_EXPECT( - cfg.exists(SECTION_IPS) && cfg.section(SECTION_IPS).lines().size() == 1 && - cfg.section(SECTION_IPS).values().size() == 1); + cfg.exists(Sections::kIps) && cfg.section(Sections::kIps).lines().size() == 1 && + cfg.section(Sections::kIps).values().size() == 1); BEAST_EXPECT( - cfg.exists(SECTION_IPS_FIXED) && cfg.section(SECTION_IPS_FIXED).lines().size() == 2 && - cfg.section(SECTION_IPS_FIXED).values().size() == 2); + cfg.exists(Sections::kIpsFixed) && + cfg.section(Sections::kIpsFixed).lines().size() == 2 && + cfg.section(Sections::kIpsFixed).values().size() == 2); } void @@ -1237,14 +1238,15 @@ r.ripple.com:51235 )"); cfg.loadFromString(toLoad); BEAST_EXPECT( - cfg.exists("port_rpc") && cfg.section("port_rpc").lines().empty() && - cfg.section("port_rpc").values().empty()); + cfg.exists(Sections::kPortRpc) && cfg.section(Sections::kPortRpc).lines().empty() && + cfg.section(Sections::kPortRpc).values().empty()); BEAST_EXPECT( - cfg.exists(SECTION_IPS) && cfg.section(SECTION_IPS).lines().size() == 1 && - cfg.section(SECTION_IPS).values().size() == 1); + cfg.exists(Sections::kIps) && cfg.section(Sections::kIps).lines().size() == 1 && + cfg.section(Sections::kIps).values().size() == 1); BEAST_EXPECT( - cfg.exists(SECTION_IPS_FIXED) && cfg.section(SECTION_IPS_FIXED).lines().size() == 15 && - cfg.section(SECTION_IPS_FIXED).values().size() == 15); + cfg.exists(Sections::kIpsFixed) && + cfg.section(Sections::kIpsFixed).lines().size() == 15 && + cfg.section(Sections::kIpsFixed).values().size() == 15); BEAST_EXPECT(cfg.ips[0] == "r.ripple.com 51235"); BEAST_EXPECT(cfg.ipsFixed[0] == "s1.ripple.com 51235"); @@ -1335,18 +1337,18 @@ r.ripple.com:51235 Section s; s.append("online_delete = 3000"); std::uint32_t od = 0; - BEAST_EXPECT(set(od, "online_delete", s)); + BEAST_EXPECT(set(od, Keys::kOnlineDelete, s)); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - BEAST_EXPECTS(od == 3000, *(s.get("online_delete"))); + BEAST_EXPECTS(od == 3000, *(s.get(Keys::kOnlineDelete))); } { Section s; s.append("online_delete = 2000 #my comment on this"); std::uint32_t od = 0; - BEAST_EXPECT(set(od, "online_delete", s)); + BEAST_EXPECT(set(od, Keys::kOnlineDelete, s)); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - BEAST_EXPECTS(od == 2000, *(s.get("online_delete"))); + BEAST_EXPECTS(od == 2000, *(s.get(Keys::kOnlineDelete))); } } diff --git a/src/test/core/SociDB_test.cpp b/src/test/core/SociDB_test.cpp index f4c6fb04f1..57ff19fec5 100644 --- a/src/test/core/SociDB_test.cpp +++ b/src/test/core/SociDB_test.cpp @@ -1,8 +1,9 @@ #include -#include #include #include +#include +#include #include #include @@ -32,10 +33,10 @@ private: static void setupSQLiteConfig(BasicConfig& config, boost::filesystem::path const& dbPath) { - config.overwrite("sqdb", "backend", "sqlite"); + config.overwrite(Sections::kSqdb, Keys::kBackend, "sqlite"); auto value = dbPath.string(); if (!value.empty()) - config.legacy("database_path", value); + config.legacy(Sections::kDatabasePath, value); } static void diff --git a/src/test/jtx/Env_test.cpp b/src/test/jtx/Env_test.cpp index 0c9e5ffe24..d82cb86b36 100644 --- a/src/test/jtx/Env_test.cpp +++ b/src/test/jtx/Env_test.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -863,7 +864,7 @@ public: jtx::Env const env{ *this, jtx::envconfig([](std::unique_ptr cfg) { - (*cfg).deprecatedClearSection("port_rpc"); + (*cfg).deprecatedClearSection(Sections::kPortRpc); return cfg; }), nullptr, diff --git a/src/test/jtx/envconfig.h b/src/test/jtx/envconfig.h index 9a4e1dc20a..4c56ec8217 100644 --- a/src/test/jtx/envconfig.h +++ b/src/test/jtx/envconfig.h @@ -4,11 +4,6 @@ namespace xrpl::test { -// frequently used macros defined here for convenience. -#define PORT_WS "port_ws" -#define PORT_RPC "port_rpc" -#define PORT_PEER "port_peer" - extern std::atomic gEnvUseIPv4; inline char const* diff --git a/src/test/jtx/impl/JSONRPCClient.cpp b/src/test/jtx/impl/JSONRPCClient.cpp index cf81bfab0c..495fc5a657 100644 --- a/src/test/jtx/impl/JSONRPCClient.cpp +++ b/src/test/jtx/impl/JSONRPCClient.cpp @@ -4,8 +4,9 @@ #include -#include #include +#include +#include #include #include #include @@ -40,8 +41,8 @@ class JSONRPCClient : public AbstractClient { auto& log = std::cerr; ParsedPort common; - parsePort(common, cfg["server"], log); - for (auto const& name : cfg.section("server").values()) + parsePort(common, cfg[Sections::kServer], log); + for (auto const& name : cfg.section(Sections::kServer).values()) { if (!cfg.exists(name)) continue; diff --git a/src/test/jtx/impl/WSClient.cpp b/src/test/jtx/impl/WSClient.cpp index 6d069523d2..c00a88f270 100644 --- a/src/test/jtx/impl/WSClient.cpp +++ b/src/test/jtx/impl/WSClient.cpp @@ -2,8 +2,9 @@ #include -#include #include +#include +#include #include #include #include @@ -62,9 +63,9 @@ class WSClientImpl : public WSClient { auto& log = std::cerr; ParsedPort common; - parsePort(common, cfg["server"], log); + parsePort(common, cfg[Sections::kServer], log); auto const ps = v2 ? "ws2" : "ws"; - for (auto const& name : cfg.section("server").values()) + for (auto const& name : cfg.section(Sections::kServer).values()) { if (!cfg.exists(name)) continue; diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index 56197e1078..bc65738b44 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -3,7 +3,8 @@ #include #include -#include + +#include #include #include @@ -27,33 +28,33 @@ setupConfigForUnitTests(Config& cfg) // The Beta API (currently v2) is always available to tests cfg.betaRpcApi = true; - cfg.overwrite(ConfigSection::nodeDatabase(), "type", "memory"); - cfg.overwrite(ConfigSection::nodeDatabase(), "path", "main"); - cfg.deprecatedClearSection(ConfigSection::importNodeDatabase()); - cfg.legacy("database_path", ""); + cfg.overwrite(Sections::kNodeDatabase, Keys::kType, "memory"); + cfg.overwrite(Sections::kNodeDatabase, Keys::kPath, "main"); + cfg.deprecatedClearSection(Sections::kImportNodeDatabase); + cfg.legacy(Sections::kDatabasePath, ""); cfg.setupControl(true, true, true); - cfg["server"].append(PORT_PEER); - cfg[PORT_PEER].set("ip", getEnvLocalhostAddr()); + cfg[Sections::kServer].append(Sections::kPortPeer); + cfg[Sections::kPortPeer].set(Keys::kIp, getEnvLocalhostAddr()); // Using port 0 asks the operating system to allocate an unused port, which // can be obtained after a "bind" call. // Works for all system (Linux, Windows, Unix, Mac). // Check https://man7.org/linux/man-pages/man7/ip.7.html // "ip_local_port_range" section for more info - cfg[PORT_PEER].set("port", "0"); - cfg[PORT_PEER].set("protocol", "peer"); + cfg[Sections::kPortPeer].set(Keys::kPort, "0"); + cfg[Sections::kPortPeer].set(Keys::kProtocol, "peer"); - cfg["server"].append(PORT_RPC); - cfg[PORT_RPC].set("ip", getEnvLocalhostAddr()); - cfg[PORT_RPC].set("admin", getEnvLocalhostAddr()); - cfg[PORT_RPC].set("port", "0"); - cfg[PORT_RPC].set("protocol", "http,ws2"); + cfg[Sections::kServer].append(Sections::kPortRpc); + cfg[Sections::kPortRpc].set(Keys::kIp, getEnvLocalhostAddr()); + cfg[Sections::kPortRpc].set(Keys::kAdmin, getEnvLocalhostAddr()); + cfg[Sections::kPortRpc].set(Keys::kPort, "0"); + cfg[Sections::kPortRpc].set(Keys::kProtocol, "http,ws2"); - cfg["server"].append(PORT_WS); - cfg[PORT_WS].set("ip", getEnvLocalhostAddr()); - cfg[PORT_WS].set("admin", getEnvLocalhostAddr()); - cfg[PORT_WS].set("port", "0"); - cfg[PORT_WS].set("protocol", "ws"); + cfg[Sections::kServer].append(Sections::kPortWs); + cfg[Sections::kPortWs].set(Keys::kIp, getEnvLocalhostAddr()); + cfg[Sections::kPortWs].set(Keys::kAdmin, getEnvLocalhostAddr()); + cfg[Sections::kPortWs].set(Keys::kPort, "0"); + cfg[Sections::kPortWs].set(Keys::kProtocol, "ws"); cfg.sslVerify = false; } @@ -62,35 +63,35 @@ namespace jtx { std::unique_ptr noAdmin(std::unique_ptr cfg) { - (*cfg)[PORT_RPC].set("admin", ""); - (*cfg)[PORT_WS].set("admin", ""); + (*cfg)[Sections::kPortRpc].set(Keys::kAdmin, ""); + (*cfg)[Sections::kPortWs].set(Keys::kAdmin, ""); return cfg; } std::unique_ptr secureGateway(std::unique_ptr cfg) { - (*cfg)[PORT_RPC].set("admin", ""); - (*cfg)[PORT_WS].set("admin", ""); - (*cfg)[PORT_RPC].set("secure_gateway", getEnvLocalhostAddr()); + (*cfg)[Sections::kPortRpc].set(Keys::kAdmin, ""); + (*cfg)[Sections::kPortWs].set(Keys::kAdmin, ""); + (*cfg)[Sections::kPortRpc].set(Keys::kSecureGateway, getEnvLocalhostAddr()); return cfg; } std::unique_ptr adminLocalnet(std::unique_ptr cfg) { - (*cfg)[PORT_RPC].set("admin", "127.0.0.0/8"); - (*cfg)[PORT_WS].set("admin", "127.0.0.0/8"); + (*cfg)[Sections::kPortRpc].set(Keys::kAdmin, "127.0.0.0/8"); + (*cfg)[Sections::kPortWs].set(Keys::kAdmin, "127.0.0.0/8"); return cfg; } std::unique_ptr secureGatewayLocalnet(std::unique_ptr cfg) { - (*cfg)[PORT_RPC].set("admin", ""); - (*cfg)[PORT_WS].set("admin", ""); - (*cfg)[PORT_RPC].set("secure_gateway", "127.0.0.0/8"); - (*cfg)[PORT_WS].set("secure_gateway", "127.0.0.0/8"); + (*cfg)[Sections::kPortRpc].set(Keys::kAdmin, ""); + (*cfg)[Sections::kPortWs].set(Keys::kAdmin, ""); + (*cfg)[Sections::kPortRpc].set(Keys::kSecureGateway, "127.0.0.0/8"); + (*cfg)[Sections::kPortWs].set(Keys::kSecureGateway, "127.0.0.0/8"); return cfg; } std::unique_ptr @@ -106,7 +107,7 @@ std::unique_ptr validator(std::unique_ptr cfg, std::string const& seed) { // If the config has valid validation keys then we run as a validator. - cfg->section(SECTION_VALIDATION_SEED) + cfg->section(Sections::kValidationSeed) .append(std::vector{seed.empty() ? kDefaultSeed : seed}); return cfg; } @@ -114,20 +115,20 @@ validator(std::unique_ptr cfg, std::string const& seed) std::unique_ptr addGrpcConfig(std::unique_ptr cfg) { - (*cfg)[SECTION_PORT_GRPC].set("ip", getEnvLocalhostAddr()); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, getEnvLocalhostAddr()); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); return cfg; } std::unique_ptr addGrpcConfigWithSecureGateway(std::unique_ptr cfg, std::string const& secureGateway) { - (*cfg)[SECTION_PORT_GRPC].set("ip", getEnvLocalhostAddr()); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, getEnvLocalhostAddr()); // Check https://man7.org/linux/man-pages/man7/ip.7.html // "ip_local_port_range" section for using 0 ports - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("secure_gateway", secureGateway); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSecureGateway, secureGateway); return cfg; } @@ -137,10 +138,10 @@ addGrpcConfigWithTLS( std::string const& certPath, std::string const& keyPath) { - (*cfg)[SECTION_PORT_GRPC].set("ip", getEnvLocalhostAddr()); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", certPath); - (*cfg)[SECTION_PORT_GRPC].set("ssl_key", keyPath); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, getEnvLocalhostAddr()); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, certPath); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, keyPath); return cfg; } @@ -151,11 +152,11 @@ addGrpcConfigWithTLSAndClientCA( std::string const& keyPath, std::string const& clientCAPath) { - (*cfg)[SECTION_PORT_GRPC].set("ip", getEnvLocalhostAddr()); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", certPath); - (*cfg)[SECTION_PORT_GRPC].set("ssl_key", keyPath); - (*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", clientCAPath); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, getEnvLocalhostAddr()); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, certPath); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, keyPath); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, clientCAPath); return cfg; } @@ -166,11 +167,11 @@ addGrpcConfigWithTLSAndCertChain( std::string const& keyPath, std::string const& certChainPath) { - (*cfg)[SECTION_PORT_GRPC].set("ip", getEnvLocalhostAddr()); - (*cfg)[SECTION_PORT_GRPC].set("port", "0"); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", certPath); - (*cfg)[SECTION_PORT_GRPC].set("ssl_key", keyPath); - (*cfg)[SECTION_PORT_GRPC].set("ssl_cert_chain", certChainPath); + (*cfg)[Sections::kPortGrpc].set(Keys::kIp, getEnvLocalhostAddr()); + (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0"); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, certPath); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, keyPath); + (*cfg)[Sections::kPortGrpc].set(Keys::kSslCertChain, certChainPath); return cfg; } @@ -180,13 +181,13 @@ makeConfig( std::map extraVoting) { auto p = test::jtx::envconfig(); - auto& section = p->section("transaction_queue"); - section.set("ledgers_in_queue", "2"); - section.set("minimum_queue_size", "2"); - section.set("min_ledgers_to_compute_size_limit", "3"); - section.set("max_ledger_counts_to_store", "100"); - section.set("retry_sequence_percent", "25"); - section.set("normal_consensus_increase_percent", "0"); + auto& section = p->section(Sections::kTransactionQueue); + section.set(Keys::kLedgersInQueue, "2"); + section.set(Keys::kMinimumQueueSize, "2"); + section.set(Keys::kMinLedgersToComputeSizeLimit, "3"); + section.set(Keys::kMaxLedgerCountsToStore, "100"); + section.set(Keys::kRetrySequencePercent, "25"); + section.set(Keys::kNormalConsensusIncreasePercent, "0"); for (auto const& [k, v] : extraTxQ) section.set(k, v); @@ -195,14 +196,14 @@ makeConfig( // a FeeVote if (!extraVoting.empty()) { - auto& votingSection = p->section("voting"); + auto& votingSection = p->section(Sections::kVoting); for (auto const& [k, v] : extraVoting) { votingSection.set(k, v); } // In order for the vote to occur, we must run as a validator - p->section("validation_seed").legacy("shUwVw52ofnCUX5m7kPTKzJdr4HEH"); + p->section(Sections::kValidationSeed).legacy("shUwVw52ofnCUX5m7kPTKzJdr4HEH"); } return p; } diff --git a/src/test/nodestore/Backend_test.cpp b/src/test/nodestore/Backend_test.cpp index 32b7d46868..65601b0cf5 100644 --- a/src/test/nodestore/Backend_test.cpp +++ b/src/test/nodestore/Backend_test.cpp @@ -1,12 +1,13 @@ #include #include -#include #include #include #include #include #include +#include +#include #include #include #include @@ -33,8 +34,8 @@ public: Section params; beast::TempDir const tempDir; - params.set("type", type); - params.set("path", tempDir.path()); + params.set(Keys::kType, type); + params.set(Keys::kPath, tempDir.path()); beast::xor_shift_engine rng(seedValue); diff --git a/src/test/nodestore/Database_test.cpp b/src/test/nodestore/Database_test.cpp index 50bdfefd4c..bb8ec7d4fd 100644 --- a/src/test/nodestore/Database_test.cpp +++ b/src/test/nodestore/Database_test.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include #include @@ -71,8 +73,8 @@ public: Env env = [&]() { auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("safety_level", "high"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kSafetyLevel, "high"); } p->ledgerHistory = 100'000'000; @@ -100,8 +102,8 @@ public: Env env = [&]() { auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("safety_level", "low"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kSafetyLevel, "low"); } p->ledgerHistory = 100'000'000; @@ -129,10 +131,10 @@ public: Env env = [&]() { auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("journal_mode", "off"); - section.set("synchronous", "extra"); - section.set("temp_store", "default"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kJournalMode, "off"); + section.set(Keys::kSynchronous, "extra"); + section.set(Keys::kTempStore, "default"); } return Env( @@ -161,10 +163,10 @@ public: Env env = [&]() { auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("journal_mode", "off"); - section.set("synchronous", "extra"); - section.set("temp_store", "default"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kJournalMode, "off"); + section.set(Keys::kSynchronous, "extra"); + section.set(Keys::kTempStore, "default"); } p->ledgerHistory = 50'000'000; @@ -197,11 +199,11 @@ public: auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("safety_level", "low"); - section.set("journal_mode", "off"); - section.set("synchronous", "extra"); - section.set("temp_store", "default"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kSafetyLevel, "low"); + section.set(Keys::kJournalMode, "off"); + section.set(Keys::kSynchronous, "extra"); + section.set(Keys::kTempStore, "default"); } try @@ -228,9 +230,9 @@ public: auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("safety_level", "high"); - section.set("journal_mode", "off"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kSafetyLevel, "high"); + section.set(Keys::kJournalMode, "off"); } try @@ -257,9 +259,9 @@ public: auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("safety_level", "low"); - section.set("synchronous", "extra"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kSafetyLevel, "low"); + section.set(Keys::kSynchronous, "extra"); } try @@ -286,9 +288,9 @@ public: auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("safety_level", "high"); - section.set("temp_store", "default"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kSafetyLevel, "high"); + section.set(Keys::kTempStore, "default"); } try @@ -315,8 +317,8 @@ public: auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("safety_level", "slow"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kSafetyLevel, "slow"); } try @@ -343,8 +345,8 @@ public: auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("journal_mode", "fast"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kJournalMode, "fast"); } try @@ -371,8 +373,8 @@ public: auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("synchronous", "instant"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kSynchronous, "instant"); } try @@ -399,8 +401,8 @@ public: auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("temp_store", "network"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kTempStore, "network"); } try @@ -434,9 +436,9 @@ public: Env env = [&]() { auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("page_size", "512"); - section.set("journal_size_limit", "2582080"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kPageSize, "512"); + section.set(Keys::kJournalSizeLimit, "2582080"); } return Env(*this, std::move(p)); }(); @@ -455,8 +457,8 @@ public: bool found = false; auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("page_size", "256"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kPageSize, "256"); } try { @@ -478,8 +480,8 @@ public: bool found = false; auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("page_size", "131072"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kPageSize, "131072"); } try { @@ -501,8 +503,8 @@ public: bool found = false; auto p = test::jtx::envconfig(); { - auto& section = p->section("sqlite"); - section.set("page_size", "513"); + auto& section = p->section(Sections::kSqlite); + section.set(Keys::kPageSize, "513"); } try { @@ -532,8 +534,8 @@ public: beast::TempDir const nodeDb; Section srcParams; - srcParams.set("type", srcBackendType); - srcParams.set("path", nodeDb.path()); + srcParams.set(Keys::kType, srcBackendType); + srcParams.set(Keys::kPath, nodeDb.path()); // Create a batch auto batch = createPredictableBatch(kNumObjectsToTest, seedValue); @@ -555,8 +557,8 @@ public: // Set up the destination database beast::TempDir const destDb; Section destParams; - destParams.set("type", destBackendType); - destParams.set("path", destDb.path()); + destParams.set(Keys::kType, destBackendType); + destParams.set(Keys::kPath, destDb.path()); std::unique_ptr dest = Manager::instance().makeDatabase(megabytes(4), scheduler, 2, destParams, journal_); @@ -593,8 +595,8 @@ public: beast::TempDir const nodeDb; Section nodeParams; - nodeParams.set("type", type); - nodeParams.set("path", nodeDb.path()); + nodeParams.set(Keys::kType, type); + nodeParams.set(Keys::kPath, nodeDb.path()); beast::xor_shift_engine rng(seedValue); @@ -653,7 +655,7 @@ public: // Set an invalid earliest ledger sequence try { - nodeParams.set("earliest_seq", "0"); + nodeParams.set(Keys::kEarliestSeq, "0"); std::unique_ptr const db = Manager::instance().makeDatabase( megabytes(4), scheduler, 2, nodeParams, journal_); } @@ -664,7 +666,7 @@ public: { // Set a valid earliest ledger sequence - nodeParams.set("earliest_seq", "1"); + nodeParams.set(Keys::kEarliestSeq, "1"); std::unique_ptr db = Manager::instance().makeDatabase( megabytes(4), scheduler, 2, nodeParams, journal_); @@ -676,7 +678,7 @@ public: try { // Set to default earliest ledger sequence - nodeParams.set("earliest_seq", std::to_string(kXrpLedgerEarliestSeq)); + nodeParams.set(Keys::kEarliestSeq, std::to_string(kXrpLedgerEarliestSeq)); std::unique_ptr const db2 = Manager::instance().makeDatabase( megabytes(4), scheduler, 2, nodeParams, journal_); } diff --git a/src/test/nodestore/NuDBFactory_test.cpp b/src/test/nodestore/NuDBFactory_test.cpp index fae13b9cc8..3ca3fa6838 100644 --- a/src/test/nodestore/NuDBFactory_test.cpp +++ b/src/test/nodestore/NuDBFactory_test.cpp @@ -1,12 +1,13 @@ #include #include -#include #include #include #include #include #include +#include +#include #include #include #include @@ -29,10 +30,10 @@ private: createSection(std::string const& path, std::string const& blockSize = "") { Section params; - params.set("type", "nudb"); - params.set("path", path); + params.set(Keys::kType, "nudb"); + params.set(Keys::kPath, path); if (!blockSize.empty()) - params.set("nudb_block_size", blockSize); + params.set(Keys::kNudbBlockSize, blockSize); return params; } diff --git a/src/test/nodestore/Timing_test.cpp b/src/test/nodestore/Timing_test.cpp index 67feace198..f5e6bf8aa4 100644 --- a/src/test/nodestore/Timing_test.cpp +++ b/src/test/nodestore/Timing_test.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -12,6 +11,8 @@ #include #include #include +#include +#include #include #include #include @@ -661,9 +662,10 @@ public: { beast::TempDir const tempDir; Section config = parse(configString); - config.set("path", tempDir.path()); + config.set(Keys::kPath, tempDir.path()); std::stringstream ss; - ss << std::left << setw(10) << get(config, "type", std::string()) << std::right; + ss << std::left << setw(10) << get(config, Keys::kType, std::string()) + << std::right; for (auto const& test : tests) { ss << " " << setw(w) << toString(doTest(test.second, config, params, journal)); diff --git a/src/test/overlay/cluster_test.cpp b/src/test/overlay/cluster_test.cpp index 0c164dfded..6c2114b7de 100644 --- a/src/test/overlay/cluster_test.cpp +++ b/src/test/overlay/cluster_test.cpp @@ -3,9 +3,9 @@ #include -#include #include #include +#include #include #include #include diff --git a/src/test/rpc/AmendmentBlocked_test.cpp b/src/test/rpc/AmendmentBlocked_test.cpp index ae4fb99542..850d6db35b 100644 --- a/src/test/rpc/AmendmentBlocked_test.cpp +++ b/src/test/rpc/AmendmentBlocked_test.cpp @@ -9,10 +9,10 @@ #include #include -#include #include #include +#include #include #include #include @@ -20,6 +20,7 @@ #include #include +#include namespace xrpl { @@ -30,7 +31,7 @@ class AmendmentBlocked_test : public beast::unit_test::Suite { using namespace test::jtx; Env env{*this, envconfig([](std::unique_ptr cfg) { - cfg->loadFromString("[" SECTION_SIGNING_SUPPORT "]\ntrue"); + cfg->loadFromString(std::string("[") + Sections::kSigningSupport + "]\ntrue"); return cfg; })}; auto const gw = Account{"gateway"}; diff --git a/src/test/rpc/Feature_test.cpp b/src/test/rpc/Feature_test.cpp index d899b6dfd9..8cda5965cb 100644 --- a/src/test/rpc/Feature_test.cpp +++ b/src/test/rpc/Feature_test.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -276,8 +277,8 @@ class Feature_test : public beast::unit_test::Suite using namespace test::jtx; Env env{*this, envconfig([](std::unique_ptr cfg) { - (*cfg)["port_rpc"].set("admin", ""); - (*cfg)["port_ws"].set("admin", ""); + (*cfg)[Sections::kPortRpc].set(Keys::kAdmin, ""); + (*cfg)[Sections::kPortWs].set(Keys::kAdmin, ""); return cfg; })}; diff --git a/src/test/rpc/JSONRPC_test.cpp b/src/test/rpc/JSONRPC_test.cpp index 1f24d229f2..7f6b123533 100644 --- a/src/test/rpc/JSONRPC_test.cpp +++ b/src/test/rpc/JSONRPC_test.cpp @@ -12,12 +12,12 @@ #include #include -#include #include #include #include #include +#include #include #include #include @@ -2375,8 +2375,9 @@ public: testcase("autofill escalated fees"); using namespace test::jtx; Env env{*this, envconfig([](std::unique_ptr cfg) { - cfg->loadFromString("[" SECTION_SIGNING_SUPPORT "]\ntrue"); - cfg->section("transaction_queue").set("minimum_txn_in_ledger_standalone", "3"); + cfg->loadFromString(std::string("[") + Sections::kSigningSupport + "]\ntrue"); + cfg->section(Sections::kTransactionQueue) + .set(Keys::kMinimumTxnInLedgerStandalone, "3"); return cfg; })}; LoadFeeTrack const& feeTrackOuter = env.app().getFeeTrack(); diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index f35dddffb1..af56a9e9ba 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -431,9 +432,9 @@ class LedgerRPC_test : public beast::unit_test::Suite testcase("Ledger with Queued Transactions"); using namespace test::jtx; auto cfg = envconfig([](std::unique_ptr cfg) { - auto& section = cfg->section("transaction_queue"); - section.set("minimum_txn_in_ledger_standalone", "3"); - section.set("normal_consensus_increase_percent", "0"); + auto& section = cfg->section(Sections::kTransactionQueue); + section.set(Keys::kMinimumTxnInLedgerStandalone, "3"); + section.set(Keys::kNormalConsensusIncreasePercent, "0"); return cfg; }); diff --git a/src/test/rpc/ManifestRPC_test.cpp b/src/test/rpc/ManifestRPC_test.cpp index c29f3d4e39..7e89cd029e 100644 --- a/src/test/rpc/ManifestRPC_test.cpp +++ b/src/test/rpc/ManifestRPC_test.cpp @@ -4,9 +4,9 @@ #include #include -#include #include +#include #include #include @@ -48,7 +48,7 @@ public: using namespace jtx; std::string const key = "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7"; Env env{*this, envconfig([&key](std::unique_ptr cfg) { - cfg->section(SECTION_VALIDATORS).append(key); + cfg->section(Sections::kValidators).append(key); return cfg; })}; { diff --git a/src/test/rpc/RPCOverload_test.cpp b/src/test/rpc/RPCOverload_test.cpp index b1030ebea5..33bc84eab0 100644 --- a/src/test/rpc/RPCOverload_test.cpp +++ b/src/test/rpc/RPCOverload_test.cpp @@ -8,14 +8,15 @@ #include #include -#include #include +#include #include #include #include #include +#include #include namespace xrpl::test { @@ -29,7 +30,7 @@ public: testcase << "Overload " << (useWS ? "WS" : "HTTP") << " RPC client"; using namespace jtx; Env env{*this, envconfig([](std::unique_ptr cfg) { - cfg->loadFromString("[" SECTION_SIGNING_SUPPORT "]\ntrue"); + cfg->loadFromString(std::string("[") + Sections::kSigningSupport + "]\ntrue"); return noAdmin(std::move(cfg)); })}; diff --git a/src/test/rpc/ServerInfo_test.cpp b/src/test/rpc/ServerInfo_test.cpp index 1d52f3b83e..52a1e6cdb0 100644 --- a/src/test/rpc/ServerInfo_test.cpp +++ b/src/test/rpc/ServerInfo_test.cpp @@ -3,9 +3,9 @@ #include #include -#include #include +#include #include #include @@ -108,9 +108,9 @@ admin = 127.0.0.1 Env env(*this, makeValidatorConfig()); auto const& config = env.app().config(); - auto const rpcPort = config["port_rpc"].get("port"); - auto const grpcPort = config[SECTION_PORT_GRPC].get("port"); - auto const wsPort = config["port_ws"].get("port"); + auto const rpcPort = config[Sections::kPortRpc].get(Keys::kPort); + auto const grpcPort = config[Sections::kPortGrpc].get(Keys::kPort); + auto const wsPort = config[Sections::kPortWs].get(Keys::kPort); BEAST_EXPECT(grpcPort); BEAST_EXPECT(rpcPort); BEAST_EXPECT(wsPort); diff --git a/src/test/rpc/Simulate_test.cpp b/src/test/rpc/Simulate_test.cpp index 9206bf42ac..5ea79c3996 100644 --- a/src/test/rpc/Simulate_test.cpp +++ b/src/test/rpc/Simulate_test.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -429,7 +430,7 @@ class Simulate_test : public beast::unit_test::Suite using namespace jtx; Env env(*this, envconfig([](std::unique_ptr cfg) { - cfg->section("transaction_queue").set("minimum_txn_in_ledger_standalone", "3"); + cfg->section(Sections::kTransactionQueue).set(Keys::kMinimumTxnInLedgerStandalone, "3"); return cfg; })); diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index 090a599e7c..5c519f3869 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -18,12 +18,12 @@ #include #include -#include #include #include #include #include +#include #include #include #include @@ -431,9 +431,10 @@ public: Env env{*this, singleThreadIo(envconfig(validator, "")), features}; auto& cfg = env.app().config(); - if (!BEAST_EXPECT(cfg.section(SECTION_VALIDATION_SEED).empty())) + if (!BEAST_EXPECT(cfg.section(Sections::kValidationSeed).empty())) return; - auto const parsedseed = parseBase58(cfg.section(SECTION_VALIDATION_SEED).values()[0]); + auto const parsedseed = + parseBase58(cfg.section(Sections::kValidationSeed).values()[0]); if (BEAST_EXPECT(parsedseed); not parsedseed.has_value()) return; diff --git a/src/test/rpc/ValidatorInfo_test.cpp b/src/test/rpc/ValidatorInfo_test.cpp index 8acdca8a1f..ece0aa1224 100644 --- a/src/test/rpc/ValidatorInfo_test.cpp +++ b/src/test/rpc/ValidatorInfo_test.cpp @@ -4,9 +4,9 @@ #include #include -#include #include +#include #include #include @@ -68,7 +68,7 @@ public: "5AqDedFv5TJa2w0i21eq3MYywLVJZnFOr7C0kw2AiTzSCjIzditQ8="; Env env{*this, envconfig([&tokenBlob](std::unique_ptr cfg) { - cfg->section(SECTION_VALIDATOR_TOKEN).append(tokenBlob); + cfg->section(Sections::kValidatorToken).append(tokenBlob); return cfg; })}; { diff --git a/src/test/rpc/ValidatorRPC_test.cpp b/src/test/rpc/ValidatorRPC_test.cpp index 1c6fb94fac..360cf13c75 100644 --- a/src/test/rpc/ValidatorRPC_test.cpp +++ b/src/test/rpc/ValidatorRPC_test.cpp @@ -5,12 +5,12 @@ #include #include #include -#include #include #include #include #include +#include #include #include #include @@ -90,7 +90,7 @@ public: *this, envconfig([&keys](std::unique_ptr cfg) { for (auto const& key : keys) - cfg->section(SECTION_VALIDATORS).append(key); + cfg->section(Sections::kValidators).append(key); return cfg; }), }; @@ -200,8 +200,8 @@ public: Env env{ *this, envconfig([&](std::unique_ptr cfg) { - cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI); - cfg->section(SECTION_VALIDATOR_LIST_KEYS) + cfg->section(Sections::kValidatorListSites).append(siteURI); + cfg->section(Sections::kValidatorListKeys) .append(strHex(server->publisherPublic())); return cfg; }), @@ -260,8 +260,8 @@ public: Env env{ *this, envconfig([&](std::unique_ptr cfg) { - cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI); - cfg->section(SECTION_VALIDATOR_LIST_KEYS) + cfg->section(Sections::kValidatorListSites).append(siteURI); + cfg->section(Sections::kValidatorListKeys) .append(strHex(server->publisherPublic())); return cfg; }), @@ -323,8 +323,8 @@ public: Env env{ *this, envconfig([&](std::unique_ptr cfg) { - cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI); - cfg->section(SECTION_VALIDATOR_LIST_KEYS) + cfg->section(Sections::kValidatorListSites).append(siteURI); + cfg->section(Sections::kValidatorListKeys) .append(strHex(server->publisherPublic())); return cfg; }), @@ -416,8 +416,8 @@ public: Env env{ *this, envconfig([&](std::unique_ptr cfg) { - cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI); - cfg->section(SECTION_VALIDATOR_LIST_KEYS) + cfg->section(Sections::kValidatorListSites).append(siteURI); + cfg->section(Sections::kValidatorListKeys) .append(strHex(server->publisherPublic())); return cfg; }), diff --git a/src/test/server/ServerStatus_test.cpp b/src/test/server/ServerStatus_test.cpp index 6569a2d2a4..5ba71962b3 100644 --- a/src/test/server/ServerStatus_test.cpp +++ b/src/test/server/ServerStatus_test.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -55,22 +56,23 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En static auto makeConfig(std::string const& proto, bool admin = true, bool credentials = false) { - auto const sectionName = boost::starts_with(proto, "h") ? "port_rpc" : "port_ws"; + auto const sectionName = + boost::starts_with(proto, "h") ? Sections::kPortRpc : Sections::kPortWs; auto p = jtx::envconfig(); - p->overwrite(sectionName, "protocol", proto); + p->overwrite(sectionName, Keys::kProtocol, proto); if (!admin) - p->overwrite(sectionName, "admin", ""); + p->overwrite(sectionName, Keys::kAdmin, ""); if (credentials) { - (*p)[sectionName].set("admin_password", "p"); - (*p)[sectionName].set("admin_user", "u"); + (*p)[sectionName].set(Keys::kAdminPassword, "p"); + (*p)[sectionName].set(Keys::kAdminUser, "u"); } p->overwrite( - boost::starts_with(proto, "h") ? "port_ws" : "port_rpc", - "protocol", + boost::starts_with(proto, "h") ? Sections::kPortWs : Sections::kPortRpc, + Keys::kProtocol, boost::starts_with(proto, "h") ? "ws" : "http"); if (proto == "https") @@ -78,11 +80,11 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En // this port is here to allow the env to create its internal client, // which requires an http endpoint to talk to. In the connection // failure test, this endpoint should never be used - (*p)["server"].append("port_alt"); - (*p)["port_alt"].set("ip", getEnvLocalhostAddr()); - (*p)["port_alt"].set("port", "7099"); - (*p)["port_alt"].set("protocol", "http"); - (*p)["port_alt"].set("admin", getEnvLocalhostAddr()); + (*p)[Sections::kServer].append("port_alt"); + (*p)["port_alt"].set(Keys::kIp, getEnvLocalhostAddr()); + (*p)["port_alt"].set(Keys::kPort, "7099"); + (*p)["port_alt"].set(Keys::kProtocol, "http"); + (*p)["port_alt"].set(Keys::kAdmin, getEnvLocalhostAddr()); } return p; @@ -212,8 +214,8 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En boost::beast::http::response& resp, boost::system::error_code& ec) { - auto const port = env.app().config()["port_ws"].get("port"); - auto ip = env.app().config()["port_ws"].get("ip"); + auto const port = env.app().config()[Sections::kPortWs].get(Keys::kPort); + auto ip = env.app().config()[Sections::kPortWs].get(Keys::kIp); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) doRequest(yield, makeWSUpgrade(*ip, *port), *ip, *port, secure, resp, ec); return; @@ -229,8 +231,8 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En std::string const& body = "", MyFields const& fields = {}) { - auto const port = env.app().config()["port_rpc"].get("port"); - auto const ip = env.app().config()["port_rpc"].get("ip"); + auto const port = env.app().config()[Sections::kPortRpc].get(Keys::kPort); + auto const ip = env.app().config()[Sections::kPortRpc].get(Keys::kIp); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) doRequest(yield, makeHTTPRequest(*ip, *port, body, fields), *ip, *port, secure, resp, ec); return; @@ -298,12 +300,13 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En if (admin && credentials) { - auto const user = - env.app().config()[protoWs ? "port_ws" : "port_rpc"].get("admin_user"); + auto const user = env.app() + .config()[protoWs ? Sections::kPortWs : Sections::kPortRpc] + .get(Keys::kAdminUser); - auto const password = - env.app().config()[protoWs ? "port_ws" : "port_rpc"].get( - "admin_password"); + auto const password = env.app() + .config()[protoWs ? Sections::kPortWs : Sections::kPortRpc] + .get(Keys::kAdminPassword); // 1 - FAILS with wrong pass // NOLINTNEXTLINE(bugprone-unchecked-optional-access) @@ -368,7 +371,7 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En testcase("WS client to http server fails"); using namespace jtx; Env env{*this, envconfig([](std::unique_ptr cfg) { - cfg->section("port_ws").set("protocol", "http,https"); + cfg->section(Sections::kPortWs).set(Keys::kProtocol, "http,https"); return cfg; })}; @@ -399,8 +402,8 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En testcase("Status request"); using namespace jtx; Env env{*this, envconfig([](std::unique_ptr cfg) { - cfg->section("port_rpc").set("protocol", "ws2,wss2"); - cfg->section("port_ws").set("protocol", "http"); + cfg->section(Sections::kPortRpc).set(Keys::kProtocol, "ws2,wss2"); + cfg->section(Sections::kPortWs).set(Keys::kProtocol, "http"); return cfg; })}; @@ -433,12 +436,12 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En using namespace boost::asio; using namespace boost::beast::http; Env env{*this, envconfig([](std::unique_ptr cfg) { - cfg->section("port_ws").set("protocol", "ws2"); + cfg->section(Sections::kPortWs).set(Keys::kProtocol, "ws2"); return cfg; })}; - auto const port = env.app().config()["port_ws"].get("port"); - auto const ip = env.app().config()["port_ws"].get("ip"); + auto const port = env.app().config()[Sections::kPortWs].get(Keys::kPort); + auto const ip = env.app().config()[Sections::kPortWs].get(Keys::kIp); boost::system::error_code ec; response resp; @@ -505,11 +508,11 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En using namespace test::jtx; Env env{*this, envconfig([secure](std::unique_ptr cfg) { - (*cfg)["port_rpc"].set("user", "me"); - (*cfg)["port_rpc"].set("password", "secret"); - (*cfg)["port_rpc"].set("protocol", secure ? "https" : "http"); + (*cfg)[Sections::kPortRpc].set(Keys::kUser, "me"); + (*cfg)[Sections::kPortRpc].set(Keys::kPassword, "secret"); + (*cfg)[Sections::kPortRpc].set(Keys::kProtocol, secure ? "https" : "http"); if (secure) - (*cfg)["port_ws"].set("protocol", "http,ws"); + (*cfg)[Sections::kPortWs].set(Keys::kProtocol, "http,ws"); return cfg; })}; @@ -533,11 +536,11 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth); BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - auto const user = env.app().config().section("port_rpc").get("user").value(); - auto const pass = - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - env.app().config().section("port_rpc").get("password").value(); + auto const section = env.app().config().section(Sections::kPortRpc); + // NOLINTBEGIN(bugprone-unchecked-optional-access) + auto const user = section.get(Keys::kUser).value(); + auto const pass = section.get(Keys::kPassword).value(); + // NOLINTEND(bugprone-unchecked-optional-access) // try with the correct user/pass, but not encoded auth.set("Authorization", "Basic " + user + ":" + pass); @@ -560,15 +563,15 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En using namespace boost::asio; using namespace boost::beast::http; Env env{*this, envconfig([&](std::unique_ptr cfg) { - (*cfg)["port_rpc"].set("limit", std::to_string(limit)); + (*cfg)[Sections::kPortRpc].set(Keys::kLimit, std::to_string(limit)); return cfg; })}; - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - auto const port = env.app().config()["port_rpc"].get("port").value(); - - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - auto const ip = env.app().config()["port_rpc"].get("ip").value(); + auto const section = env.app().config().section(Sections::kPortRpc); + // NOLINTBEGIN(bugprone-unchecked-optional-access) + auto const port = section.get(Keys::kPort).value(); + auto const ip = section.get(Keys::kIp).value(); + // NOLINTEND(bugprone-unchecked-optional-access) boost::system::error_code ec; io_context& ios = getIoContext(); @@ -620,14 +623,15 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En using namespace test::jtx; Env env{*this, envconfig([](std::unique_ptr cfg) { - (*cfg)["port_ws"].set("protocol", "wss"); + (*cfg)[Sections::kPortWs].set(Keys::kProtocol, "wss"); return cfg; })}; - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - auto const port = env.app().config()["port_ws"].get("port").value(); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - auto const ip = env.app().config()["port_ws"].get("ip").value(); + auto const section = env.app().config().section(Sections::kPortWs); + // NOLINTBEGIN(bugprone-unchecked-optional-access) + auto const port = section.get(Keys::kPort).value(); + auto const ip = section.get(Keys::kIp).value(); + // NOLINTEND(bugprone-unchecked-optional-access) boost::beast::http::response resp; boost::system::error_code ec; doRequest(yield, makeWSUpgrade(ip, port), ip, port, true, resp, ec); @@ -644,10 +648,11 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En using namespace test::jtx; Env env{*this}; - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - auto const port = env.app().config()["port_ws"].get("port").value(); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - auto const ip = env.app().config()["port_ws"].get("ip").value(); + auto const section = env.app().config().section(Sections::kPortWs); + // NOLINTBEGIN(bugprone-unchecked-optional-access) + auto const port = section.get(Keys::kPort).value(); + auto const ip = section.get(Keys::kIp).value(); + // NOLINTEND(bugprone-unchecked-optional-access) boost::beast::http::response resp; boost::system::error_code ec; // body content is required here to avoid being @@ -667,10 +672,11 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En using namespace boost::beast::http; Env env{*this}; - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - auto const port = env.app().config()["port_ws"].get("port").value(); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - auto const ip = env.app().config()["port_ws"].get("ip").value(); + auto const section = env.app().config().section(Sections::kPortWs); + // NOLINTBEGIN(bugprone-unchecked-optional-access) + auto const port = section.get(Keys::kPort).value(); + auto const ip = section.get(Keys::kIp).value(); + // NOLINTEND(bugprone-unchecked-optional-access) boost::system::error_code ec; io_context& ios = getIoContext(); @@ -746,7 +752,7 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En *this, validator( envconfig([](std::unique_ptr cfg) { - cfg->section("port_rpc").set("protocol", "http"); + cfg->section(Sections::kPortRpc).set(Keys::kProtocol, "http"); return cfg; }), "")}; @@ -774,8 +780,8 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En BEAST_EXPECT(env.app().getOPs().getConsensusInfo()["validating"] == true); BEAST_EXPECT(!si[jss::state].isMember(jss::warnings)); - auto const portWs = env.app().config()["port_ws"].get("port"); - auto const ipWs = env.app().config()["port_ws"].get("ip"); + auto const portWs = env.app().config()[Sections::kPortWs].get(Keys::kPort); + auto const ipWs = env.app().config()[Sections::kPortWs].get(Keys::kIp); boost::system::error_code ec; response resp; @@ -874,7 +880,7 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En *this, validator( envconfig([](std::unique_ptr cfg) { - cfg->section("port_rpc").set("protocol", "http"); + cfg->section(Sections::kPortRpc).set(Keys::kProtocol, "http"); return cfg; }), "")}; @@ -902,8 +908,8 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En BEAST_EXPECT(env.app().getOPs().getConsensusInfo()["validating"] == true); BEAST_EXPECT(!si[jss::state].isMember(jss::warnings)); - auto const portWs = env.app().config()["port_ws"].get("port"); - auto const ipWs = env.app().config()["port_ws"].get("ip"); + auto const portWs = env.app().config()[Sections::kPortWs].get(Keys::kPort); + auto const ipWs = env.app().config()[Sections::kPortWs].get(Keys::kIp); boost::system::error_code ec; response resp; diff --git a/src/test/server/Server_test.cpp b/src/test/server/Server_test.cpp index 1b0107167c..455a365b7c 100644 --- a/src/test/server/Server_test.cpp +++ b/src/test/server/Server_test.cpp @@ -4,11 +4,11 @@ #include #include -#include #include #include #include +#include #include #include #include @@ -396,7 +396,7 @@ public: Env const env{ *this, envconfig([](std::unique_ptr cfg) { - (*cfg).deprecatedClearSection("port_rpc"); + (*cfg).deprecatedClearSection(Sections::kPortRpc); return cfg; }), std::make_unique(&messages)}; @@ -407,8 +407,8 @@ public: Env const env{ *this, envconfig([](std::unique_ptr cfg) { - (*cfg).deprecatedClearSection("port_rpc"); - (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr()); + (*cfg).deprecatedClearSection(Sections::kPortRpc); + (*cfg)[Sections::kPortRpc].set(Keys::kIp, getEnvLocalhostAddr()); return cfg; }), std::make_unique(&messages)}; @@ -419,9 +419,9 @@ public: Env const env{ *this, envconfig([](std::unique_ptr cfg) { - (*cfg).deprecatedClearSection("port_rpc"); - (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr()); - (*cfg)["port_rpc"].set("port", "0"); + (*cfg).deprecatedClearSection(Sections::kPortRpc); + (*cfg)[Sections::kPortRpc].set(Keys::kIp, getEnvLocalhostAddr()); + (*cfg)[Sections::kPortRpc].set(Keys::kPort, "0"); return cfg; }), std::make_unique(&messages)}; @@ -433,7 +433,7 @@ public: Env const env{ *this, envconfig([](std::unique_ptr cfg) { - (*cfg)["server"].set("port", "0"); + (*cfg)[Sections::kServer].set(Keys::kPort, "0"); return cfg; }), std::make_unique(&messages)}; @@ -445,10 +445,10 @@ public: Env const env{ *this, envconfig([](std::unique_ptr cfg) { - (*cfg).deprecatedClearSection("port_rpc"); - (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr()); - (*cfg)["port_rpc"].set("port", "8081"); - (*cfg)["port_rpc"].set("protocol", ""); + (*cfg).deprecatedClearSection(Sections::kPortRpc); + (*cfg)[Sections::kPortRpc].set(Keys::kIp, getEnvLocalhostAddr()); + (*cfg)[Sections::kPortRpc].set(Keys::kPort, "8081"); + (*cfg)[Sections::kPortRpc].set(Keys::kProtocol, ""); return cfg; }), std::make_unique(&messages)}; @@ -462,22 +462,22 @@ public: *this, envconfig([](std::unique_ptr cfg) { cfg = std::make_unique(); - cfg->overwrite(ConfigSection::nodeDatabase(), "type", "memory"); - cfg->overwrite(ConfigSection::nodeDatabase(), "path", "main"); - cfg->deprecatedClearSection(ConfigSection::importNodeDatabase()); - cfg->legacy("database_path", ""); + cfg->overwrite(Sections::kNodeDatabase, Keys::kType, "memory"); + cfg->overwrite(Sections::kNodeDatabase, Keys::kPath, "main"); + cfg->deprecatedClearSection(Sections::kImportNodeDatabase); + cfg->legacy(Sections::kDatabasePath, ""); cfg->setupControl(true, true, true); - (*cfg)["port_peer"].set("ip", getEnvLocalhostAddr()); - (*cfg)["port_peer"].set("port", "8080"); - (*cfg)["port_peer"].set("protocol", "peer"); - (*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr()); - (*cfg)["port_rpc"].set("port", "8081"); - (*cfg)["port_rpc"].set("protocol", "http,ws2"); - (*cfg)["port_rpc"].set("admin", getEnvLocalhostAddr()); - (*cfg)["port_ws"].set("ip", getEnvLocalhostAddr()); - (*cfg)["port_ws"].set("port", "8082"); - (*cfg)["port_ws"].set("protocol", "ws"); - (*cfg)["port_ws"].set("admin", getEnvLocalhostAddr()); + (*cfg)[Sections::kPortPeer].set(Keys::kIp, getEnvLocalhostAddr()); + (*cfg)[Sections::kPortPeer].set(Keys::kPort, "8080"); + (*cfg)[Sections::kPortPeer].set(Keys::kProtocol, "peer"); + (*cfg)[Sections::kPortRpc].set(Keys::kIp, getEnvLocalhostAddr()); + (*cfg)[Sections::kPortRpc].set(Keys::kPort, "8081"); + (*cfg)[Sections::kPortRpc].set(Keys::kProtocol, "http,ws2"); + (*cfg)[Sections::kPortRpc].set(Keys::kAdmin, getEnvLocalhostAddr()); + (*cfg)[Sections::kPortWs].set(Keys::kIp, getEnvLocalhostAddr()); + (*cfg)[Sections::kPortWs].set(Keys::kPort, "8082"); + (*cfg)[Sections::kPortWs].set(Keys::kProtocol, "ws"); + (*cfg)[Sections::kPortWs].set(Keys::kAdmin, getEnvLocalhostAddr()); return cfg; }), std::make_unique(&messages)}; @@ -491,14 +491,14 @@ public: *this, envconfig([](std::unique_ptr cfg) { cfg = std::make_unique(); - cfg->overwrite(ConfigSection::nodeDatabase(), "type", "memory"); - cfg->overwrite(ConfigSection::nodeDatabase(), "path", "main"); - cfg->deprecatedClearSection(ConfigSection::importNodeDatabase()); - cfg->legacy("database_path", ""); + cfg->overwrite(Sections::kNodeDatabase, Keys::kType, "memory"); + cfg->overwrite(Sections::kNodeDatabase, Keys::kPath, "main"); + cfg->deprecatedClearSection(Sections::kImportNodeDatabase); + cfg->legacy(Sections::kDatabasePath, ""); cfg->setupControl(true, true, true); - (*cfg)["server"].append("port_peer"); - (*cfg)["server"].append("port_rpc"); - (*cfg)["server"].append("port_ws"); + (*cfg)[Sections::kServer].append(Sections::kPortPeer); + (*cfg)[Sections::kServer].append(Sections::kPortRpc); + (*cfg)[Sections::kServer].append(Sections::kPortWs); return cfg; }), std::make_unique(&messages)}; diff --git a/src/test/shamap/common.h b/src/test/shamap/common.h index 49f1c07741..0475acdf6d 100644 --- a/src/test/shamap/common.h +++ b/src/test/shamap/common.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include #include @@ -33,8 +35,8 @@ public: , j_(j) { Section testSection; - testSection.set("type", "memory"); - testSection.set("path", "SHAMap_test"); + testSection.set(Keys::kType, "memory"); + testSection.set(Keys::kPath, "SHAMap_test"); db_ = NodeStore::Manager::instance().makeDatabase( megabytes(4), scheduler_, 1, testSection, j); } diff --git a/src/tests/libxrpl/helpers/TestFamily.h b/src/tests/libxrpl/helpers/TestFamily.h index dea7a6d4b4..50f514480a 100644 --- a/src/tests/libxrpl/helpers/TestFamily.h +++ b/src/tests/libxrpl/helpers/TestFamily.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include #include @@ -37,8 +39,8 @@ public: , j_(j) { Section config; - config.set("type", "memory"); - config.set("path", "TestFamily"); + config.set(Keys::kType, "memory"); + config.set(Keys::kPath, "TestFamily"); db_ = NodeStore::Manager::instance().makeDatabase(megabytes(4), scheduler_, 1, config, j); } diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index af5d51289d..67b5e30eb7 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -40,7 +39,6 @@ #include #include -#include #include #include #include @@ -56,6 +54,8 @@ #include #include #include +#include +#include #include #include #include @@ -316,13 +316,14 @@ public: // PerfLog must be started before any other threads are launched. , perfLog_( perf::makePerfLog( - perf::setupPerfLog(config_->section("perf"), config_->configDir), + perf::setupPerfLog(config_->section(Sections::kPerf), config_->configDir), *this, logs_->journal("PerfLog"), [this] { signalStop("PerfLog"); })) , txMaster_(*this) - , collectorManager_( - makeCollectorManager(config_->section(SECTION_INSIGHT), logs_->journal("Collector"))) + , collectorManager_(makeCollectorManager( + config_->section(Sections::kInsight), + logs_->journal("Collector"))) , jobQueue_( std::make_unique( [](std::unique_ptr const& config) { @@ -864,7 +865,7 @@ public: megabytes(config_->getValueFor(SizedItem::BurstSize, std::nullopt)), dummyScheduler, 0, - config_->section(ConfigSection::importNodeDatabase()), + config_->section(Sections::kImportNodeDatabase), j); JLOG(j.warn()) << "Starting node import from '" << source->getName() << "' to '" @@ -1224,9 +1225,9 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) } return supported; }(); - Section const& downVoted = config_->section(SECTION_VETO_AMENDMENTS); + Section const& downVoted = config_->section(Sections::kVetoAmendments); - Section const& upVoted = config_->section(SECTION_AMENDMENTS); + Section const& upVoted = config_->section(Sections::kAmendments); amendmentTable_ = makeAmendmentTable( *this, @@ -1295,7 +1296,7 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) nodeIdentity_ = getNodeIdentity(*this, cmdline); - if (!cluster_->load(config().section(SECTION_CLUSTER_NODES))) + if (!cluster_->load(config().section(Sections::kClusterNodes))) { JLOG(journal_.fatal()) << "Invalid entry in cluster configuration."; return false; @@ -1309,7 +1310,7 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) getWalletDB(), "ValidatorManifests", validatorKeys_.manifest, - config().section(SECTION_VALIDATOR_KEY_REVOCATION).values())) + config().section(Sections::kValidatorKeyRevocation).values())) { JLOG(journal_.fatal()) << "Invalid configured validator manifest."; return false; @@ -1320,7 +1321,7 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) // It is possible to have a valid ValidatorKeys object without // setting the signingKey or masterKey. This occurs if the // configuration file does not have either - // SECTION_VALIDATOR_TOKEN or SECTION_VALIDATION_SEED section. + // Sections::kValidatorToken or Sections::kValidationSeed section. // masterKey for the configuration-file specified validator keys std::optional localSigningKey; @@ -1330,8 +1331,8 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) // Setup trusted validators if (!validators_->load( localSigningKey, - config().section(SECTION_VALIDATORS).values(), - config().section(SECTION_VALIDATOR_LIST_KEYS).values(), + config().section(Sections::kValidators).values(), + config().section(Sections::kValidatorListKeys).values(), config().validatorListThreshold)) { JLOG(journal_.fatal()) << "Invalid entry in validator configuration."; @@ -1339,9 +1340,9 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) } } - if (!validatorSites_->load(config().section(SECTION_VALIDATOR_LIST_SITES).values())) + if (!validatorSites_->load(config().section(Sections::kValidatorListSites).values())) { - JLOG(journal_.fatal()) << "Invalid entry in [" << SECTION_VALIDATOR_LIST_SITES << "]"; + JLOG(journal_.fatal()) << "Invalid entry in [" << Sections::kValidatorListSites << "]"; return false; } @@ -1439,7 +1440,7 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) // // Execute start up rpc commands. // - for (auto const& cmd : config_->section(SECTION_RPC_STARTUP).lines()) + for (auto const& cmd : config_->section(Sections::kRpcStartup).lines()) { json::Reader jrReader; json::Value jvCommand; @@ -1447,7 +1448,7 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) if (!jrReader.parse(cmd, jvCommand)) { JLOG(journal_.fatal()) - << "Couldn't parse entry in [" << SECTION_RPC_STARTUP << "]: '" << cmd; + << "Couldn't parse entry in [" << Sections::kRpcStartup << "]: '" << cmd; } if (!config_->quiet()) @@ -1503,7 +1504,7 @@ ApplicationImp::start(bool withTimers) overlay_->start(); if (grpcServer_->start()) - fixConfigPorts(*config_, {{SECTION_PORT_GRPC, grpcServer_->getEndpoint()}}); + fixConfigPorts(*config_, {{Sections::kPortGrpc, grpcServer_->getEndpoint()}}); ledgerCleaner_->start(); perfLog_->start(); @@ -2170,12 +2171,12 @@ fixConfigPorts(Config& config, Endpoints const& endpoints) continue; auto& section = config[name]; - auto const optPort = section.get("port"); + auto const optPort = section.get(Keys::kPort); if (optPort) { std::uint16_t const port = beast::lexicalCast(*optPort); if (port == 0u) - section.set("port", std::to_string(ep.port())); + section.set(Keys::kPort, std::to_string(ep.port())); } } } diff --git a/src/xrpld/app/main/CollectorManager.cpp b/src/xrpld/app/main/CollectorManager.cpp index 6cdbca8d8a..9e1278607f 100644 --- a/src/xrpld/app/main/CollectorManager.cpp +++ b/src/xrpld/app/main/CollectorManager.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -8,6 +7,8 @@ #include #include #include +#include +#include #include #include @@ -25,13 +26,13 @@ public: CollectorManagerImp(Section const& params, beast::Journal journal) : journal_(journal) { - std::string const& server = get(params, "server"); + std::string const& server = get(params, Keys::kServer); if (server == "statsd") { beast::IP::Endpoint const address( - beast::IP::Endpoint::fromString(get(params, "address"))); - std::string const& prefix(get(params, "prefix")); + beast::IP::Endpoint::fromString(get(params, Keys::kAddress))); + std::string const& prefix(get(params, Keys::kPrefix)); collector_ = beast::insight::StatsDCollector::make(address, prefix, journal); } diff --git a/src/xrpld/app/main/CollectorManager.h b/src/xrpld/app/main/CollectorManager.h index e736ae57db..d07da15353 100644 --- a/src/xrpld/app/main/CollectorManager.h +++ b/src/xrpld/app/main/CollectorManager.h @@ -1,7 +1,7 @@ #pragma once -#include #include +#include namespace xrpl { diff --git a/src/xrpld/app/main/GRPCServer.cpp b/src/xrpld/app/main/GRPCServer.cpp index 7a622f632f..1af1343fc5 100644 --- a/src/xrpld/app/main/GRPCServer.cpp +++ b/src/xrpld/app/main/GRPCServer.cpp @@ -1,13 +1,11 @@ #include #include -#include #include #include #include #include -#include #include #include #include @@ -15,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -335,15 +335,15 @@ GRPCServerImpl::GRPCServerImpl(Application& app) : app_(app), journal_(app_.getJournal("gRPC Server")) { // if present, get endpoint from config - if (app_.config().exists(SECTION_PORT_GRPC)) + if (app_.config().exists(Sections::kPortGrpc)) { - Section const& section = app_.config().section(SECTION_PORT_GRPC); + Section const& section = app_.config().section(Sections::kPortGrpc); - auto const optIp = section.get("ip"); + auto const optIp = section.get(Keys::kIp); if (!optIp) return; - auto const optPort = section.get("port"); + auto const optPort = section.get(Keys::kPort); if (!optPort) return; try @@ -361,7 +361,7 @@ GRPCServerImpl::GRPCServerImpl(Application& app) Throw("Error setting grpc server address"); } - auto const optSecureGateway = section.get("secure_gateway"); + auto const optSecureGateway = section.get(Keys::kSecureGateway); if (optSecureGateway) { try @@ -391,10 +391,10 @@ GRPCServerImpl::GRPCServerImpl(Application& app) } // Read TLS certificate configuration (optional) - sslCertPath_ = section.get("ssl_cert"); - sslKeyPath_ = section.get("ssl_key"); - sslCertChainPath_ = section.get("ssl_cert_chain"); - sslClientCAPath_ = section.get("ssl_client_ca"); + sslCertPath_ = section.get(Keys::kSslCert); + sslKeyPath_ = section.get(Keys::kSslKey); + sslCertChainPath_ = section.get(Keys::kSslCertChain); + sslClientCAPath_ = section.get(Keys::kSslClientCa); // If cert or key is specified, both must be specified if (sslCertPath_.has_value() || sslKeyPath_.has_value()) diff --git a/src/xrpld/app/main/Main.cpp b/src/xrpld/app/main/Main.cpp index f470a2d80f..d0b40efce8 100644 --- a/src/xrpld/app/main/Main.cpp +++ b/src/xrpld/app/main/Main.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -12,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -362,10 +362,10 @@ run(int argc, char** argv) std::string importText; { importText += "Import an existing node database (specified in the ["; - importText += ConfigSection::importNodeDatabase(); + importText += Sections::kImportNodeDatabase; importText += "] configuration file section) into the current "; importText += "node database (specified in the ["; - importText += ConfigSection::nodeDatabase(); + importText += Sections::kNodeDatabase; importText += "] configuration file section)."; } diff --git a/src/xrpld/app/main/NodeIdentity.cpp b/src/xrpld/app/main/NodeIdentity.cpp index 656e2b91a2..8198c43af7 100644 --- a/src/xrpld/app/main/NodeIdentity.cpp +++ b/src/xrpld/app/main/NodeIdentity.cpp @@ -2,9 +2,9 @@ #include #include -#include #include +#include #include #include #include @@ -31,12 +31,15 @@ getNodeIdentity(Application& app, boost::program_options::variables_map const& c if (!seed) Throw("Invalid 'nodeid' in command line"); } - else if (app.config().exists(SECTION_NODE_SEED)) + else if (app.config().exists(Sections::kNodeSeed)) { - seed = parseBase58(app.config().section(SECTION_NODE_SEED).lines().front()); + seed = parseBase58(app.config().section(Sections::kNodeSeed).lines().front()); if (!seed) - Throw("Invalid [" SECTION_NODE_SEED "] in configuration file"); + { + Throw( + std::string("Invalid [") + Sections::kNodeSeed + "] in configuration file"); + } } if (seed) diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 54cf85ba35..917cf6aeb2 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -54,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -313,7 +313,7 @@ public: , consensus_( registry_.get().getApp(), makeFeeVote( - setupFeeVote(registry_.get().getApp().config().section("voting")), + setupFeeVote(registry_.get().getApp().config().section(Sections::kVoting)), registry_.get().getJournal("FeeVote")), ledgerMaster, *localTX_, @@ -2973,11 +2973,12 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) } } - if (registry_.get().getApp().config().exists(SECTION_PORT_GRPC)) + if (registry_.get().getApp().config().exists(Sections::kPortGrpc)) { - auto const& grpcSection = registry_.get().getApp().config().section(SECTION_PORT_GRPC); - auto const optPort = grpcSection.get("port"); - if (optPort && grpcSection.get("ip")) + auto const& grpcSection = + registry_.get().getApp().config().section(Sections::kPortGrpc); + auto const optPort = grpcSection.get(Keys::kPort); + if (optPort && grpcSection.get(Keys::kIp)) { auto& jv = ports.append(json::Value(json::ValueType::Object)); jv[jss::port] = *optPort; diff --git a/src/xrpld/app/misc/SHAMapStoreImp.cpp b/src/xrpld/app/misc/SHAMapStoreImp.cpp index 3f4bd6280f..7259c233e4 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.cpp +++ b/src/xrpld/app/misc/SHAMapStoreImp.cpp @@ -4,15 +4,15 @@ #include #include #include -#include -#include #include #include #include #include #include #include +#include +#include #include #include #include @@ -101,44 +101,45 @@ SHAMapStoreImp::SHAMapStoreImp( { Config& config{app.config()}; - Section& section{config.section(ConfigSection::nodeDatabase())}; + Section& section{config.section(Sections::kNodeDatabase)}; if (section.empty()) { Throw( - "Missing [" + ConfigSection::nodeDatabase() + "] entry in configuration file"); + std::string("Missing [") + Sections::kNodeDatabase + "] entry in configuration file"); } // RocksDB only. Use sensible defaults if no values specified. - if (boost::iequals(get(section, "type"), "RocksDB")) + if (boost::iequals(get(section, Keys::kType), "RocksDB")) { - if (!section.exists("cache_mb")) + if (!section.exists(Keys::kCacheMb)) { - section.set("cache_mb", std::to_string(config.getValueFor(SizedItem::HashNodeDbCache))); + section.set( + Keys::kCacheMb, std::to_string(config.getValueFor(SizedItem::HashNodeDbCache))); } - if (!section.exists("filter_bits") && (config.nodeSize >= 2)) - section.set("filter_bits", "10"); + if (!section.exists(Keys::kFilterBits) && (config.nodeSize >= 2)) + section.set(Keys::kFilterBits, "10"); } - getIfExists(section, "online_delete", deleteInterval_); + getIfExists(section, Keys::kOnlineDelete, deleteInterval_); if (deleteInterval_ != 0u) { // Configuration that affects the behavior of online delete - getIfExists(section, "delete_batch", deleteBatch_); + getIfExists(section, Keys::kDeleteBatch, deleteBatch_); std::uint32_t temp = 0; - if (getIfExists(section, "back_off_milliseconds", temp) || + if (getIfExists(section, Keys::kBackOffMilliseconds, temp) || // Included for backward compatibility with an undocumented setting - getIfExists(section, "backOff", temp)) + getIfExists(section, Keys::kBackOff, temp)) { backOff_ = std::chrono::milliseconds{temp}; } - if (getIfExists(section, "age_threshold_seconds", temp)) + if (getIfExists(section, Keys::kAgeThresholdSeconds, temp)) ageThreshold_ = std::chrono::seconds{temp}; - if (getIfExists(section, "recovery_wait_seconds", temp)) + if (getIfExists(section, Keys::kRecoveryWaitSeconds, temp)) recoveryWaitTime_ = std::chrono::seconds{temp}; - getIfExists(section, "advisory_delete", advisoryDelete_); + getIfExists(section, Keys::kAdvisoryDelete, advisoryDelete_); auto const minInterval = config.standalone() ? kMinimumDeletionIntervalSa : kMinimumDeletionInterval; @@ -164,20 +165,20 @@ SHAMapStoreImp::SHAMapStoreImp( std::unique_ptr SHAMapStoreImp::makeNodeStore(int readThreads) { - auto nscfg = app_.config().section(ConfigSection::nodeDatabase()); + auto nscfg = app_.config().section(Sections::kNodeDatabase); // Provide default values. - if (!nscfg.exists("cache_size")) + if (!nscfg.exists(Keys::kCacheSize)) { nscfg.set( - "cache_size", + Keys::kCacheSize, std::to_string(app_.config().getValueFor(SizedItem::TreeCacheSize, std::nullopt))); } - if (!nscfg.exists("cache_age")) + if (!nscfg.exists(Keys::kCacheAge)) { nscfg.set( - "cache_age", + Keys::kCacheAge, std::to_string(app_.config().getValueFor(SizedItem::TreeCacheAge, std::nullopt))); } @@ -385,8 +386,8 @@ SHAMapStoreImp::run() void SHAMapStoreImp::dbPaths() { - Section const section{app_.config().section(ConfigSection::nodeDatabase())}; - boost::filesystem::path dbPath = get(section, "path"); + Section const section{app_.config().section(Sections::kNodeDatabase)}; + boost::filesystem::path dbPath = get(section, Keys::kPath); if (boost::filesystem::exists(dbPath)) { @@ -451,7 +452,7 @@ SHAMapStoreImp::dbPaths() (!archiveDbExists && !state.archiveDb.empty()) || (writableDbExists != archiveDbExists) || state.writableDb.empty() != state.archiveDb.empty()) { - boost::filesystem::path stateDbPathName = app_.config().legacy("database_path"); + boost::filesystem::path stateDbPathName = app_.config().legacy(Sections::kDatabasePath); stateDbPathName /= dbName_; stateDbPathName += "*"; @@ -463,7 +464,7 @@ SHAMapStoreImp::dbPaths() << "The existing data is in a corrupted state.\n" << "To resume operation, remove the files matching " << stateDbPathName.string() << " and contents of the directory " - << get(section, "path") << '\n' + << get(section, Keys::kPath) << '\n' << "Optionally, you can move those files to another\n" << "location if you wish to analyze or back up the data.\n" << "However, there is no guarantee that the data in its\n" @@ -480,7 +481,7 @@ SHAMapStoreImp::dbPaths() std::unique_ptr SHAMapStoreImp::makeBackendRotating(std::string path) { - Section section{app_.config().section(ConfigSection::nodeDatabase())}; + Section section{app_.config().section(Sections::kNodeDatabase)}; boost::filesystem::path newPath; if (!path.empty()) @@ -489,12 +490,12 @@ SHAMapStoreImp::makeBackendRotating(std::string path) } else { - boost::filesystem::path p = get(section, "path"); + boost::filesystem::path p = get(section, Keys::kPath); p /= dbPrefix_; p += ".%%%%"; newPath = boost::filesystem::unique_path(p); } - section.set("path", newPath.string()); + section.set(Keys::kPath, newPath.string()); auto backend{NodeStore::Manager::instance().makeBackend( section, diff --git a/src/xrpld/app/misc/ValidatorList.h b/src/xrpld/app/misc/ValidatorList.h index dcd7a24499..3f1823f930 100644 --- a/src/xrpld/app/misc/ValidatorList.h +++ b/src/xrpld/app/misc/ValidatorList.h @@ -233,8 +233,8 @@ class ValidatorList std::optional localPubKey_; // The below variable contains the Publisher list specified in the local - // config file under the title of SECTION_VALIDATORS or [validators]. - // This list is not associated with the masterKey of any publisher. + // config file under the title of [validators]. This list is not associated + // with the masterKey of any publisher. // Apropos PublisherListCollection fields, localPublisherList does not // have any "remaining" manifests. It is assumed to be perennially diff --git a/src/xrpld/app/misc/detail/AmendmentTable.cpp b/src/xrpld/app/misc/detail/AmendmentTable.cpp index 65771f6aa3..4f331b0781 100644 --- a/src/xrpld/app/misc/detail/AmendmentTable.cpp +++ b/src/xrpld/app/misc/detail/AmendmentTable.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -8,6 +7,7 @@ #include #include #include +#include #include #include #include diff --git a/src/xrpld/app/misc/detail/TxQ.cpp b/src/xrpld/app/misc/detail/TxQ.cpp index c12632875d..f98580ede4 100644 --- a/src/xrpld/app/misc/detail/TxQ.cpp +++ b/src/xrpld/app/misc/detail/TxQ.cpp @@ -3,12 +3,13 @@ #include #include -#include #include #include #include #include #include +#include +#include #include #include #include @@ -1872,16 +1873,16 @@ TxQ::Setup setupTxQ(Config const& config) { TxQ::Setup setup; - auto const& section = config.section("transaction_queue"); - set(setup.ledgersInQueue, "ledgers_in_queue", section); - set(setup.queueSizeMin, "minimum_queue_size", section); - set(setup.retrySequencePercent, "retry_sequence_percent", section); - set(setup.minimumEscalationMultiplier, "minimum_escalation_multiplier", section); - set(setup.minimumTxnInLedger, "minimum_txn_in_ledger", section); - set(setup.minimumTxnInLedgerSA, "minimum_txn_in_ledger_standalone", section); - set(setup.targetTxnInLedger, "target_txn_in_ledger", section); + auto const& section = config.section(Sections::kTransactionQueue); + set(setup.ledgersInQueue, Keys::kLedgersInQueue, section); + set(setup.queueSizeMin, Keys::kMinimumQueueSize, section); + set(setup.retrySequencePercent, Keys::kRetrySequencePercent, section); + set(setup.minimumEscalationMultiplier, Keys::kMinimumEscalationMultiplier, section); + set(setup.minimumTxnInLedger, Keys::kMinimumTxnInLedger, section); + set(setup.minimumTxnInLedgerSA, Keys::kMinimumTxnInLedgerStandalone, section); + set(setup.targetTxnInLedger, Keys::kTargetTxnInLedger, section); std::uint32_t max = 0; - if (set(max, "maximum_txn_in_ledger", section)) + if (set(max, Keys::kMaximumTxnInLedger, section)) { if (max < setup.minimumTxnInLedger) { @@ -1909,7 +1910,7 @@ setupTxQ(Config const& config) moot. (There are other ways to do that, including minimum_txn_in_ledger_.) */ - set(setup.normalConsensusIncreasePercent, "normal_consensus_increase_percent", section); + set(setup.normalConsensusIncreasePercent, Keys::kNormalConsensusIncreasePercent, section); setup.normalConsensusIncreasePercent = std::clamp(setup.normalConsensusIncreasePercent, 0u, 1000u); @@ -1917,11 +1918,11 @@ setupTxQ(Config const& config) are nonsensical (uint overflows happen, so the limit grows instead of shrinking). 0 is not recommended. */ - set(setup.slowConsensusDecreasePercent, "slow_consensus_decrease_percent", section); + set(setup.slowConsensusDecreasePercent, Keys::kSlowConsensusDecreasePercent, section); setup.slowConsensusDecreasePercent = std::clamp(setup.slowConsensusDecreasePercent, 0u, 100u); - set(setup.maximumTxnPerAccount, "maximum_txn_per_account", section); - set(setup.minimumLastLedgerBuffer, "minimum_last_ledger_buffer", section); + set(setup.maximumTxnPerAccount, Keys::kMaximumTxnPerAccount, section); + set(setup.minimumLastLedgerBuffer, Keys::kMinimumLastLedgerBuffer, section); setup.standAlone = config.standalone(); return setup; diff --git a/src/xrpld/app/misc/detail/ValidatorKeys.cpp b/src/xrpld/app/misc/detail/ValidatorKeys.cpp index 0380e2e294..e439eca936 100644 --- a/src/xrpld/app/misc/detail/ValidatorKeys.cpp +++ b/src/xrpld/app/misc/detail/ValidatorKeys.cpp @@ -1,11 +1,11 @@ #include #include -#include #include #include #include +#include #include #include #include @@ -17,18 +17,18 @@ namespace xrpl { ValidatorKeys::ValidatorKeys(Config const& config, beast::Journal j) { - if (config.exists(SECTION_VALIDATOR_TOKEN) && config.exists(SECTION_VALIDATION_SEED)) + if (config.exists(Sections::kValidatorToken) && config.exists(Sections::kValidationSeed)) { configInvalid_ = true; - JLOG(j.fatal()) << "Cannot specify both [" SECTION_VALIDATION_SEED - "] and [" SECTION_VALIDATOR_TOKEN "]"; + JLOG(j.fatal()) << "Cannot specify both [" << Sections::kValidationSeed << "] and [" + << Sections::kValidatorToken << "]"; return; } - if (config.exists(SECTION_VALIDATOR_TOKEN)) + if (config.exists(Sections::kValidatorToken)) { // token is non-const so it can be moved from - if (auto token = loadValidatorToken(config.section(SECTION_VALIDATOR_TOKEN).lines())) + if (auto token = loadValidatorToken(config.section(Sections::kValidatorToken).lines())) { auto const pk = derivePublicKey(KeyType::Secp256k1, token->validationSecret); auto const m = deserializeManifest(base64Decode(token->manifest)); @@ -36,7 +36,8 @@ ValidatorKeys::ValidatorKeys(Config const& config, beast::Journal j) if (!m || pk != m->signingKey) { configInvalid_ = true; - JLOG(j.fatal()) << "Invalid token specified in [" SECTION_VALIDATOR_TOKEN "]"; + JLOG(j.fatal()) << "Invalid token specified in [" << Sections::kValidatorToken + << "]"; } else { @@ -49,17 +50,17 @@ ValidatorKeys::ValidatorKeys(Config const& config, beast::Journal j) else { configInvalid_ = true; - JLOG(j.fatal()) << "Invalid token specified in [" SECTION_VALIDATOR_TOKEN "]"; + JLOG(j.fatal()) << "Invalid token specified in [" << Sections::kValidatorToken << "]"; } } - else if (config.exists(SECTION_VALIDATION_SEED)) + else if (config.exists(Sections::kValidationSeed)) { auto const seed = - parseBase58(config.section(SECTION_VALIDATION_SEED).lines().front()); + parseBase58(config.section(Sections::kValidationSeed).lines().front()); if (!seed) { configInvalid_ = true; - JLOG(j.fatal()) << "Invalid seed specified in [" SECTION_VALIDATION_SEED "]"; + JLOG(j.fatal()) << "Invalid seed specified in [" << Sections::kValidationSeed << "]"; } else { diff --git a/src/xrpld/app/misc/detail/setup_HashRouter.cpp b/src/xrpld/app/misc/detail/setup_HashRouter.cpp index 9727f5d733..97aa501698 100644 --- a/src/xrpld/app/misc/detail/setup_HashRouter.cpp +++ b/src/xrpld/app/misc/detail/setup_HashRouter.cpp @@ -2,8 +2,9 @@ #include -#include #include +#include +#include #include #include @@ -18,11 +19,11 @@ setupHashRouter(Config const& config) using namespace std::chrono; HashRouter::Setup setup; - auto const& section = config.section("hashrouter"); + auto const& section = config.section(Sections::kHashrouter); std::int32_t tmp{}; - if (set(tmp, "hold_time", section)) + if (set(tmp, Keys::kHoldTime, section)) { if (tmp < 12) { @@ -32,7 +33,7 @@ setupHashRouter(Config const& config) } setup.holdTime = seconds(tmp); } - if (set(tmp, "relay_time", section)) + if (set(tmp, Keys::kRelayTime, section)) { if (tmp < 8) { diff --git a/src/xrpld/app/rdb/backend/detail/Node.cpp b/src/xrpld/app/rdb/backend/detail/Node.cpp index 1fd3136420..9b7db6f0f5 100644 --- a/src/xrpld/app/rdb/backend/detail/Node.cpp +++ b/src/xrpld/app/rdb/backend/detail/Node.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include // IWYU pragma: keep @@ -1291,7 +1292,7 @@ bool dbHasSpace(soci::session& session, Config const& config, beast::Journal j) { boost::filesystem::space_info const space = - boost::filesystem::space(config.legacy("database_path")); + boost::filesystem::space(config.legacy(Sections::kDatabasePath)); if (space.available < megabytes(512)) { diff --git a/src/xrpld/app/rdb/detail/PeerFinder.cpp b/src/xrpld/app/rdb/detail/PeerFinder.cpp index 2481b63d2b..9452c3af99 100644 --- a/src/xrpld/app/rdb/detail/PeerFinder.cpp +++ b/src/xrpld/app/rdb/detail/PeerFinder.cpp @@ -2,11 +2,11 @@ #include -#include #include #include #include #include +#include #include #include // IWYU pragma: keep diff --git a/src/xrpld/core/Config.h b/src/xrpld/core/Config.h index a18b68a508..45b36808b2 100644 --- a/src/xrpld/core/Config.h +++ b/src/xrpld/core/Config.h @@ -1,9 +1,9 @@ #pragma once -#include #include #include #include +#include #include #include #include // VFALCO Breaks levelization diff --git a/src/xrpld/core/ConfigSections.h b/src/xrpld/core/ConfigSections.h deleted file mode 100644 index 7f22dd59c1..0000000000 --- a/src/xrpld/core/ConfigSections.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -namespace xrpl { - -// VFALCO DEPRECATED in favor of the BasicConfig interface -struct ConfigSection -{ - explicit ConfigSection() = default; - - static std::string - nodeDatabase() - { - return "node_db"; - } - static std::string - importNodeDatabase() - { - return "import_db"; - } -}; - -// VFALCO TODO Rename and replace these macros with variables. -#define SECTION_AMENDMENTS "amendments" -#define SECTION_AMENDMENT_MAJORITY_TIME "amendment_majority_time" -#define SECTION_BETA_RPC_API "beta_rpc_api" -#define SECTION_CLUSTER_NODES "cluster_nodes" -#define SECTION_COMPRESSION "compression" -#define SECTION_DEBUG_LOGFILE "debug_logfile" -#define SECTION_ELB_SUPPORT "elb_support" -#define SECTION_FEE_DEFAULT "fee_default" -#define SECTION_FETCH_DEPTH "fetch_depth" -#define SECTION_INSIGHT "insight" -#define SECTION_IO_WORKERS "io_workers" -#define SECTION_IPS "ips" -#define SECTION_IPS_FIXED "ips_fixed" -#define SECTION_LEDGER_HISTORY "ledger_history" -#define SECTION_LEDGER_REPLAY "ledger_replay" -#define SECTION_MAX_TRANSACTIONS "max_transactions" -#define SECTION_NETWORK_ID "network_id" -#define SECTION_NETWORK_QUORUM "network_quorum" -#define SECTION_NODE_SEED "node_seed" -#define SECTION_NODE_SIZE "node_size" -#define SECTION_OVERLAY "overlay" -#define SECTION_PATH_SEARCH_OLD "path_search_old" -#define SECTION_PATH_SEARCH "path_search" -#define SECTION_PATH_SEARCH_FAST "path_search_fast" -#define SECTION_PATH_SEARCH_MAX "path_search_max" -#define SECTION_PEER_PRIVATE "peer_private" -#define SECTION_PEERS_MAX "peers_max" -#define SECTION_PEERS_IN_MAX "peers_in_max" -#define SECTION_PEERS_OUT_MAX "peers_out_max" -#define SECTION_PORT_GRPC "port_grpc" -#define SECTION_PREFETCH_WORKERS "prefetch_workers" -#define SECTION_REDUCE_RELAY "reduce_relay" -#define SECTION_RELATIONAL_DB "relational_db" -#define SECTION_RELAY_PROPOSALS "relay_proposals" -#define SECTION_RELAY_VALIDATIONS "relay_validations" -#define SECTION_RPC_STARTUP "rpc_startup" -#define SECTION_SIGNING_SUPPORT "signing_support" -#define SECTION_SNTP "sntp_servers" -#define SECTION_SSL_VERIFY "ssl_verify" -#define SECTION_SSL_VERIFY_FILE "ssl_verify_file" -#define SECTION_SSL_VERIFY_DIR "ssl_verify_dir" -#define SECTION_SERVER_DOMAIN "server_domain" -#define SECTION_SWEEP_INTERVAL "sweep_interval" -#define SECTION_VALIDATORS_FILE "validators_file" -#define SECTION_VALIDATION_SEED "validation_seed" -#define SECTION_VALIDATOR_KEYS "validator_keys" -#define SECTION_VALIDATOR_KEY_REVOCATION "validator_key_revocation" -#define SECTION_VALIDATOR_LIST_KEYS "validator_list_keys" -#define SECTION_VALIDATOR_LIST_SITES "validator_list_sites" -#define SECTION_VALIDATOR_LIST_THRESHOLD "validator_list_threshold" -#define SECTION_VALIDATORS "validators" -#define SECTION_VALIDATOR_TOKEN "validator_token" -#define SECTION_VETO_AMENDMENTS "veto_amendments" -#define SECTION_WORKERS "workers" - -} // namespace xrpl diff --git a/src/xrpld/core/detail/Config.cpp b/src/xrpld/core/detail/Config.cpp index 6eedc43edd..1b2449823e 100644 --- a/src/xrpld/core/detail/Config.cpp +++ b/src/xrpld/core/detail/Config.cpp @@ -1,8 +1,5 @@ #include -#include - -#include #include #include #include @@ -11,6 +8,8 @@ #include #include #include +#include +#include #include #include #include @@ -385,7 +384,7 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand load(); { // load() may have set a new value for the dataDir - std::string const dbPath(legacy("database_path")); + std::string const dbPath(legacy(Sections::kDatabasePath)); if (!dbPath.empty()) { dataDir = boost::filesystem::path(dbPath); @@ -404,7 +403,7 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand if (ec) Throw(boost::str(boost::format("Can not create %s") % dataDir)); - legacy("database_path", boost::filesystem::absolute(dataDir).string()); + legacy(Sections::kDatabasePath, boost::filesystem::absolute(dataDir).string()); } HTTPClient::initializeSSLContext(this->sslVerifyDir, this->sslVerifyFile, this->sslVerify, j_); @@ -412,11 +411,11 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand if (runStandalone_) ledgerHistory = 0; - Section const ledgerTxTablesSection = section("ledger_tx_tables"); - getIfExists(ledgerTxTablesSection, "use_tx_tables", useTxTables_); + Section const ledgerTxTablesSection = section(Sections::kLedgerTxTables); + getIfExists(ledgerTxTablesSection, Keys::kUseTxTables, useTxTables_); - Section const& nodeDbSection{section(ConfigSection::nodeDatabase())}; - getIfExists(nodeDbSection, "fast_load", fastLoad); + Section const& nodeDbSection{section(Sections::kNodeDatabase)}; + getIfExists(nodeDbSection, Keys::kFastLoad, fastLoad); } // 0 ports are allowed for unit tests, but still not allowed to be present in @@ -424,16 +423,16 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand static void checkZeroPorts(Config const& config) { - if (!config.exists("server")) + if (!config.exists(Sections::kServer)) return; - for (auto const& name : config.section("server").values()) + for (auto const& name : config.section(Sections::kServer).values()) { if (!config.exists(name)) return; auto const& section = config[name]; - auto const optResult = section.get("port"); + auto const optResult = section.get(Keys::kPort); if (optResult) { auto const port = beast::lexicalCast(*optResult); @@ -477,10 +476,10 @@ Config::loadFromString(std::string const& fileContents) build(secConfig); - if (auto s = getIniFileSection(secConfig, SECTION_IPS)) + if (auto s = getIniFileSection(secConfig, Sections::kIps)) ips = *s; - if (auto s = getIniFileSection(secConfig, SECTION_IPS_FIXED)) + if (auto s = getIniFileSection(secConfig, Sections::kIpsFixed)) ipsFixed = *s; // if the user has specified ip:port then replace : with a space. @@ -507,16 +506,16 @@ Config::loadFromString(std::string const& fileContents) { std::string dbPath; - if (getSingleSection(secConfig, "database_path", dbPath, j_)) + if (getSingleSection(secConfig, Sections::kDatabasePath, dbPath, j_)) { boost::filesystem::path const p(dbPath); - legacy("database_path", boost::filesystem::absolute(p).string()); + legacy(Sections::kDatabasePath, boost::filesystem::absolute(p).string()); } } std::string strTemp; - if (getSingleSection(secConfig, SECTION_NETWORK_ID, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kNetworkId, strTemp, j_)) { if (strTemp == "main") { @@ -536,43 +535,45 @@ Config::loadFromString(std::string const& fileContents) } } - if (getSingleSection(secConfig, SECTION_PEER_PRIVATE, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kPeerPrivate, strTemp, j_)) peerPrivate = beast::lexicalCastThrow(strTemp); - if (getSingleSection(secConfig, SECTION_PEERS_MAX, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kPeersMax, strTemp, j_)) { peersMax = beast::lexicalCastThrow(strTemp); } else { std::optional peersInMaxOpt{}; - if (getSingleSection(secConfig, SECTION_PEERS_IN_MAX, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kPeersInMax, strTemp, j_)) { peersInMaxOpt = beast::lexicalCastThrow(strTemp); if (*peersInMaxOpt > 1000) { - Throw("Invalid value specified in [" SECTION_PEERS_IN_MAX - "] section; the value must be less or equal than 1000"); + Throw( + std::string("Invalid value specified in [") + Sections::kPeersInMax + + "] section; the value must be less or equal than 1000"); } } std::optional peersOutMaxOpt{}; - if (getSingleSection(secConfig, SECTION_PEERS_OUT_MAX, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kPeersOutMax, strTemp, j_)) { peersOutMaxOpt = beast::lexicalCastThrow(strTemp); if (*peersOutMaxOpt < 10 || *peersOutMaxOpt > 1000) { - Throw("Invalid value specified in [" SECTION_PEERS_OUT_MAX - "] section; the value must be in range 10-1000"); + Throw( + std::string("Invalid value specified in [") + Sections::kPeersOutMax + + "] section; the value must be in range 10-1000"); } } // if one section is configured then the other must be configured too if ((peersInMaxOpt && !peersOutMaxOpt) || (peersOutMaxOpt && !peersInMaxOpt)) { - Throw("Both sections [" SECTION_PEERS_IN_MAX - "]" - "and [" SECTION_PEERS_OUT_MAX "] must be configured"); + Throw( + std::string("Both sections [") + Sections::kPeersInMax + "]" + " and [" + + Sections::kPeersOutMax + "] must be configured"); } if (peersInMaxOpt && peersOutMaxOpt) @@ -582,7 +583,7 @@ Config::loadFromString(std::string const& fileContents) } } - if (getSingleSection(secConfig, SECTION_NODE_SIZE, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kNodeSize, strTemp, j_)) { if (boost::iequals(strTemp, "tiny")) { @@ -610,19 +611,19 @@ Config::loadFromString(std::string const& fileContents) } } - if (getSingleSection(secConfig, SECTION_SIGNING_SUPPORT, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kSigningSupport, strTemp, j_)) signingEnabled_ = beast::lexicalCastThrow(strTemp); - if (getSingleSection(secConfig, SECTION_ELB_SUPPORT, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kElbSupport, strTemp, j_)) elbSupport = beast::lexicalCastThrow(strTemp); - getSingleSection(secConfig, SECTION_SSL_VERIFY_FILE, sslVerifyFile, j_); - getSingleSection(secConfig, SECTION_SSL_VERIFY_DIR, sslVerifyDir, j_); + getSingleSection(secConfig, Sections::kSslVerifyFile, sslVerifyFile, j_); + getSingleSection(secConfig, Sections::kSslVerifyDir, sslVerifyDir, j_); - if (getSingleSection(secConfig, SECTION_SSL_VERIFY, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kSslVerify, strTemp, j_)) sslVerify = beast::lexicalCastThrow(strTemp); - if (getSingleSection(secConfig, SECTION_RELAY_VALIDATIONS, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kRelayValidations, strTemp, j_)) { if (boost::iequals(strTemp, "all")) { @@ -638,12 +639,13 @@ Config::loadFromString(std::string const& fileContents) } else { - Throw("Invalid value specified in [" SECTION_RELAY_VALIDATIONS - "] section"); + Throw( + std::string("Invalid value specified in [") + Sections::kRelayValidations + + "] section"); } } - if (getSingleSection(secConfig, SECTION_RELAY_PROPOSALS, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kRelayProposals, strTemp, j_)) { if (boost::iequals(strTemp, "all")) { @@ -659,28 +661,30 @@ Config::loadFromString(std::string const& fileContents) } else { - Throw("Invalid value specified in [" SECTION_RELAY_PROPOSALS - "] section"); + Throw( + std::string("Invalid value specified in [") + Sections::kRelayProposals + + "] section"); } } - if (exists(SECTION_VALIDATION_SEED) && exists(SECTION_VALIDATOR_TOKEN)) + if (exists(Sections::kValidationSeed) && exists(Sections::kValidatorToken)) { - Throw("Cannot have both [" SECTION_VALIDATION_SEED - "] and [" SECTION_VALIDATOR_TOKEN "] config sections"); + Throw( + std::string("Cannot have both [") + Sections::kValidationSeed + "] and [" + + Sections::kValidatorToken + "] config sections"); } - if (getSingleSection(secConfig, SECTION_NETWORK_QUORUM, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kNetworkQuorum, strTemp, j_)) networkQuorum = beast::lexicalCastThrow(strTemp); - fees = setupFeeVote(section("voting")); + fees = setupFeeVote(section(Sections::kVoting)); /* [fee_default] is documented in the example config files as useful for * things like offline transaction signing. Until that's completely * deprecated, allow it to override the [voting] section. */ - if (getSingleSection(secConfig, SECTION_FEE_DEFAULT, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kFeeDefault, strTemp, j_)) fees.referenceFee = beast::lexicalCastThrow(strTemp); - if (getSingleSection(secConfig, SECTION_LEDGER_HISTORY, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kLedgerHistory, strTemp, j_)) { if (boost::iequals(strTemp, "full")) { @@ -696,7 +700,7 @@ Config::loadFromString(std::string const& fileContents) } } - if (getSingleSection(secConfig, SECTION_FETCH_DEPTH, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kFetchDepth, strTemp, j_)) { if (boost::iequals(strTemp, "none")) { @@ -716,74 +720,78 @@ Config::loadFromString(std::string const& fileContents) // By default, validators don't have pathfinding enabled, unless it is // explicitly requested by the server's admin. - if (exists(SECTION_VALIDATION_SEED) || exists(SECTION_VALIDATOR_TOKEN)) + if (exists(Sections::kValidationSeed) || exists(Sections::kValidatorToken)) pathSearchMax = 0; - if (getSingleSection(secConfig, SECTION_PATH_SEARCH_OLD, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kPathSearchOld, strTemp, j_)) pathSearchOld = beast::lexicalCastThrow(strTemp); - if (getSingleSection(secConfig, SECTION_PATH_SEARCH, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kPathSearch, strTemp, j_)) pathSearch = beast::lexicalCastThrow(strTemp); - if (getSingleSection(secConfig, SECTION_PATH_SEARCH_FAST, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kPathSearchFast, strTemp, j_)) pathSearchFast = beast::lexicalCastThrow(strTemp); - if (getSingleSection(secConfig, SECTION_PATH_SEARCH_MAX, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kPathSearchMax, strTemp, j_)) pathSearchMax = beast::lexicalCastThrow(strTemp); - if (getSingleSection(secConfig, SECTION_DEBUG_LOGFILE, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kDebugLogfile, strTemp, j_)) debugLogfile_ = strTemp; - if (getSingleSection(secConfig, SECTION_SWEEP_INTERVAL, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kSweepInterval, strTemp, j_)) { sweepInterval = beast::lexicalCastThrow(strTemp); if (sweepInterval < 10 || sweepInterval > 600) { - Throw("Invalid " SECTION_SWEEP_INTERVAL - ": must be between 10 and 600 inclusive"); + Throw( + std::string("Invalid ") + Sections::kSweepInterval + + ": must be between 10 and 600 inclusive"); } } - if (getSingleSection(secConfig, SECTION_WORKERS, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kWorkers, strTemp, j_)) { workers = beast::lexicalCastThrow(strTemp); if (workers < 1 || workers > 1024) { - Throw("Invalid " SECTION_WORKERS - ": must be between 1 and 1024 inclusive."); + Throw( + std::string("Invalid ") + Sections::kWorkers + + ": must be between 1 and 1024 inclusive."); } } - if (getSingleSection(secConfig, SECTION_IO_WORKERS, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kIoWorkers, strTemp, j_)) { ioWorkers = beast::lexicalCastThrow(strTemp); if (ioWorkers < 1 || ioWorkers > 1024) { - Throw("Invalid " SECTION_IO_WORKERS - ": must be between 1 and 1024 inclusive."); + Throw( + std::string("Invalid ") + Sections::kIoWorkers + + ": must be between 1 and 1024 inclusive."); } } - if (getSingleSection(secConfig, SECTION_PREFETCH_WORKERS, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kPrefetchWorkers, strTemp, j_)) { prefetchWorkers = beast::lexicalCastThrow(strTemp); if (prefetchWorkers < 1 || prefetchWorkers > 1024) { - Throw("Invalid " SECTION_PREFETCH_WORKERS - ": must be between 1 and 1024 inclusive."); + Throw( + std::string("Invalid ") + Sections::kPrefetchWorkers + + ": must be between 1 and 1024 inclusive."); } } - if (getSingleSection(secConfig, SECTION_COMPRESSION, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kCompression, strTemp, j_)) compression = beast::lexicalCastThrow(strTemp); - if (getSingleSection(secConfig, SECTION_LEDGER_REPLAY, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kLedgerReplay, strTemp, j_)) ledgerReplay = beast::lexicalCastThrow(strTemp); - if (exists(SECTION_REDUCE_RELAY)) + if (exists(Sections::kReduceRelay)) { - auto sec = section(SECTION_REDUCE_RELAY); + auto sec = section(Sections::kReduceRelay); ///////////////////// !!TEMPORARY CODE BLOCK!! //////////////////////// // vp_enable config option is deprecated by vp_base_squelch_enable // @@ -791,22 +799,23 @@ Config::loadFromString(std::string const& fileContents) // is the default algorithm, it must be replaced with: // // VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE = // // sec.value_or("vp_base_squelch_enable", true); // - if (sec.exists("vp_base_squelch_enable") && sec.exists("vp_enable")) + if (sec.exists(Keys::kVpBaseSquelchEnable) && sec.exists(Keys::kVpEnable)) { - Throw("Invalid " SECTION_REDUCE_RELAY - " cannot specify both vp_base_squelch_enable and vp_enable " - "options. " - "vp_enable was deprecated and replaced by " - "vp_base_squelch_enable"); + Throw( + std::string("Invalid ") + Sections::kReduceRelay + + " cannot specify both vp_base_squelch_enable and vp_enable " + "options. " + "vp_enable was deprecated and replaced by " + "vp_base_squelch_enable"); } - if (sec.exists("vp_base_squelch_enable")) + if (sec.exists(Keys::kVpBaseSquelchEnable)) { - vpReduceRelayBaseSquelchEnable = sec.valueOr("vp_base_squelch_enable", false); + vpReduceRelayBaseSquelchEnable = sec.valueOr(Keys::kVpBaseSquelchEnable, false); } - else if (sec.exists("vp_enable")) + else if (sec.exists(Keys::kVpEnable)) { - vpReduceRelayBaseSquelchEnable = sec.valueOr("vp_enable", false); + vpReduceRelayBaseSquelchEnable = sec.valueOr(Keys::kVpEnable, false); } else { @@ -818,97 +827,103 @@ Config::loadFromString(std::string const& fileContents) // Temporary squelching config for the peers selected as a source of // // validator messages. The config must be removed once squelching is // // made the default routing algorithm. // - vpReduceRelaySquelchMaxSelectedPeers = sec.valueOr("vp_base_squelch_max_selected_peers", 5); + vpReduceRelaySquelchMaxSelectedPeers = sec.valueOr(Keys::kVpBaseSquelchMaxSelectedPeers, 5); if (vpReduceRelaySquelchMaxSelectedPeers < 3) { - Throw("Invalid " SECTION_REDUCE_RELAY - " vp_base_squelch_max_selected_peers must be " - "greater than or equal to 3"); + Throw( + std::string("Invalid ") + Sections::kReduceRelay + + " vp_base_squelch_max_selected_peers must be " + "greater than or equal to 3"); } ///////////////// !!END OF TEMPORARY CODE BLOCK!! ///////////////////// - txReduceRelayEnable = sec.valueOr("tx_enable", false); - txReduceRelayMetrics = sec.valueOr("tx_metrics", false); - txReduceRelayMinPeers = sec.valueOr("tx_min_peers", 20); - txRelayPercentage = sec.valueOr("tx_relay_percentage", 25); + txReduceRelayEnable = sec.valueOr(Keys::kTxEnable, false); + txReduceRelayMetrics = sec.valueOr(Keys::kTxMetrics, false); + txReduceRelayMinPeers = sec.valueOr(Keys::kTxMinPeers, 20); + txRelayPercentage = sec.valueOr(Keys::kTxRelayPercentage, 25); if (txRelayPercentage < 10 || txRelayPercentage > 100 || txReduceRelayMinPeers < 10) { - Throw("Invalid " SECTION_REDUCE_RELAY - ", tx_min_peers must be greater than or equal to 10" - ", tx_relay_percentage must be greater than or equal to 10 " - "and less than or equal to 100"); + Throw( + std::string("Invalid ") + Sections::kReduceRelay + + ", tx_min_peers must be greater than or equal to 10" + ", tx_relay_percentage must be greater than or equal to 10 " + "and less than or equal to 100"); } } - if (getSingleSection(secConfig, SECTION_MAX_TRANSACTIONS, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kMaxTransactions, strTemp, j_)) { maxTransactions = std::clamp(beast::lexicalCastThrow(strTemp), kMinJobQueueTx, kMaxJobQueueTx); } - if (getSingleSection(secConfig, SECTION_SERVER_DOMAIN, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kServerDomain, strTemp, j_)) { if (!isProperlyFormedTomlDomain(strTemp)) { Throw( - "Invalid " SECTION_SERVER_DOMAIN + std::string("Invalid ") + Sections::kServerDomain + ": the domain name does not appear to meet the requirements."); } serverDomain = strTemp; } - if (exists(SECTION_OVERLAY)) + if (exists(Sections::kOverlay)) { - auto const sec = section(SECTION_OVERLAY); + auto const sec = section(Sections::kOverlay); using namespace std::chrono; try { - if (auto val = sec.get("max_unknown_time")) + if (auto val = sec.get(Keys::kMaxUnknownTime)) maxUnknownTime = seconds{beast::lexicalCastThrow(*val)}; } catch (...) { - Throw("Invalid value 'max_unknown_time' in " SECTION_OVERLAY - ": must be of the form '' representing seconds."); + Throw( + std::string("Invalid value 'max_unknown_time' in ") + Sections::kOverlay + + ": must be of the form '' representing seconds."); } if (maxUnknownTime < seconds{300} || maxUnknownTime > seconds{1800}) { Throw( - "Invalid value 'max_unknown_time' in " SECTION_OVERLAY + std::string("Invalid value 'max_unknown_time' in ") + Sections::kOverlay + ": the time must be between 300 and 1800 seconds, inclusive."); } try { - if (auto val = sec.get("max_diverged_time")) + if (auto val = sec.get(Keys::kMaxDivergedTime)) maxDivergedTime = seconds{beast::lexicalCastThrow(*val)}; } catch (...) { - Throw("Invalid value 'max_diverged_time' in " SECTION_OVERLAY - ": must be of the form '' representing seconds."); + Throw( + std::string("Invalid value 'max_diverged_time' in ") + Sections::kOverlay + + ": must be of the form '' representing seconds."); } if (maxDivergedTime < seconds{60} || maxDivergedTime > seconds{900}) { - Throw("Invalid value 'max_diverged_time' in " SECTION_OVERLAY - ": the time must be between 60 and 900 seconds, inclusive."); + Throw( + std::string("Invalid value 'max_diverged_time' in ") + Sections::kOverlay + + ": the time must be between 60 and 900 seconds, inclusive."); } } - if (getSingleSection(secConfig, SECTION_AMENDMENT_MAJORITY_TIME, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kAmendmentMajorityTime, strTemp, j_)) { using namespace std::chrono; boost::regex const re("^\\s*(\\d+)\\s*(minutes|hours|days|weeks)\\s*(\\s+.*)?$"); boost::smatch match; if (!boost::regex_match(strTemp, match, re)) { - Throw("Invalid " SECTION_AMENDMENT_MAJORITY_TIME - ", must be: [0-9]+ [minutes|hours|days|weeks]"); + Throw( + std::string("Invalid ") + Sections::kAmendmentMajorityTime + + ", must be: [0-9]+ [minutes|hours|days|weeks]"); } std::uint32_t const duration = beast::lexicalCastThrow(match[1].str()); @@ -932,13 +947,14 @@ Config::loadFromString(std::string const& fileContents) if (amendmentMajorityTime < minutes(15)) { - Throw("Invalid " SECTION_AMENDMENT_MAJORITY_TIME - ", the minimum amount of time an amendment must hold a " - "majority is 15 minutes"); + Throw( + std::string("Invalid ") + Sections::kAmendmentMajorityTime + + ", the minimum amount of time an amendment must hold a " + "majority is 15 minutes"); } } - if (getSingleSection(secConfig, SECTION_BETA_RPC_API, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kBetaRpcApi, strTemp, j_)) betaRpcApi = beast::lexicalCastThrow(strTemp); // Do not load trusted validator configuration for standalone mode @@ -954,14 +970,14 @@ Config::loadFromString(std::string const& fileContents) // if we can't find it. boost::filesystem::path validatorsFile; - if (getSingleSection(secConfig, SECTION_VALIDATORS_FILE, strTemp, j_)) + if (getSingleSection(secConfig, Sections::kValidatorsFile, strTemp, j_)) { validatorsFile = strTemp; if (validatorsFile.empty()) { - Throw("Invalid path specified in [" SECTION_VALIDATORS_FILE - "]"); + Throw( + std::string("Invalid path specified in [") + Sections::kValidatorsFile + "]"); } if (!validatorsFile.is_absolute() && !configDir.empty()) @@ -970,7 +986,7 @@ Config::loadFromString(std::string const& fileContents) if (!boost::filesystem::exists(validatorsFile)) { Throw( - "The file specified in [" SECTION_VALIDATORS_FILE + std::string("The file specified in [") + Sections::kValidatorsFile + "] " "does not exist: " + validatorsFile.string()); @@ -980,8 +996,8 @@ Config::loadFromString(std::string const& fileContents) !boost::filesystem::is_symlink(validatorsFile)) { Throw( - "Invalid file specified in [" SECTION_VALIDATORS_FILE "]: " + - validatorsFile.string()); + std::string("Invalid file specified in [") + Sections::kValidatorsFile + + "]: " + validatorsFile.string()); } } else if (!configDir.empty()) @@ -1018,41 +1034,44 @@ Config::loadFromString(std::string const& fileContents) auto iniFile = parseIniFile(data, true); - auto entries = getIniFileSection(iniFile, SECTION_VALIDATORS); + auto entries = getIniFileSection(iniFile, Sections::kValidators); if (entries != nullptr) - section(SECTION_VALIDATORS).append(*entries); + section(Sections::kValidators).append(*entries); - auto valKeyEntries = getIniFileSection(iniFile, SECTION_VALIDATOR_KEYS); + auto valKeyEntries = getIniFileSection(iniFile, Sections::kValidatorKeys); if (valKeyEntries != nullptr) - section(SECTION_VALIDATOR_KEYS).append(*valKeyEntries); + section(Sections::kValidatorKeys).append(*valKeyEntries); - auto valSiteEntries = getIniFileSection(iniFile, SECTION_VALIDATOR_LIST_SITES); + auto valSiteEntries = getIniFileSection(iniFile, Sections::kValidatorListSites); if (valSiteEntries != nullptr) - section(SECTION_VALIDATOR_LIST_SITES).append(*valSiteEntries); + section(Sections::kValidatorListSites).append(*valSiteEntries); - auto valListKeys = getIniFileSection(iniFile, SECTION_VALIDATOR_LIST_KEYS); + auto valListKeys = getIniFileSection(iniFile, Sections::kValidatorListKeys); if (valListKeys != nullptr) - section(SECTION_VALIDATOR_LIST_KEYS).append(*valListKeys); + section(Sections::kValidatorListKeys).append(*valListKeys); - auto valListThreshold = getIniFileSection(iniFile, SECTION_VALIDATOR_LIST_THRESHOLD); + auto valListThreshold = getIniFileSection(iniFile, Sections::kValidatorListThreshold); if (valListThreshold != nullptr) - section(SECTION_VALIDATOR_LIST_THRESHOLD).append(*valListThreshold); + section(Sections::kValidatorListThreshold).append(*valListThreshold); if ((entries == nullptr) && (valKeyEntries == nullptr) && (valListKeys == nullptr)) { Throw( - "The file specified in [" SECTION_VALIDATORS_FILE + std::string("The file specified in [") + Sections::kValidatorsFile + "] " - "does not contain a [" SECTION_VALIDATORS + "does not contain a [" + + Sections::kValidators + "], " - "[" SECTION_VALIDATOR_KEYS + "[" + + Sections::kValidatorKeys + "] or " - "[" SECTION_VALIDATOR_LIST_KEYS + "[" + + Sections::kValidatorListKeys + "]" " section: " + validatorsFile.string()); @@ -1060,7 +1079,7 @@ Config::loadFromString(std::string const& fileContents) } validatorListThreshold = [&]() -> std::optional { - auto const& listThreshold = section(SECTION_VALIDATOR_LIST_THRESHOLD); + auto const& listThreshold = section(Sections::kValidatorListThreshold); if (listThreshold.lines().empty()) { return std::nullopt; @@ -1073,34 +1092,38 @@ Config::loadFromString(std::string const& fileContents) { return std::nullopt; // NOTE: Explicitly ask for computed } - if (listThreshold > section(SECTION_VALIDATOR_LIST_KEYS).values().size()) + if (listThreshold > section(Sections::kValidatorListKeys).values().size()) { Throw( - "Value in config section " - "[" SECTION_VALIDATOR_LIST_THRESHOLD + std::string( + "Value in config section " + "[") + + Sections::kValidatorListThreshold + "] exceeds the number of configured list keys"); } return listThreshold; } Throw( - "Config section " - "[" SECTION_VALIDATOR_LIST_THRESHOLD "] should contain single value only"); + std::string( + "Config section " + "[") + + Sections::kValidatorListThreshold + "] should contain single value only"); }(); // Consolidate [validator_keys] and [validators] - section(SECTION_VALIDATORS).append(section(SECTION_VALIDATOR_KEYS).lines()); + section(Sections::kValidators).append(section(Sections::kValidatorKeys).lines()); - if (!section(SECTION_VALIDATOR_LIST_SITES).lines().empty() && - section(SECTION_VALIDATOR_LIST_KEYS).lines().empty()) + if (!section(Sections::kValidatorListSites).lines().empty() && + section(Sections::kValidatorListKeys).lines().empty()) { Throw( - "[" + std::string(SECTION_VALIDATOR_LIST_KEYS) + "] config section is missing"); + "[" + std::string(Sections::kValidatorListKeys) + "] config section is missing"); } } { - auto const part = section("features"); + auto const part = section(Sections::kFeatures); for (auto const& s : part.values()) { if (auto const f = getRegisteredFeature(s)) @@ -1182,15 +1205,15 @@ setupFeeVote(Section const& section) FeeSetup setup; { std::uint64_t temp = 0; - if (set(temp, "reference_fee", section) && + if (set(temp, Keys::kReferenceFee, section) && temp <= std::numeric_limits::max()) setup.referenceFee = temp; } { std::uint32_t temp = 0; - if (set(temp, "account_reserve", section)) + if (set(temp, Keys::kAccountReserve, section)) setup.accountReserve = temp; - if (set(temp, "owner_reserve", section)) + if (set(temp, Keys::kOwnerReserve, section)) setup.ownerReserve = temp; } return setup; @@ -1203,7 +1226,7 @@ setupDatabaseCon(Config const& c, std::optional j) setup.startUp = c.startUp; setup.standAlone = c.standalone(); - setup.dataDir = c.legacy("database_path"); + setup.dataDir = c.legacy(Sections::kDatabasePath); if (!setup.standAlone && setup.dataDir.empty()) { Throw("database_path must be set."); @@ -1211,7 +1234,7 @@ setupDatabaseCon(Config const& c, std::optional j) if (!setup.globalPragma) { - auto const& sqlite = c.section("sqlite"); + auto const& sqlite = c.section(Sections::kSqlite); auto result = std::make_unique>(); result->reserve(3); @@ -1328,11 +1351,11 @@ setupDatabaseCon(Config const& c, std::optional j) // TX Pragma int64_t pageSize = 4096; int64_t journalSizeLimit = 1582080; - if (c.exists("sqlite")) + if (c.exists(Sections::kSqlite)) { - auto& s = c.section("sqlite"); - set(journalSizeLimit, "journal_size_limit", s); - set(pageSize, "page_size", s); + auto& s = c.section(Sections::kSqlite); + set(journalSizeLimit, Keys::kJournalSizeLimit, s); + set(pageSize, Keys::kPageSize, s); if (pageSize < 512 || pageSize > 65536) Throw("Invalid page_size. Must be between 512 and 65536."); diff --git a/src/xrpld/overlay/Cluster.h b/src/xrpld/overlay/Cluster.h index 982f11aaae..a8c2083fbc 100644 --- a/src/xrpld/overlay/Cluster.h +++ b/src/xrpld/overlay/Cluster.h @@ -2,9 +2,9 @@ #include -#include #include #include +#include #include #include diff --git a/src/xrpld/overlay/detail/Cluster.cpp b/src/xrpld/overlay/detail/Cluster.cpp index 7855c9647d..15c8fa9c66 100644 --- a/src/xrpld/overlay/detail/Cluster.cpp +++ b/src/xrpld/overlay/detail/Cluster.cpp @@ -2,11 +2,11 @@ #include -#include #include #include #include #include +#include #include #include diff --git a/src/xrpld/overlay/detail/OverlayImpl.cpp b/src/xrpld/overlay/detail/OverlayImpl.cpp index b71cef6719..89c7dfe5eb 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.cpp +++ b/src/xrpld/overlay/detail/OverlayImpl.cpp @@ -16,7 +16,6 @@ #include #include -#include #include #include #include @@ -36,6 +35,8 @@ #include #include #include +#include +#include #include #include #include @@ -96,7 +97,7 @@ static constexpr auto kDisabled = 0; static constexpr auto kOverlay = (1 << 0); static constexpr auto kServerInfo = (1 << 1); static constexpr auto kServerCounts = (1 << 2); -static constexpr auto kUNL = (1 << 3); +static constexpr auto kUnl = (1 << 3); } // namespace CrawlOptions //------------------------------------------------------------------------------ @@ -885,7 +886,7 @@ OverlayImpl::processCrawl(http_request_type const& req, Handoff& handoff) { msg.body()["counts"] = getServerCounts(); } - if ((setup_.crawlOptions & CrawlOptions::kUNL) != 0u) + if ((setup_.crawlOptions & CrawlOptions::kUnl) != 0u) { msg.body()["unl"] = getUnlInfo(); } @@ -1516,7 +1517,7 @@ setupOverlay(BasicConfig const& config, beast::Journal j) Overlay::Setup setup; { - auto const& section = config.section("overlay"); + auto const& section = config.section(Sections::kOverlay); setup.context = makeSslContext(""); set(setup.ipLimit, "ip_limit", section); @@ -1543,7 +1544,7 @@ setupOverlay(BasicConfig const& config, beast::Journal j) } { - auto const& section = config.section("crawl"); + auto const& section = config.section(Sections::kCrawl); auto const& values = section.values(); if (values.size() > 1) @@ -1569,33 +1570,33 @@ setupOverlay(BasicConfig const& config, beast::Journal j) if (crawlEnabled) { - if (get(section, "overlay", true)) + if (get(section, Keys::kOverlay, true)) { setup.crawlOptions |= CrawlOptions::kOverlay; } - if (get(section, "server", true)) + if (get(section, Keys::kServer, true)) { setup.crawlOptions |= CrawlOptions::kServerInfo; } - if (get(section, "counts", false)) + if (get(section, Keys::kCounts, false)) { setup.crawlOptions |= CrawlOptions::kServerCounts; } - if (get(section, "unl", true)) + if (get(section, Keys::kUnl, true)) { - setup.crawlOptions |= CrawlOptions::kUNL; + setup.crawlOptions |= CrawlOptions::kUnl; } } } { - auto const& section = config.section("vl"); + auto const& section = config.section(Sections::kVl); set(setup.vlEnabled, "enabled", section); } try { - auto id = config.legacy("network_id"); + auto id = config.legacy(Sections::kNetworkId); if (!id.empty()) { diff --git a/src/xrpld/peerfinder/detail/PeerfinderManager.cpp b/src/xrpld/peerfinder/detail/PeerfinderManager.cpp index f51f3630eb..9dbedfe4f1 100644 --- a/src/xrpld/peerfinder/detail/PeerfinderManager.cpp +++ b/src/xrpld/peerfinder/detail/PeerfinderManager.cpp @@ -7,13 +7,13 @@ #include #include -#include #include #include #include #include #include #include +#include #include #include diff --git a/src/xrpld/perflog/detail/PerfLogImp.cpp b/src/xrpld/perflog/detail/PerfLogImp.cpp index 60b6efc0a9..5ace4d8c8b 100644 --- a/src/xrpld/perflog/detail/PerfLogImp.cpp +++ b/src/xrpld/perflog/detail/PerfLogImp.cpp @@ -1,11 +1,12 @@ #include -#include #include #include #include #include #include +#include +#include #include #include #include @@ -489,7 +490,7 @@ setupPerfLog(Section const& section, boost::filesystem::path const& configDir) } std::uint64_t logInterval = 0; - if (getIfExists(section, "log_interval", logInterval)) + if (getIfExists(section, Keys::kLogInterval, logInterval)) setup.logInterval = std::chrono::seconds(logInterval); return setup; } diff --git a/src/xrpld/rpc/detail/ServerHandler.cpp b/src/xrpld/rpc/detail/ServerHandler.cpp index c73e474e18..5177c85738 100644 --- a/src/xrpld/rpc/detail/ServerHandler.cpp +++ b/src/xrpld/rpc/detail/ServerHandler.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -16,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -1128,16 +1128,16 @@ parsePorts(Config const& config, std::ostream& log) { std::vector result; - if (!config.exists("server")) + if (!config.exists(Sections::kServer)) { log << "Required section [server] is missing"; Throw(); } ParsedPort common; - parsePort(common, config["server"], log); + parsePort(common, config[Sections::kServer], log); - auto const& names = config.section("server").values(); + auto const& names = config.section(Sections::kServer).values(); result.reserve(names.size()); for (auto const& name : names) { @@ -1149,7 +1149,7 @@ parsePorts(Config const& config, std::ostream& log) // grpc ports are parsed by GRPCServer class. Do not validate // grpc port information in this file. - if (name == SECTION_PORT_GRPC) + if (name == Sections::kPortGrpc) continue; ParsedPort parsed = common; From 0fb1aca461c775b2f5e0334dcff9ded3571634d7 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Tue, 9 Jun 2026 19:02:06 +0200 Subject: [PATCH 074/135] refactor: Introduce XRPL_ASSERT_IF for amendment-gated assertions (#7378) Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com> --- include/xrpl/beast/utility/instrumentation.h | 12 +++ src/libxrpl/ledger/helpers/LendingHelpers.cpp | 84 +++++++++---------- src/libxrpl/ledger/helpers/MPTokenHelpers.cpp | 8 +- 3 files changed, 57 insertions(+), 47 deletions(-) diff --git a/include/xrpl/beast/utility/instrumentation.h b/include/xrpl/beast/utility/instrumentation.h index 39b80bc438..c20c42156a 100644 --- a/include/xrpl/beast/utility/instrumentation.h +++ b/include/xrpl/beast/utility/instrumentation.h @@ -11,6 +11,8 @@ // Macros below are copied from antithesis_sdk.h and slightly simplified // The duplication is because Visual Studio 2019 cannot compile that header // even with the option -Zc:__cplusplus added. +// NOTE: cond must not contain bare commas outside () or []. Commas inside {} +// are not protected by the preprocessor and would be parsed as extra arguments. #define ALWAYS(cond, message, ...) assert((message) && (cond)) #define ALWAYS_OR_UNREACHABLE(cond, message) assert((message) && (cond)) #define SOMETIMES(cond, message, ...) @@ -22,6 +24,8 @@ #define XRPL_ASSERT_PARTS(cond, function, description, ...) \ XRPL_ASSERT(cond, function " : " description) +#define XRPL_ASSERT_IF(guard, cond, message) XRPL_ASSERT(!(guard) || (cond), message) + // How to use the instrumentation macros: // // * XRPL_ASSERT if cond must be true but the line might not be reached during @@ -29,6 +33,14 @@ // * 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. +// * XRPL_ASSERT_IF(guard, cond, message) asserts the implication +// `guard => cond`: it can only fail when guard is true (e.g. an amendment +// is enabled) and cond is false. Unlike `if (guard) XRPL_ASSERT(...)`, the +// assertion site is always evaluated, so the fuzzer registers it +// unconditionally; cond itself is short-circuited and only evaluated when +// guard is true. NOTE: do not rely on side effects in guard — in release +// builds the assertion body is stripped, and the compiler may optimize away +// a side-effect-free guard entirely. // * 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/src/libxrpl/ledger/helpers/LendingHelpers.cpp b/src/libxrpl/ledger/helpers/LendingHelpers.cpp index 15ebf33e46..0a195f9cbe 100644 --- a/src/libxrpl/ledger/helpers/LendingHelpers.cpp +++ b/src/libxrpl/ledger/helpers/LendingHelpers.cpp @@ -798,44 +798,44 @@ doOverpayment( // (P * factor) / factor round-trip can leave the new principal one // scale-unit high, so these equalities do not hold on the pre-amendment // code path and must be gated to match the fix they verify. - if (rules.enabled(fixCleanup3_2_0)) - { - // The valueChange returned by tryOverpayment satisfies - // valueChange = (newInterestDue - oldInterestDue) + untrackedInterest. - // Using the loan-state identity v = p + i + m and the adjacent - // `principal change agrees` assertion (dp = oldP - newP), this - // rearranges into three independently-computable terms: - // - // 1. TVO change beyond what principal repayment alone explains: - // newTVO - (oldTVO - dp) - // 2. Management fee released by re-amortization (positive when - // mfee decreased; zero when managementFeeRate == 0): - // oldMfee - newMfee - // 3. The overpayment's penalty interest part (= untrackedInterest - // for the overpayment path; see computeOverpaymentComponents): - // trackedInterestPart() - [[maybe_unused]] Number const tvoChange = newRoundedLoanState.valueOutstanding - - (totalValueOutstandingProxy - overpaymentComponents.trackedPrincipalDelta); - [[maybe_unused]] Number const managementFeeReleased = - managementFeeOutstandingProxy - newRoundedLoanState.managementFeeDue; - [[maybe_unused]] Number const interestPart = overpaymentComponents.trackedInterestPart(); + // + // The valueChange returned by tryOverpayment satisfies + // valueChange = (newInterestDue - oldInterestDue) + untrackedInterest. + // Using the loan-state identity v = p + i + m and the adjacent + // `principal change agrees` assertion (dp = oldP - newP), this + // rearranges into three independently-computable terms: + // + // 1. TVO change beyond what principal repayment alone explains: + // newTVO - (oldTVO - dp) + // 2. Management fee released by re-amortization (positive when + // mfee decreased; zero when managementFeeRate == 0): + // oldMfee - newMfee + // 3. The overpayment's penalty interest part (= untrackedInterest + // for the overpayment path; see computeOverpaymentComponents): + // trackedInterestPart() + bool const fix320Enabled = rules.enabled(fixCleanup3_2_0); + XRPL_ASSERT_IF( + fix320Enabled, + overpaymentComponents.trackedPrincipalDelta == + principalOutstandingProxy - newRoundedLoanState.principalOutstanding, + "xrpl::detail::doOverpayment : principal change agrees"); - XRPL_ASSERT_PARTS( - overpaymentComponents.trackedPrincipalDelta == - principalOutstandingProxy - newRoundedLoanState.principalOutstanding, - "xrpl::detail::doOverpayment", - "principal change agrees"); + XRPL_ASSERT_IF( + fix320Enabled, + [&] { + Number const tvoChange = newRoundedLoanState.valueOutstanding - + (totalValueOutstandingProxy - overpaymentComponents.trackedPrincipalDelta); + Number const managementFeeReleased = + managementFeeOutstandingProxy - newRoundedLoanState.managementFeeDue; + Number const interestPart = overpaymentComponents.trackedInterestPart(); + return loanPaymentParts.valueChange == tvoChange + managementFeeReleased + interestPart; + }(), + "xrpl::detail::doOverpayment : interest paid agrees"); - XRPL_ASSERT_PARTS( - loanPaymentParts.valueChange == tvoChange + managementFeeReleased + interestPart, - "xrpl::detail::doOverpayment", - "interest paid agrees"); - - XRPL_ASSERT_PARTS( - overpaymentComponents.trackedPrincipalDelta == loanPaymentParts.principalPaid, - "xrpl::detail::doOverpayment", - "principal payment matches"); - } + XRPL_ASSERT_IF( + fix320Enabled, + overpaymentComponents.trackedPrincipalDelta == loanPaymentParts.principalPaid, + "xrpl::detail::doOverpayment : principal payment matches"); // All validations passed, so update the proxy objects (which will // modify the actual Loan ledger object) @@ -1326,13 +1326,11 @@ computeOverpaymentComponents( TenthBips32 const overpaymentFeeRate, TenthBips16 const managementFeeRate) { - if (rules.enabled(fixCleanup3_2_0)) - { - XRPL_ASSERT( - overpayment > 0 && isRounded(asset, overpayment, loanScale), - "xrpl::detail::computeOverpaymentComponents : valid overpayment " - "amount"); - } + XRPL_ASSERT_IF( + rules.enabled(fixCleanup3_2_0), + overpayment > 0 && isRounded(asset, overpayment, loanScale), + "xrpl::detail::computeOverpaymentComponents : valid overpayment " + "amount"); // First, deduct the fixed overpayment fee from the total amount. // This reduces the effective payment that will be applied to the loan. diff --git a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp index 387116d820..8b3385471d 100644 --- a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp @@ -736,10 +736,10 @@ unlockEscrowMPT( STAmount const& grossAmount, beast::Journal j) { - if (!view.rules().enabled(fixTokenEscrowV1)) - { - XRPL_ASSERT(netAmount == grossAmount, "xrpl::unlockEscrowMPT : netAmount == grossAmount"); - } + XRPL_ASSERT_IF( + !view.rules().enabled(fixTokenEscrowV1), + netAmount == grossAmount, + "xrpl::unlockEscrowMPT : netAmount == grossAmount"); auto const& issuer = netAmount.getIssuer(); auto const& mptIssue = netAmount.get(); From fccb109e4829a3608f3b35f97586ccd41452061a Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 9 Jun 2026 18:36:17 +0100 Subject: [PATCH 075/135] feat: Use C++ 23 standard (#7431) --- .clang-tidy | 2 +- .../workflows/reusable-build-test-config.yml | 2 +- BUILD.md | 14 +- CMakeLists.txt | 2 +- conan/lockfile/linux.profile | 2 +- conan/lockfile/macos.profile | 2 +- conan/lockfile/windows.profile | 2 +- conan/profiles/default | 2 +- include/xrpl/basics/Expected.h | 248 ------------------ include/xrpl/basics/base_uint.h | 8 +- include/xrpl/beast/hash/xxhasher.h | 3 + include/xrpl/ledger/helpers/AMMHelpers.h | 9 +- .../xrpl/ledger/helpers/AccountRootHelpers.h | 4 +- include/xrpl/ledger/helpers/LendingHelpers.h | 5 +- include/xrpl/protocol/STTx.h | 16 +- include/xrpl/protocol/XChainAttestations.h | 2 +- include/xrpl/protocol/detail/STVar.h | 2 +- include/xrpl/protocol/tokens.h | 4 +- include/xrpl/tx/SignerEntries.h | 4 +- .../transactors/token/MPTokenIssuanceCreate.h | 5 +- .../xrpl/tx/transactors/vault/VaultClawback.h | 4 +- src/libxrpl/ledger/helpers/AMMHelpers.cpp | 32 +-- .../ledger/helpers/AccountRootHelpers.cpp | 6 +- .../ledger/helpers/CredentialHelpers.cpp | 6 +- src/libxrpl/ledger/helpers/LendingHelpers.cpp | 48 ++-- src/libxrpl/protocol/STTx.cpp | 46 ++-- src/libxrpl/protocol/tokens.cpp | 34 +-- src/libxrpl/tx/SignerEntries.cpp | 8 +- .../tx/transactors/bridge/XChainBridge.cpp | 50 ++-- src/libxrpl/tx/transactors/dex/AMMBid.cpp | 8 +- .../lending/LoanBrokerCoverClawback.cpp | 18 +- .../tx/transactors/lending/LoanPay.cpp | 4 +- .../tx/transactors/nft/NFTokenMint.cpp | 10 +- .../token/MPTokenIssuanceCreate.cpp | 14 +- .../tx/transactors/vault/VaultClawback.cpp | 22 +- src/test/app/Delegate_test.cpp | 4 +- src/test/app/GRPCServerTLS_test.cpp | 26 +- src/test/app/Invariants_test.cpp | 5 +- src/test/app/MultiSign_test.cpp | 4 +- src/test/app/NFTokenBurn_test.cpp | 8 +- src/test/app/NetworkOPs_test.cpp | 2 +- src/test/app/ValidatorSite_test.cpp | 12 +- src/test/basics/Expected_test.cpp | 221 ---------------- src/test/consensus/Consensus_test.cpp | 9 +- src/test/jtx/CheckMessageLogs.h | 2 +- src/test/jtx/directory.h | 4 +- src/test/jtx/impl/directory.cpp | 20 +- src/test/nodestore/NuDBFactory_test.cpp | 18 +- src/test/overlay/reduce_relay_test.cpp | 2 +- src/test/server/ServerStatus_test.cpp | 14 +- src/test/server/Server_test.cpp | 16 +- src/xrpld/overlay/detail/PeerImp.cpp | 2 +- src/xrpld/rpc/detail/RPCLedgerHelpers.cpp | 29 +- src/xrpld/rpc/detail/RPCLedgerHelpers.h | 5 +- src/xrpld/rpc/detail/TransactionSign.cpp | 10 +- src/xrpld/rpc/handlers/ledger/Ledger.cpp | 6 +- src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp | 156 +++++------ .../rpc/handlers/ledger/LedgerEntryHelpers.h | 37 +-- src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp | 28 +- .../server_info/ServerDefinitions.cpp | 2 +- .../rpc/handlers/transaction/Simulate.cpp | 10 +- src/xrpld/rpc/handlers/transaction/Submit.cpp | 6 +- 62 files changed, 412 insertions(+), 894 deletions(-) delete mode 100644 include/xrpl/basics/Expected.h delete mode 100644 src/test/basics/Expected_test.cpp diff --git a/.clang-tidy b/.clang-tidy index 2d72eae701..e09d326916 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -153,7 +153,7 @@ Checks: "-*, readability-use-std-min-max " # --- -# readability-inconsistent-declaration-parameter-name, # in this codebase this check will break a lot of arg names +# readability-inconsistent-declaration-parameter-name, # In this codebase this check will break a lot of arg names # readability-static-accessed-through-instance, # this check is probably unnecessary. It makes the code less readable # --- diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index d53cf97a39..8cb5f8c46a 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -82,7 +82,7 @@ jobs: name: ${{ inputs.config_name }} runs-on: ${{ fromJSON(inputs.runs_on) }} container: ${{ inputs.image != '' && inputs.image || null }} - timeout-minutes: ${{ inputs.sanitizers != '' && 360 || 60 }} + timeout-minutes: ${{ inputs.sanitizers != '' && 360 || 90 }} env: # Use a namespace to keep the objects separate for each configuration. CCACHE_NAMESPACE: ${{ inputs.config_name }} diff --git a/BUILD.md b/BUILD.md index 1d3fc8f774..662ba0d33d 100644 --- a/BUILD.md +++ b/BUILD.md @@ -45,14 +45,14 @@ found here](./docs/build/environment.md). It is possible to build with Conan 1.60+, but the instructions are significantly different, which is why we are not recommending it. -`xrpld` is written in the C++20 dialect and includes the `` header. -The [minimum compiler versions][2] required are: +`xrpld` is written in the C++23 dialect and includes the `` header. +The [tested compiler versions][2] are: | Compiler | Version | | ----------- | --------- | -| GCC | 12 | -| Clang | 16 | -| Apple Clang | 16 | +| GCC | 15 | +| Clang | 22 | +| Apple Clang | 17 | | MSVC | 19.44[^3] | ### Linux @@ -232,11 +232,11 @@ name and then creating a new `default` profile for a different compiler. #### Select language The default profile created by Conan will typically select different C++ dialect -than C++20 used by this project. You should set `20` in the profile line +than C++23 used by this project. You should set `23` in the profile line starting with `compiler.cppstd=`. For example: ```bash -sed -i.bak -e 's|^compiler\.cppstd=.*$|compiler.cppstd=20|' $(conan config home)/profiles/default +sed -i.bak -e 's|^compiler\.cppstd=.*$|compiler.cppstd=23|' $(conan config home)/profiles/default ``` #### Select standard library in Linux diff --git a/CMakeLists.txt b/CMakeLists.txt index d315a5dcec..3dbe60a220 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") project(xrpl) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) diff --git a/conan/lockfile/linux.profile b/conan/lockfile/linux.profile index 25ad5988c5..ea9f66de69 100644 --- a/conan/lockfile/linux.profile +++ b/conan/lockfile/linux.profile @@ -2,7 +2,7 @@ arch=x86_64 build_type=Release compiler=gcc -compiler.cppstd=20 +compiler.cppstd=23 compiler.libcxx=libstdc++11 compiler.version=13 os=Linux diff --git a/conan/lockfile/macos.profile b/conan/lockfile/macos.profile index 332a0c143d..f223627c26 100644 --- a/conan/lockfile/macos.profile +++ b/conan/lockfile/macos.profile @@ -2,7 +2,7 @@ arch=armv8 build_type=Release compiler=apple-clang -compiler.cppstd=20 +compiler.cppstd=23 compiler.libcxx=libc++ compiler.version=17.0 os=Macos diff --git a/conan/lockfile/windows.profile b/conan/lockfile/windows.profile index 4bb266a62e..b3a8fed4f3 100644 --- a/conan/lockfile/windows.profile +++ b/conan/lockfile/windows.profile @@ -2,7 +2,7 @@ arch=x86_64 build_type=Release compiler=msvc -compiler.cppstd=20 +compiler.cppstd=23 compiler.runtime=dynamic compiler.runtime_type=Release compiler.version=194 diff --git a/conan/profiles/default b/conan/profiles/default index cde59f7f3b..e0a88ebca1 100644 --- a/conan/profiles/default +++ b/conan/profiles/default @@ -12,7 +12,7 @@ arch={{ arch }} build_type=Debug compiler={{compiler}} compiler.version={{ compiler_version }} -compiler.cppstd=20 +compiler.cppstd=23 {% if os == "Windows" %} compiler.runtime=static {% else %} diff --git a/include/xrpl/basics/Expected.h b/include/xrpl/basics/Expected.h deleted file mode 100644 index 3796151777..0000000000 --- a/include/xrpl/basics/Expected.h +++ /dev/null @@ -1,248 +0,0 @@ -#pragma once - -#include - -#include - -#include - -namespace xrpl { - -/** Expected is an approximation of std::expected (hoped for in C++23) - - See: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0323r10.html - - The implementation is entirely based on boost::outcome_v2::result. -*/ - -// Exception thrown by an invalid access to Expected. -struct BadExpectedAccess : public std::runtime_error -{ - BadExpectedAccess() : runtime_error("bad expected access") - { - } -}; - -namespace detail { - -// Custom policy for Expected. Always throw on an invalid access. -struct ThrowPolicy : public boost::outcome_v2::policy::base -{ - template - static constexpr void - // NOLINTNEXTLINE(readability-identifier-naming) - wide_value_check(Impl&& self) - { - if (!base::_has_value(std::forward(self))) - Throw(); - } - - template - static constexpr void - // NOLINTNEXTLINE(readability-identifier-naming) - wide_error_check(Impl&& self) - { - if (!base::_has_error(std::forward(self))) - Throw(); - } - - template - static constexpr void - // NOLINTNEXTLINE(readability-identifier-naming) - wide_exception_check(Impl&& self) - { - if (!base::_has_exception(std::forward(self))) - Throw(); - } -}; - -} // namespace detail - -// Definition of Unexpected, which is used to construct the unexpected -// return type of an Expected. -template -class Unexpected -{ -public: - static_assert(!std::is_same_v, "E must not be void"); - - Unexpected() = delete; - - constexpr explicit Unexpected(E const& e) : val_(e) - { - } - - constexpr explicit Unexpected(E&& e) : val_(std::move(e)) - { - } - - [[nodiscard]] constexpr E const& - value() const& - { - return val_; - } - - constexpr E& - value() & - { - return val_; - } - - constexpr E&& - value() && - { - return std::move(val_); - } - - [[nodiscard]] constexpr E const&& - value() const&& - { - return std::move(val_); - } - -private: - E val_; -}; - -// Unexpected deduction guide that converts array to const*. -template -Unexpected(E (&)[N]) -> Unexpected; - -// Definition of Expected. All of the machinery comes from boost::result. -template -class [[nodiscard]] Expected : private boost::outcome_v2::result -{ - using Base = boost::outcome_v2::result; - -public: - template - requires std::convertible_to - constexpr Expected(U&& r) : Base(boost::outcome_v2::in_place_type_t{}, std::forward(r)) - { - } - - template - requires std::convertible_to && (!std::is_reference_v) - constexpr Expected(Unexpected e) - : Base(boost::outcome_v2::in_place_type_t{}, std::move(e.value())) - { - } - - [[nodiscard]] constexpr bool - // NOLINTNEXTLINE(readability-identifier-naming) - has_value() const - { - return Base::has_value(); - } - - [[nodiscard]] constexpr T const& - value() const - { - return Base::value(); - } - - constexpr T& - value() - { - return Base::value(); - } - - [[nodiscard]] constexpr E const& - error() const& - { - return Base::error(); - } - - [[nodiscard]] constexpr E& - error() & - { - return Base::error(); - } - - [[nodiscard]] constexpr E&& - error() && - { - return std::move(Base::error()); - } - - constexpr explicit - operator bool() const - { - return has_value(); - } - - // Add operator* and operator-> so the Expected API looks a bit more like - // what std::expected is likely to look like. See: - // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0323r10.html - [[nodiscard]] constexpr T& - operator*() - { - return this->value(); - } - - [[nodiscard]] constexpr T const& - operator*() const - { - return this->value(); - } - - [[nodiscard]] constexpr T* - operator->() - { - return &this->value(); - } - - [[nodiscard]] constexpr T const* - operator->() const - { - return &this->value(); - } -}; - -// Specialization of Expected. Allows returning either success -// (without a value) or the reason for the failure. -template -class [[nodiscard]] -Expected : private boost::outcome_v2::result -{ - using Base = boost::outcome_v2::result; - -public: - // The default constructor makes a successful Expected. - // This aligns with std::expected behavior proposed in P0323R10. - constexpr Expected() : Base(boost::outcome_v2::success()) - { - } - - template - requires std::convertible_to && (!std::is_reference_v) - constexpr Expected(Unexpected e) : Base(E(std::move(e.value()))) - { - } - - [[nodiscard]] constexpr E const& - error() const& - { - return Base::error(); - } - - [[nodiscard]] constexpr E& - error() & - { - return Base::error(); - } - - [[nodiscard]] constexpr E&& - error() && - { - return std::move(Base::error()); - } - - constexpr explicit - operator bool() const - { - return Base::has_value(); - } -}; - -} // namespace xrpl diff --git a/include/xrpl/basics/base_uint.h b/include/xrpl/basics/base_uint.h index 93a9ced15e..93520ff699 100644 --- a/include/xrpl/basics/base_uint.h +++ b/include/xrpl/basics/base_uint.h @@ -5,7 +5,6 @@ #pragma once -#include #include #include #include @@ -20,6 +19,7 @@ #include #include #include +#include #include namespace xrpl { @@ -177,7 +177,7 @@ private: BadChar, }; - constexpr Expected + constexpr std::expected parseFromStringView(std::string_view sv) noexcept { // Local lambda that converts a single hex char to four bits and @@ -216,7 +216,7 @@ private: } if (sv.size() != size() * 2) - return Unexpected(ParseResult::BadLength); + return std::unexpected(ParseResult::BadLength); std::size_t i = 0u; auto in = sv.begin(); @@ -227,7 +227,7 @@ private: { if (auto const result = hexCharToUInt(*in++, shift, accum); result != ParseResult::Okay) - return Unexpected(result); + return std::unexpected(result); } ret[i++] = accum; } diff --git a/include/xrpl/beast/hash/xxhasher.h b/include/xrpl/beast/hash/xxhasher.h index 95a67dede0..978bbc6917 100644 --- a/include/xrpl/beast/hash/xxhasher.h +++ b/include/xrpl/beast/hash/xxhasher.h @@ -7,8 +7,11 @@ #include #include #include +#include +#include #include #include +#include namespace beast { diff --git a/include/xrpl/ledger/helpers/AMMHelpers.h b/include/xrpl/ledger/helpers/AMMHelpers.h index 61d6e9d2fb..d21e50e7cb 100644 --- a/include/xrpl/ledger/helpers/AMMHelpers.h +++ b/include/xrpl/ledger/helpers/AMMHelpers.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -18,6 +17,8 @@ #include #include +#include + namespace xrpl { namespace detail { @@ -741,7 +742,7 @@ ammPoolHolds( * provided then they are used as the AMM token pair issues. * Otherwise the missing issues are fetched from ammSle. */ -Expected, TER> +std::expected, TER> ammHolds( ReadView const& view, SLE const& ammSle, @@ -801,14 +802,14 @@ initializeFeeAuctionVote( * otherwise. Return tecINTERNAL if encountered an unexpected condition, * for instance Liquidity Provider has more than one LPToken trustline. */ -Expected +std::expected isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount); /** Due to rounding, the LPTokenBalance of the last LP might * not match the LP's trustline balance. If it's within the tolerance, * update LPTokenBalance to match the LP's trustline balance. */ -Expected +std::expected verifyAndAdjustLPTokenBalance( Sandbox& sb, STAmount const& lpTokens, diff --git a/include/xrpl/ledger/helpers/AccountRootHelpers.h b/include/xrpl/ledger/helpers/AccountRootHelpers.h index c02cad98d8..cf6082d533 100644 --- a/include/xrpl/ledger/helpers/AccountRootHelpers.h +++ b/include/xrpl/ledger/helpers/AccountRootHelpers.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -9,6 +8,7 @@ #include #include +#include #include #include @@ -91,7 +91,7 @@ isPseudoAccount( * before using a field. The amendment check is **not** performed in * createPseudoAccount. */ -[[nodiscard]] Expected +[[nodiscard]] std::expected createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField); /** Checks the destination and tag. diff --git a/include/xrpl/ledger/helpers/LendingHelpers.h b/include/xrpl/ledger/helpers/LendingHelpers.h index 32f94ee277..8de945233b 100644 --- a/include/xrpl/ledger/helpers/LendingHelpers.h +++ b/include/xrpl/ledger/helpers/LendingHelpers.h @@ -4,6 +4,7 @@ #include #include +#include #include namespace xrpl { @@ -397,7 +398,7 @@ struct LoanStateDeltas nonNegative(); }; -Expected, TER> +std::expected, TER> tryOverpayment( Rules const& rules, Asset const& asset, @@ -523,7 +524,7 @@ isRounded(Asset const& asset, Number const& value, std::int32_t scale); // potential extra work at the end. enum class LoanPaymentType { Regular = 0, Late, Full, Overpayment }; -Expected +std::expected loanMakePayment( Asset const& asset, ApplyView& view, diff --git a/include/xrpl/protocol/STTx.h b/include/xrpl/protocol/STTx.h index 4deedfafb7..659fede31d 100644 --- a/include/xrpl/protocol/STTx.h +++ b/include/xrpl/protocol/STTx.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -11,6 +10,7 @@ #include +#include #include namespace xrpl { @@ -108,10 +108,10 @@ public: @param rules The current ledger rules. @return `true` if valid signature. If invalid, the error message string. */ - Expected + std::expected checkSign(Rules const& rules) const; - Expected + std::expected checkBatchSign(Rules const& rules) const; // SQL Functions with metadata. @@ -138,19 +138,19 @@ private: Will be *this more often than not. @return `true` if valid signature. If invalid, the error message string. */ - Expected + std::expected checkSign(Rules const& rules, STObject const& sigObject) const; - Expected + std::expected checkSingleSign(STObject const& sigObject) const; - Expected + std::expected checkMultiSign(Rules const& rules, STObject const& sigObject) const; - Expected + std::expected checkBatchSingleSign(STObject const& batchSigner) const; - Expected + std::expected checkBatchMultiSign(STObject const& batchSigner, Rules const& rules) const; STBase* diff --git a/include/xrpl/protocol/XChainAttestations.h b/include/xrpl/protocol/XChainAttestations.h index 993f478b5e..457af727a2 100644 --- a/include/xrpl/protocol/XChainAttestations.h +++ b/include/xrpl/protocol/XChainAttestations.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -15,6 +14,7 @@ #include #include +#include #include #include diff --git a/include/xrpl/protocol/detail/STVar.h b/include/xrpl/protocol/detail/STVar.h index 98a0b8dcd2..71077d4b33 100644 --- a/include/xrpl/protocol/detail/STVar.h +++ b/include/xrpl/protocol/detail/STVar.h @@ -37,7 +37,7 @@ private: // The largest "small object" we can accommodate static constexpr std::size_t kMaxSize = 72; - std::aligned_storage::type d_ = {}; + alignas(std::max_align_t) std::byte d_[kMaxSize] = {}; STBase* p_ = nullptr; public: diff --git a/include/xrpl/protocol/tokens.h b/include/xrpl/protocol/tokens.h index 125cfe8583..67cb25c7fb 100644 --- a/include/xrpl/protocol/tokens.h +++ b/include/xrpl/protocol/tokens.h @@ -1,10 +1,10 @@ #pragma once -#include #include #include #include +#include #include #include #include @@ -13,7 +13,7 @@ namespace xrpl { template -using B58Result = Expected; +using B58Result = std::expected; enum class TokenType : std::uint8_t { None = 1, // unused diff --git a/include/xrpl/tx/SignerEntries.h b/include/xrpl/tx/SignerEntries.h index 91fc4bd030..af0c9b9d28 100644 --- a/include/xrpl/tx/SignerEntries.h +++ b/include/xrpl/tx/SignerEntries.h @@ -1,11 +1,11 @@ #pragma once -#include // #include // beast::Journal #include // temMALFORMED #include // AccountID #include // NotTEC +#include #include #include @@ -60,7 +60,7 @@ public: // obj Contains a SignerEntries field that is an STArray. // journal For reporting error conditions. // annotation Source of SignerEntries, like "ledger" or "transaction". - static Expected, NotTEC> + static std::expected, NotTEC> deserialize(STObject const& obj, beast::Journal journal, std::string_view annotation); }; diff --git a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h index a706c71e18..d946587e32 100644 --- a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h +++ b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h @@ -1,9 +1,10 @@ #pragma once -#include #include #include +#include + namespace xrpl { // NOLINTBEGIN(readability-redundant-member-init) @@ -61,7 +62,7 @@ public: ReadView const& view, beast::Journal const& j) override; - static Expected + static std::expected create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args); }; diff --git a/include/xrpl/tx/transactors/vault/VaultClawback.h b/include/xrpl/tx/transactors/vault/VaultClawback.h index b8032809ee..2ff97abca2 100644 --- a/include/xrpl/tx/transactors/vault/VaultClawback.h +++ b/include/xrpl/tx/transactors/vault/VaultClawback.h @@ -2,6 +2,8 @@ #include +#include + namespace xrpl { class VaultClawback : public Transactor @@ -34,7 +36,7 @@ public: beast::Journal const& j) override; private: - Expected, TER> + std::expected, TER> assetsToClawback( SLE::ref vault, SLE::const_ref sleShareIssuance, diff --git a/src/libxrpl/ledger/helpers/AMMHelpers.cpp b/src/libxrpl/ledger/helpers/AMMHelpers.cpp index fe6d022490..a59b8e4436 100644 --- a/src/libxrpl/ledger/helpers/AMMHelpers.cpp +++ b/src/libxrpl/ledger/helpers/AMMHelpers.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -34,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -433,7 +433,7 @@ ammPoolHolds( return std::make_pair(assetInBalance, assetOutBalance); } -Expected, TER> +std::expected, TER> ammHolds( ReadView const& view, SLE const& ammSle, @@ -489,7 +489,7 @@ ammHolds( return std::make_optional(std::make_pair(asset1, asset2)); }(); if (!assets) - return Unexpected(tecAMM_INVALID_TOKENS); + return std::unexpected(tecAMM_INVALID_TOKENS); auto const [amount1, amount2] = ammPoolHolds( view, ammSle.getAccountID(sfAccount), @@ -821,7 +821,7 @@ initializeFeeAuctionVote( auctionSlot.makeFieldAbsent(sfAuthAccounts); } -Expected +std::expected isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount) { // Liquidity Provider (LP) must have one LPToken trustline @@ -852,18 +852,18 @@ isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID c { auto const ownerDir = view.read(currentIndex); if (!ownerDir) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE for (auto const& key : ownerDir->getFieldV256(sfIndexes)) { auto const sle = view.read(keylet::child(key)); if (!sle) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE auto const entryType = sle->getFieldU16(sfLedgerEntryType); // Only one AMM object if (entryType == ltAMM) { if (hasAMM) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE hasAMM = true; continue; } @@ -873,7 +873,7 @@ isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID c continue; } if (entryType != ltRIPPLE_STATE) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE auto const lowLimit = sle->getFieldAmount(sfLowLimit); auto const highLimit = sle->getFieldAmount(sfHighLimit); auto const isLPTrustline = @@ -889,12 +889,12 @@ isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID c { // LP has exactly one LPToken trustline if (++nLPTokenTrustLines > 1) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE } // AMM account has at most two IOU trustlines else if (++nIOUTrustLines > 2) { - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE } } // Another Liquidity Provider LPToken trustline @@ -905,7 +905,7 @@ isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID c // AMM account has at most two IOU trustlines else if (++nIOUTrustLines > 2) { - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE } } auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext); @@ -913,15 +913,15 @@ isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID c { if (nLPTokenTrustLines != 1 || (nIOUTrustLines == 0 && nMPT == 0) || (nIOUTrustLines > 2 || nMPT > 2) || (nIOUTrustLines + nMPT) > 2) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE return true; } currentIndex = keylet::page(root, uNodeNext); } - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE } -Expected +std::expected verifyAndAdjustLPTokenBalance( Sandbox& sb, STAmount const& lpTokens, @@ -931,7 +931,7 @@ verifyAndAdjustLPTokenBalance( auto const res = isOnlyLiquidityProvider(sb, lpTokens.get(), account); if (!res.has_value()) { - return Unexpected(res.error()); + return std::unexpected(res.error()); } if (res.value()) @@ -944,7 +944,7 @@ verifyAndAdjustLPTokenBalance( } else { - return Unexpected(tecAMM_INVALID_TOKENS); + return std::unexpected(tecAMM_INVALID_TOKENS); } } return true; diff --git a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp index 1634de93c9..1c4acc7bc4 100644 --- a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp +++ b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -22,6 +21,7 @@ #include #include +#include #include #include #include @@ -202,7 +202,7 @@ isPseudoAccount(SLE::const_pointer sleAcct, std::set const& pseud }) > 0; } -Expected +std::expected createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField) { [[maybe_unused]] @@ -216,7 +216,7 @@ createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const auto const accountId = pseudoAccountAddress(view, pseudoOwnerKey); if (accountId == beast::kZero) - return Unexpected(tecDUPLICATE); + return std::unexpected(tecDUPLICATE); // Create pseudo-account. auto account = std::make_shared(keylet::account(accountId)); diff --git a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp index 28b50b51d6..ca5876f88a 100644 --- a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp +++ b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -24,6 +23,7 @@ #include #include +#include #include #include #include @@ -43,7 +43,7 @@ checkExpired(SLE const& sleCredential, NetClock::time_point const& closed) } [[nodiscard]] -static Expected +static std::expected removeExpired(ApplyView& view, STVector256 const& arr, beast::Journal const j) { auto const closeTime = view.header().parentCloseTime; @@ -61,7 +61,7 @@ removeExpired(ApplyView& view, STVector256 const& arr, beast::Journal const j) // delete expired credentials even if the transaction failed auto const err = deleteSLE(view, sleCred, j); if (view.rules().enabled(fixCleanup3_1_3) && !isTesSuccess(err)) - return Unexpected(err); + return std::unexpected(err); foundExpired = true; } } diff --git a/src/libxrpl/ledger/helpers/LendingHelpers.cpp b/src/libxrpl/ledger/helpers/LendingHelpers.cpp index 0a195f9cbe..676b473132 100644 --- a/src/libxrpl/ledger/helpers/LendingHelpers.cpp +++ b/src/libxrpl/ledger/helpers/LendingHelpers.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -25,6 +24,7 @@ #include #include #include +#include #include #include @@ -514,7 +514,7 @@ doPayment( * The function preserves accumulated rounding errors across the re-amortization * to ensure the loan state remains consistent with its payment history. */ -Expected, TER> +std::expected, TER> tryOverpayment( Rules const& rules, Asset const& asset, @@ -643,7 +643,7 @@ tryOverpayment( JLOG(j.warn()) << "Principal overpayment would cause the loan to be in " "an invalid state. Ignore the overpayment"; - return Unexpected(tesSUCCESS); + return std::unexpected(tesSUCCESS); } // Validate that all computed properties are reasonable. These checks should @@ -660,7 +660,7 @@ tryOverpayment( << ", PeriodicPayment : " << newLoanProperties.periodicPayment << ", ManagementFeeOwedToBroker: " << newLoanProperties.loanState.managementFeeDue; - return Unexpected(tesSUCCESS); + return std::unexpected(tesSUCCESS); // LCOV_EXCL_STOP } @@ -685,7 +685,7 @@ tryOverpayment( { JLOG(j.warn()) << "Principal overpayment would increase the value of " "the loan. Ignore the overpayment"; - return Unexpected(tesSUCCESS); + return std::unexpected(tesSUCCESS); } return std::make_pair( @@ -718,7 +718,7 @@ tryOverpayment( * gracefully without corrupting the ledger data. */ template -Expected +std::expected doOverpayment( Rules const& rules, Asset const& asset, @@ -760,7 +760,7 @@ doOverpayment( managementFeeRate, j); if (!ret) - return Unexpected(ret.error()); + return std::unexpected(ret.error()); auto const& [loanPaymentParts, newLoanProperties] = *ret; auto const newRoundedLoanState = newLoanProperties.loanState; @@ -774,7 +774,7 @@ doOverpayment( JLOG(j.warn()) << "Overpayment not allowed: principal " << "outstanding did not decrease. Before: " << *principalOutstandingProxy << ". After: " << newRoundedLoanState.principalOutstanding; - return Unexpected(tesSUCCESS); + return std::unexpected(tesSUCCESS); // LCOV_EXCL_STOP } @@ -860,7 +860,7 @@ doOverpayment( * * Implements equation (15) from XLS-66 spec, Section A-2 Equation Glossary */ -Expected +std::expected computeLatePayment( Asset const& asset, ApplyView const& view, @@ -877,7 +877,7 @@ computeLatePayment( // Check if the due date has passed. If not, reject the payment as // being too soon if (!hasExpired(view, nextDueDate)) - return Unexpected(tecTOO_SOON); + return std::unexpected(tecTOO_SOON); // Calculate the penalty interest based on how long the payment is overdue. auto const latePaymentInterest = loanLatePaymentInterest( @@ -929,7 +929,7 @@ computeLatePayment( { JLOG(j.warn()) << "Late loan payment amount is insufficient. Due: " << late.totalDue << ", paid: " << amount; - return Unexpected(tecINSUFFICIENT_PAYMENT); + return std::unexpected(tecINSUFFICIENT_PAYMENT); } return late; @@ -954,7 +954,7 @@ computeLatePayment( * * Implements equation (26) from XLS-66 spec, Section A-2 Equation Glossary */ -Expected +std::expected computeFullPayment( Asset const& asset, ApplyView& view, @@ -979,7 +979,7 @@ computeFullPayment( { // If this is the last payment, it has to be a regular payment JLOG(j.warn()) << "Last payment cannot be a full payment."; - return Unexpected(tecKILLED); + return std::unexpected(tecKILLED); } // Calculate the theoretical principal based on the payment schedule. @@ -1059,7 +1059,7 @@ computeFullPayment( { // If the payment is less than the full payment amount, it's not // sufficient to be a full payment. - return Unexpected(tecINSUFFICIENT_PAYMENT); + return std::unexpected(tecINSUFFICIENT_PAYMENT); } return full; @@ -1780,7 +1780,7 @@ computeLoanProperties( * It is an implementation of the make_payment function from the XLS-66 * spec. Section 3.2.4.4 */ -Expected +std::expected loanMakePayment( Asset const& asset, ApplyView& view, @@ -1800,7 +1800,7 @@ loanMakePayment( // Loan complete this is already checked in LoanPay::preclaim() // LCOV_EXCL_START JLOG(j.warn()) << "Loan is already paid off."; - return Unexpected(tecKILLED); + return std::unexpected(tecKILLED); // LCOV_EXCL_STOP } @@ -1812,7 +1812,7 @@ loanMakePayment( if (*nextDueDateProxy == 0) { JLOG(j.warn()) << "Loan next payment due date is not set."; - return Unexpected(tecINTERNAL); + return std::unexpected(tecINTERNAL); } std::int32_t const loanScale = loan->at(sfLoanScale); @@ -1850,7 +1850,7 @@ loanMakePayment( << startDate << ", prev payment due date is " << prevPaymentDateProxy << ", next payment due date is " << nextDueDateProxy << ", ledger time is " << view.parentCloseTime().time_since_epoch().count(); - return Unexpected(tecEXPIRED); + return std::unexpected(tecEXPIRED); } // ------------------------------------------------------------- @@ -1900,13 +1900,13 @@ loanMakePayment( // error() will be the TER returned if a payment is not made. It // will only evaluate to true if it's unsuccessful. Otherwise, // tesSUCCESS means nothing was done, so continue. - return Unexpected(fullPaymentComponents.error()); + return std::unexpected(fullPaymentComponents.error()); } // LCOV_EXCL_START UNREACHABLE("xrpl::loanMakePayment : invalid full payment result"); JLOG(j.error()) << "Full payment computation failed unexpectedly."; - return Unexpected(tecINTERNAL); + return std::unexpected(tecINTERNAL); // LCOV_EXCL_STOP } @@ -1968,13 +1968,13 @@ loanMakePayment( { // error() will be the TER returned if a payment is not made. It // will only evaluate to true if it's unsuccessful. - return Unexpected(latePaymentComponents.error()); + return std::unexpected(latePaymentComponents.error()); } // LCOV_EXCL_START UNREACHABLE("xrpl::loanMakePayment : invalid late payment result"); JLOG(j.error()) << "Late payment computation failed unexpectedly."; - return Unexpected(tecINTERNAL); + return std::unexpected(tecINTERNAL); // LCOV_EXCL_STOP } @@ -2041,7 +2041,7 @@ loanMakePayment( { JLOG(j.warn()) << "Regular loan payment amount is insufficient. Due: " << periodic.totalDue << ", paid: " << amount; - return Unexpected(tecINSUFFICIENT_PAYMENT); + return std::unexpected(tecINSUFFICIENT_PAYMENT); } XRPL_ASSERT_PARTS( @@ -2127,7 +2127,7 @@ loanMakePayment( // made. It will only evaluate to true if it's unsuccessful. // Otherwise, tesSUCCESS means nothing was done, so // continue. - return Unexpected(overResult.error()); + return std::unexpected(overResult.error()); } } } diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 2777981fd7..55f0ea1289 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -40,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -248,7 +248,7 @@ STTx::sign( tid_ = getHash(HashPrefix::TransactionId); } -Expected +std::expected STTx::checkSign(Rules const& rules, STObject const& sigObject) const { try @@ -263,11 +263,11 @@ STTx::checkSign(Rules const& rules, STObject const& sigObject) const } catch (...) { - return Unexpected("Internal signature check failure."); + return std::unexpected("Internal signature check failure."); } } -Expected +std::expected STTx::checkSign(Rules const& rules) const { if (auto const ret = checkSign(rules, *this); !ret) @@ -277,12 +277,12 @@ STTx::checkSign(Rules const& rules) const { auto const counterSig = getFieldObject(sfCounterpartySignature); if (auto const ret = checkSign(rules, counterSig); !ret) - return Unexpected("Counterparty: " + ret.error()); + return std::unexpected("Counterparty: " + ret.error()); } return {}; } -Expected +std::expected STTx::checkBatchSign(Rules const& rules) const { try @@ -291,7 +291,7 @@ STTx::checkBatchSign(Rules const& rules) const if (getTxnType() != ttBATCH) { JLOG(debugLog().fatal()) << "not a batch transaction"; - return Unexpected("Not a batch transaction."); + return std::unexpected("Not a batch transaction."); } STArray const& signers{getFieldArray(sfBatchSigners)}; for (auto const& signer : signers) @@ -309,7 +309,7 @@ STTx::checkBatchSign(Rules const& rules) const { JLOG(debugLog().error()) << "Batch signature check failed: " << e.what(); } - return Unexpected("Internal batch signature check failure."); + return std::unexpected("Internal batch signature check failure."); } json::Value @@ -389,14 +389,14 @@ STTx::getMetaSQL( safeCast(status) % rTxn % escapedMetaData); } -static Expected +static std::expected singleSignHelper(STObject const& sigObject, Slice const& data) { // We don't allow both a non-empty sfSigningPubKey and an sfSigners. // That would allow the transaction to be signed two ways. So if both // fields are present the signature is invalid. if (sigObject.isFieldPresent(sfSigners)) - return Unexpected("Cannot both single- and multi-sign."); + return std::unexpected("Cannot both single- and multi-sign."); bool validSig = false; try @@ -414,19 +414,19 @@ singleSignHelper(STObject const& sigObject, Slice const& data) } if (!validSig) - return Unexpected("Invalid signature."); + return std::unexpected("Invalid signature."); return {}; } -Expected +std::expected STTx::checkSingleSign(STObject const& sigObject) const { auto const data = getSigningData(*this); return singleSignHelper(sigObject, makeSlice(data)); } -Expected +std::expected STTx::checkBatchSingleSign(STObject const& batchSigner) const { Serializer msg; @@ -434,7 +434,7 @@ STTx::checkBatchSingleSign(STObject const& batchSigner) const return singleSignHelper(batchSigner, msg.slice()); } -Expected +std::expected multiSignHelper( STObject const& sigObject, std::optional txnAccountID, @@ -444,18 +444,18 @@ multiSignHelper( // Make sure the MultiSigners are present. Otherwise they are not // attempting multi-signing and we just have a bad SigningPubKey. if (!sigObject.isFieldPresent(sfSigners)) - return Unexpected("Empty SigningPubKey."); + return std::unexpected("Empty SigningPubKey."); // We don't allow both an sfSigners and an sfTxnSignature. Both fields // being present would indicate that the transaction is signed both ways. if (sigObject.isFieldPresent(sfTxnSignature)) - return Unexpected("Cannot both single- and multi-sign."); + return std::unexpected("Cannot both single- and multi-sign."); STArray const& signers{sigObject.getFieldArray(sfSigners)}; // There are well known bounds that the number of signers must be within. if (signers.size() < STTx::kMinMultiSigners || signers.size() > STTx::kMaxMultiSigners) - return Unexpected("Invalid Signers array size."); + return std::unexpected("Invalid Signers array size."); // Signers must be in sorted order by AccountID. AccountID lastAccountID(beast::kZero); @@ -468,15 +468,15 @@ multiSignHelper( // If they can, txnAccountID will be unseated, which is not equal to any // value. if (txnAccountID == accountID) - return Unexpected("Invalid multisigner."); + return std::unexpected("Invalid multisigner."); // No duplicate signers allowed. if (lastAccountID == accountID) - return Unexpected("Duplicate Signers not allowed."); + return std::unexpected("Duplicate Signers not allowed."); // Accounts must be in order by account ID. No duplicates allowed. if (lastAccountID > accountID) - return Unexpected("Unsorted Signers array."); + return std::unexpected("Unsorted Signers array."); // The next signature must be greater than this one. lastAccountID = accountID; @@ -502,7 +502,7 @@ multiSignHelper( } if (!validSig) { - return Unexpected( + return std::unexpected( std::string("Invalid signature on account ") + toBase58(accountID) + errorWhat.value_or("") + "."); } @@ -511,7 +511,7 @@ multiSignHelper( return {}; } -Expected +std::expected STTx::checkBatchMultiSign(STObject const& batchSigner, Rules const& rules) const { // We can ease the computational load inside the loop a bit by @@ -530,7 +530,7 @@ STTx::checkBatchMultiSign(STObject const& batchSigner, Rules const& rules) const rules); } -Expected +std::expected STTx::checkMultiSign(Rules const& rules, STObject const& sigObject) const { // Used inside the loop in multiSignHelper to enforce that diff --git a/src/libxrpl/protocol/tokens.cpp b/src/libxrpl/protocol/tokens.cpp index a43cbd9c85..fcd822a747 100644 --- a/src/libxrpl/protocol/tokens.cpp +++ b/src/libxrpl/protocol/tokens.cpp @@ -9,7 +9,6 @@ #include -#include #include #include #include @@ -23,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -353,7 +353,7 @@ b256ToB58Be(std::span input, std::span out) // (33 bytes for nodepublic + 1 byte token + 4 bytes checksum) if (input.size() > 38) { - return Unexpected(TokenCodecErrc::InputTooLarge); + return std::unexpected(TokenCodecErrc::InputTooLarge); }; auto countLeadingZeros = [](std::span const& col) -> std::size_t { @@ -441,7 +441,7 @@ b256ToB58Be(std::span input, std::span out) static constexpr std::uint64_t kB5810 = 430804206899405824; // 58^10; if (base5810Coeff[i] >= kB5810) { - return Unexpected(TokenCodecErrc::InputTooLarge); + return std::unexpected(TokenCodecErrc::InputTooLarge); } std::array const b58Be = xrpl::b58_fast::detail::b5810ToB58Be(base5810Coeff[i]); @@ -453,7 +453,7 @@ b256ToB58Be(std::span input, std::span out) skipZeros = false; if (out.size() < ((i + 1) * 10) - toSkip) { - return Unexpected(TokenCodecErrc::OutputTooSmall); + return std::unexpected(TokenCodecErrc::OutputTooSmall); } } for (auto b58Coeff : b58BeS.subspan(toSkip)) @@ -476,11 +476,11 @@ b58ToB256Be(std::string_view input, std::span out) // log(2^(38*8),58) ~= 51.9 if (input.size() > 52) { - return Unexpected(TokenCodecErrc::InputTooLarge); + return std::unexpected(TokenCodecErrc::InputTooLarge); }; if (out.size() < 8) { - return Unexpected(TokenCodecErrc::OutputTooSmall); + return std::unexpected(TokenCodecErrc::OutputTooSmall); } auto countLeadingZeros = [&](auto const& col) -> std::size_t { @@ -513,7 +513,7 @@ b58ToB256Be(std::string_view input, std::span out) auto curVal = ::xrpl::kAlphabetReverse[c]; if (curVal < 0) { - return Unexpected(TokenCodecErrc::InvalidEncodingChar); + return std::unexpected(TokenCodecErrc::InvalidEncodingChar); } b5810Coeff[0] *= 58; b5810Coeff[0] += curVal; @@ -526,7 +526,7 @@ b58ToB256Be(std::string_view input, std::span out) auto curVal = ::xrpl::kAlphabetReverse[c]; if (curVal < 0) { - return Unexpected(TokenCodecErrc::InvalidEncodingChar); + return std::unexpected(TokenCodecErrc::InvalidEncodingChar); } b5810Coeff[numPartialCoeffs + j] *= 58; b5810Coeff[numPartialCoeffs + j] += curVal; @@ -548,7 +548,7 @@ b58ToB256Be(std::string_view input, std::span out) std::span(&result[0], curResultSize + 1), kB5810); if (code != TokenCodecErrc::Success) { - return Unexpected(code); + return std::unexpected(code); } } { @@ -556,7 +556,7 @@ b58ToB256Be(std::string_view input, std::span out) std::span(&result[0], curResultSize + 1), c); if (code != TokenCodecErrc::Success) { - return Unexpected(code); + return std::unexpected(code); } } if (result[curResultSize] != 0) @@ -589,7 +589,7 @@ b58ToB256Be(std::string_view input, std::span out) } if ((curOutI + (8 * (curResultSize - 1))) > out.size()) { - return Unexpected(TokenCodecErrc::OutputTooSmall); + return std::unexpected(TokenCodecErrc::OutputTooSmall); } for (int i = curResultSize - 2; i >= 0; --i) @@ -614,11 +614,11 @@ encodeBase58Token( std::array buf{}; if (input.size() > kTmpBufSize - 5) { - return Unexpected(TokenCodecErrc::InputTooLarge); + return std::unexpected(TokenCodecErrc::InputTooLarge); } if (input.empty()) { - return Unexpected(TokenCodecErrc::InputTooSmall); + return std::unexpected(TokenCodecErrc::InputTooSmall); } // buf[0] = static_cast(tokenType); @@ -648,23 +648,23 @@ decodeBase58Token(TokenType type, std::string_view s, std::span ou // Reject zero length tokens if (ret.size() < 6) - return Unexpected(TokenCodecErrc::InputTooSmall); + return std::unexpected(TokenCodecErrc::InputTooSmall); // The type must match. if (type != static_cast(static_cast(ret[0]))) - return Unexpected(TokenCodecErrc::MismatchedTokenType); + return std::unexpected(TokenCodecErrc::MismatchedTokenType); // And the checksum must as well. std::array guard{}; checksum(guard.data(), ret.data(), ret.size() - guard.size()); if (!std::equal(guard.rbegin(), guard.rend(), ret.rbegin())) { - return Unexpected(TokenCodecErrc::MismatchedChecksum); + return std::unexpected(TokenCodecErrc::MismatchedChecksum); } std::size_t const outSize = ret.size() - 1 - guard.size(); if (outBuf.size() < outSize) - return Unexpected(TokenCodecErrc::OutputTooSmall); + return std::unexpected(TokenCodecErrc::OutputTooSmall); // Skip the leading type byte and the trailing checksum. std::copy(ret.begin() + 1, ret.begin() + outSize + 1, outBuf.begin()); return outBuf.subspan(0, outSize); diff --git a/src/libxrpl/tx/SignerEntries.cpp b/src/libxrpl/tx/SignerEntries.cpp index 7251b8260f..943b66292e 100644 --- a/src/libxrpl/tx/SignerEntries.cpp +++ b/src/libxrpl/tx/SignerEntries.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -12,19 +11,20 @@ #include #include +#include #include #include #include namespace xrpl { -Expected, NotTEC> +std::expected, NotTEC> SignerEntries::deserialize(STObject const& obj, beast::Journal journal, std::string_view annotation) { if (!obj.isFieldPresent(sfSignerEntries)) { JLOG(journal.trace()) << "Malformed " << annotation << ": Need signer entry array."; - return Unexpected(temMALFORMED); + return std::unexpected(temMALFORMED); } std::vector accountVec; @@ -37,7 +37,7 @@ SignerEntries::deserialize(STObject const& obj, beast::Journal journal, std::str if (sEntry.getFName() != sfSignerEntry) { JLOG(journal.trace()) << "Malformed " << annotation << ": Expected SignerEntry."; - return Unexpected(temMALFORMED); + return std::unexpected(temMALFORMED); } // Extract SignerEntry fields. diff --git a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp index 7c9c9d95dc..273beaea0f 100644 --- a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp +++ b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -40,6 +39,7 @@ #include #include +#include #include #include #include @@ -186,7 +186,7 @@ checkAttestationPublicKey( enum class CheckDst { Check, Ignore }; template -Expected, TER> +std::expected, TER> claimHelper( XChainAttestationsBase& attestations, ReadView const& view, @@ -233,7 +233,7 @@ claimHelper( if (weight >= quorum) return rewardAccounts; - return Unexpected(tecXCHAIN_CLAIM_NO_QUORUM); + return std::unexpected(tecXCHAIN_CLAIM_NO_QUORUM); } /** @@ -337,7 +337,7 @@ onNewAttestations( // Check if there is a quorum of attestations for the given amount and // chain. If so return the reward accounts, if not return the tec code (most // likely tecXCHAIN_CLAIM_NO_QUORUM) -Expected, TER> +std::expected, TER> onClaim( XChainClaimAttestations& attestations, ReadView const& view, @@ -847,14 +847,14 @@ applyClaimAttestations( AccountID cidOwner; }; - auto const scopeResult = [&]() -> Expected { + auto const scopeResult = [&]() -> std::expected { // This lambda is ugly - admittedly. The purpose of this lambda is to // limit the scope of sles so they don't overlap with // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child // views, it's important that the sle's lifetime doesn't overlap. auto const sleClaimID = psb.peek(claimIDKeylet); if (!sleClaimID) - return Unexpected(tecXCHAIN_NO_CLAIM_ID); + return std::unexpected(tecXCHAIN_NO_CLAIM_ID); // Add claims that are part of the signer's list to the "claims" vector std::vector atts; @@ -868,13 +868,13 @@ applyClaimAttestations( if (atts.empty()) { - return Unexpected(tecXCHAIN_PROOF_UNKNOWN_KEY); + return std::unexpected(tecXCHAIN_PROOF_UNKNOWN_KEY); } AccountID const otherChainSource = (*sleClaimID)[sfOtherChainSource]; if (attBegin->sendingAccount != otherChainSource) { - return Unexpected(tecXCHAIN_SENDING_ACCOUNT_MISMATCH); + return std::unexpected(tecXCHAIN_SENDING_ACCOUNT_MISMATCH); } { @@ -885,7 +885,7 @@ applyClaimAttestations( if (attDstChain != dstChain) { - return Unexpected(tecXCHAIN_WRONG_CHAIN); + return std::unexpected(tecXCHAIN_WRONG_CHAIN); } } @@ -964,10 +964,10 @@ applyCreateAccountAttestations( PaymentSandbox psb(&view); - auto const claimCountResult = [&]() -> Expected { + auto const claimCountResult = [&]() -> std::expected { auto const sleBridge = psb.peek(bridgeK); if (!sleBridge) - return Unexpected(tecINTERNAL); + return std::unexpected(tecINTERNAL); return (*sleBridge)[sfXChainAccountClaimCount]; }(); @@ -1009,7 +1009,7 @@ applyCreateAccountAttestations( XChainCreateAccountAttestations curAtts; }; - auto const scopeResult = [&]() -> Expected { + auto const scopeResult = [&]() -> std::expected { // This lambda is ugly - admittedly. The purpose of this lambda is to // limit the scope of sles so they don't overlap with // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child @@ -1025,14 +1025,14 @@ applyCreateAccountAttestations( auto const sleDoor = psb.peek(doorK); if (!sleDoor) - return Unexpected(tecINTERNAL); + return std::unexpected(tecINTERNAL); // Check reserve auto const balance = (*sleDoor)[sfBalance]; auto const reserve = psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1); if (balance < reserve) - return Unexpected(tecINSUFFICIENT_RESERVE); + return std::unexpected(tecINSUFFICIENT_RESERVE); } std::vector atts; @@ -1045,7 +1045,7 @@ applyCreateAccountAttestations( } if (atts.empty()) { - return Unexpected(tecXCHAIN_PROOF_UNKNOWN_KEY); + return std::unexpected(tecXCHAIN_PROOF_UNKNOWN_KEY); } XChainCreateAccountAttestations curAtts = [&] { @@ -1071,7 +1071,7 @@ applyCreateAccountAttestations( // Modify the object before it's potentially deleted, so the meta // data will include the new attestations if (!sleClaimID) - return Unexpected(tecINTERNAL); + return std::unexpected(tecINTERNAL); sleClaimID->setFieldArray(sfXChainCreateAccountAttestations, curAtts.toSTArray()); psb.update(sleClaimID); } @@ -1244,7 +1244,7 @@ attestationDoApply(ApplyContext& ctx) Keylet bridgeK; }; - auto const scopeResult = [&]() -> Expected { + auto const scopeResult = [&]() -> std::expected { // This lambda is ugly - admittedly. The purpose of this lambda is to // limit the scope of sles so they don't overlap with // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child @@ -1252,7 +1252,7 @@ attestationDoApply(ApplyContext& ctx) auto sleBridge = readBridge(ctx.view(), bridgeSpec); if (!sleBridge) { - return Unexpected(tecNO_ENTRY); + return std::unexpected(tecNO_ENTRY); } Keylet const bridgeK{ltBRIDGE, sleBridge->key()}; AccountID const thisDoor = (*sleBridge)[sfAccount]; @@ -1269,7 +1269,7 @@ attestationDoApply(ApplyContext& ctx) } else { - return Unexpected(tecINTERNAL); + return std::unexpected(tecINTERNAL); } } STXChainBridge::ChainType const srcChain = STXChainBridge::otherChain(dstChain); @@ -1279,7 +1279,7 @@ attestationDoApply(ApplyContext& ctx) getSignersListAndQuorum(ctx.view(), *sleBridge, ctx.journal); if (!isTesSuccess(slTer)) - return Unexpected(slTer); + return std::unexpected(slTer); return ScopeResult{srcChain, std::move(signersList), quorum, thisDoor, bridgeK}; }(); @@ -1721,7 +1721,7 @@ XChainClaim::doApply() STAmount signatureReward; }; - auto const scopeResult = [&]() -> Expected { + auto const scopeResult = [&]() -> std::expected { // This lambda is ugly - admittedly. The purpose of this lambda is to // limit the scope of sles so they don't overlap with // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child @@ -1732,7 +1732,7 @@ XChainClaim::doApply() auto const sleClaimID = psb.peek(claimIDKeylet); if (!(sleBridge && sleClaimID && sleAcct)) - return Unexpected(tecINTERNAL); + return std::unexpected(tecINTERNAL); AccountID const thisDoor = (*sleBridge)[sfAccount]; @@ -1748,7 +1748,7 @@ XChainClaim::doApply() } else { - return Unexpected(tecINTERNAL); + return std::unexpected(tecINTERNAL); } } STXChainBridge::ChainType const srcChain = STXChainBridge::otherChain(dstChain); @@ -1763,7 +1763,7 @@ XChainClaim::doApply() getSignersListAndQuorum(ctx_.view(), *sleBridge, ctx_.journal); if (!isTesSuccess(slTer)) - return Unexpected(slTer); + return std::unexpected(slTer); XChainClaimAttestations curAtts{sleClaimID->getFieldArray(sfXChainClaimAttestations)}; @@ -1776,7 +1776,7 @@ XChainClaim::doApply() signersList, ctx_.journal); if (!claimR.has_value()) - return Unexpected(claimR.error()); + return std::unexpected(claimR.error()); return ScopeResult{ .rewardAccounts = claimR.value(), diff --git a/src/libxrpl/tx/transactors/dex/AMMBid.cpp b/src/libxrpl/tx/transactors/dex/AMMBid.cpp index a98f439d0a..83432fa0d0 100644 --- a/src/libxrpl/tx/transactors/dex/AMMBid.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMBid.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -27,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -266,7 +266,7 @@ applyBid(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Journa auto const bidMin = ctx.tx[~sfBidMin]; auto const bidMax = ctx.tx[~sfBidMax]; - auto getPayPrice = [&](Number const& computedPrice) -> Expected { + auto getPayPrice = [&](Number const& computedPrice) -> std::expected { auto const payPrice = [&]() -> std::optional { // Both min/max bid price are defined if (bidMin && bidMax) @@ -295,11 +295,11 @@ applyBid(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Journa }(); if (!payPrice) { - return Unexpected(tecAMM_FAILED); + return std::unexpected(tecAMM_FAILED); } if (payPrice > lpTokens) { - return Unexpected(tecAMM_INVALID_TOKENS); + return std::unexpected(tecAMM_INVALID_TOKENS); } return *payPrice; }; diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp index 0e1a4b3a3d..041bf73abf 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -28,6 +27,7 @@ #include #include +#include #include #include @@ -83,7 +83,7 @@ LoanBrokerCoverClawback::preflight(PreflightContext const& ctx) return tesSUCCESS; } -Expected +std::expected determineBrokerID(ReadView const& view, STTx const& tx) { // If the broker ID was provided in the transaction, that's all we @@ -96,7 +96,7 @@ determineBrokerID(ReadView const& view, STTx const& tx) // because that should have been rejected in preflight(). auto const dstAmount = tx[~sfAmount]; if (!dstAmount || !dstAmount->holds()) - return Unexpected{tecINTERNAL}; // LCOV_EXCL_LINE + return std::unexpected{tecINTERNAL}; // LCOV_EXCL_LINE // Every trust line is bidirectional. Both sides are simultaneously // issuer and holder. For this transaction, the Account is acting as @@ -112,7 +112,7 @@ determineBrokerID(ReadView const& view, STTx const& tx) // If the account was not found, the transaction can't go further. if (!sle) - return Unexpected{tecNO_ENTRY}; + return std::unexpected{tecNO_ENTRY}; // If the account was found, and has a LoanBrokerID (and therefore // is a pseudo-account), that's the @@ -122,11 +122,11 @@ determineBrokerID(ReadView const& view, STTx const& tx) // If the account does not have a LoanBrokerID, the transaction // can't go further, even if it's a different type of Pseudo-account. - return Unexpected{tecOBJECT_NOT_FOUND}; + return std::unexpected{tecOBJECT_NOT_FOUND}; // Or tecWRONG_ASSET? } -Expected +std::expected determineAsset( ReadView const& view, AccountID const& account, @@ -153,10 +153,10 @@ determineAsset( return Issue{amount.get().currency, account}; } - return Unexpected(tecWRONG_ASSET); + return std::unexpected(tecWRONG_ASSET); } -Expected +std::expected determineClawAmount( SLE const& sleBroker, Asset const& vaultAsset, @@ -182,7 +182,7 @@ determineClawAmount( return sleBroker[sfCoverAvailable] - minRequiredCover; }(); if (maxClawAmount <= beast::kZero) - return Unexpected(tecINSUFFICIENT_FUNDS); + return std::unexpected(tecINSUFFICIENT_FUNDS); // Use the vaultAsset here, because it will be the right type in all // circumstances. The amount may be an IOU indicating the pseudo-account's diff --git a/src/libxrpl/tx/transactors/lending/LoanPay.cpp b/src/libxrpl/tx/transactors/lending/LoanPay.cpp index a0a1479bdb..5c0b46de42 100644 --- a/src/libxrpl/tx/transactors/lending/LoanPay.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanPay.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -29,6 +28,7 @@ #include #include #include +#include #include namespace xrpl { @@ -380,7 +380,7 @@ LoanPay::doApply() return LoanPaymentType::Regular; }(); - Expected const paymentParts = + std::expected const paymentParts = loanMakePayment(asset, view, loanSle, brokerSle, amount, paymentType, j_); if (!paymentParts) diff --git a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp index bd7413d50b..d8e5a7b235 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -26,6 +25,7 @@ #include #include #include +#include #include // IWYU pragma: keep #include @@ -223,12 +223,12 @@ NFTokenMint::doApply() { auto const issuer = ctx_.tx[~sfIssuer].value_or(accountID_); - auto const tokenSeq = [this, &issuer]() -> Expected { + auto const tokenSeq = [this, &issuer]() -> std::expected { auto const root = view().peek(keylet::account(issuer)); if (root == nullptr) { // Should not happen. Checked in preclaim. - return Unexpected(tecNO_ISSUER); + return std::unexpected(tecNO_ISSUER); } // If the issuer hasn't minted an NFToken before we must add a @@ -259,7 +259,7 @@ NFTokenMint::doApply() (*root)[sfMintedNFTokens] = mintedNftCnt + 1u; if ((*root)[sfMintedNFTokens] == 0u) - return Unexpected(tecMAX_SEQUENCE_REACHED); + return std::unexpected(tecMAX_SEQUENCE_REACHED); // Get the unique sequence number of this token by // sfFirstNFTokenSequence + sfMintedNFTokens @@ -268,7 +268,7 @@ NFTokenMint::doApply() // Check for more overflow cases if (tokenSeq + 1u == 0u || tokenSeq < offset) - return Unexpected(tecMAX_SEQUENCE_REACHED); + return std::unexpected(tecMAX_SEQUENCE_REACHED); ctx_.view().update(root); return tokenSeq; diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp index 94a5ca848d..90e33d3a70 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -22,6 +21,7 @@ #include #include +#include #include namespace xrpl { @@ -100,16 +100,16 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) return tesSUCCESS; } -Expected +std::expected MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args) { auto const acct = view.peek(keylet::account(args.account)); if (!acct) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE if (args.priorBalance && *(args.priorBalance) < view.fees().accountReserve((*acct)[sfOwnerCount] + 1)) - return Unexpected(tecINSUFFICIENT_RESERVE); + return std::unexpected(tecINSUFFICIENT_RESERVE); auto const mptId = makeMptID(args.sequence, args.account); auto const mptIssuanceKeylet = keylet::mptIssuance(mptId); @@ -120,7 +120,7 @@ MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreate keylet::ownerDir(args.account), mptIssuanceKeylet, describeOwnerDir(args.account)); if (!ownerNode) - return Unexpected(tecDIR_FULL); // LCOV_EXCL_LINE + return std::unexpected(tecDIR_FULL); // LCOV_EXCL_LINE auto mptIssuance = std::make_shared(mptIssuanceKeylet); (*mptIssuance)[sfFlags] = args.flags & ~tfUniversal; @@ -156,10 +156,10 @@ MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreate // would dangle the pointer and is a programmer error. auto const sleHolding = view.read(keylet::unchecked(*args.referenceHolding)); if (!sleHolding) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE auto const type = sleHolding->getType(); if (type != ltMPTOKEN && type != ltRIPPLE_STATE) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE (*mptIssuance)[sfReferenceHolding] = *args.referenceHolding; } diff --git a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp index eb12905467..a8587feaeb 100644 --- a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -26,6 +25,7 @@ #include #include +#include #include #include #include @@ -218,7 +218,7 @@ VaultClawback::preclaim(PreclaimContext const& ctx) return tecWRONG_ASSET; } -Expected, TER> +std::expected, TER> VaultClawback::assetsToClawback( SLE::ref vault, SLE::const_ref sleShareIssuance, @@ -230,7 +230,7 @@ VaultClawback::assetsToClawback( // preclaim should have blocked this , now it's an internal error // LCOV_EXCL_START JLOG(j_.error()) << "VaultClawback: asset mismatch in clawback."; - return Unexpected(tecINTERNAL); + return std::unexpected(tecINTERNAL); // LCOV_EXCL_STOP } @@ -248,7 +248,7 @@ VaultClawback::assetsToClawback( view(), holder, share, FreezeHandling::IgnoreFreeze, AuthHandling::IgnoreAuth, j_); auto const maybeAssets = sharesToAssetsWithdraw(vault, sleShareIssuance, sharesDestroyed); if (!maybeAssets) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE return std::make_pair(*maybeAssets, sharesDestroyed); } @@ -265,7 +265,7 @@ VaultClawback::assetsToClawback( auto const maybeAssets = sharesToAssetsWithdraw(vault, sleShareIssuance, sharesDestroyed); if (!maybeAssets) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE assetsRecovered = *maybeAssets; } @@ -274,13 +274,13 @@ VaultClawback::assetsToClawback( auto const maybeShares = assetsToSharesWithdraw(vault, sleShareIssuance, clawbackAmount); if (!maybeShares) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE sharesDestroyed = *maybeShares; auto const maybeAssets = sharesToAssetsWithdraw(vault, sleShareIssuance, sharesDestroyed); if (!maybeAssets) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE assetsRecovered = *maybeAssets; } // Clamp to maximum. @@ -294,20 +294,20 @@ VaultClawback::assetsToClawback( auto const maybeShares = assetsToSharesWithdraw( vault, sleShareIssuance, assetsRecovered, TruncateShares::Yes); if (!maybeShares) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE sharesDestroyed = *maybeShares; } auto const maybeAssets = sharesToAssetsWithdraw(vault, sleShareIssuance, sharesDestroyed); if (!maybeAssets) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE assetsRecovered = *maybeAssets; if (assetsRecovered > *assetsAvailable) { // LCOV_EXCL_START JLOG(j_.error()) << "VaultClawback: invalid rounding of shares."; - return Unexpected(tecINTERNAL); + return std::unexpected(tecINTERNAL); // LCOV_EXCL_STOP } } @@ -322,7 +322,7 @@ VaultClawback::assetsToClawback( << ", assetsTotal=" << vault->at(sfAssetsTotal).value() << ", sharesTotal=" << sleShareIssuance->at(sfOutstandingAmount) << ", amount=" << clawbackAmount.value(); - return Unexpected(tecPATH_DRY); + return std::unexpected(tecPATH_DRY); } return std::make_pair(assetsRecovered, sharesDestroyed); diff --git a/src/test/app/Delegate_test.cpp b/src/test/app/Delegate_test.cpp index 588aeee634..70b091290c 100644 --- a/src/test/app/Delegate_test.cpp +++ b/src/test/app/Delegate_test.cpp @@ -2018,8 +2018,8 @@ class Delegate_test : public beast::unit_test::Suite auto jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result]; BEAST_EXPECT(jrr[jss::status] == "error"); BEAST_EXPECT( - jrr[jss::error_message].asString().find( - "A Signer may not be the transaction's Account") != std::string::npos); + jrr[jss::error_message].asString().contains( + "A Signer may not be the transaction's Account")); } } diff --git a/src/test/app/GRPCServerTLS_test.cpp b/src/test/app/GRPCServerTLS_test.cpp index ae0d839a6e..a48986d004 100644 --- a/src/test/app/GRPCServerTLS_test.cpp +++ b/src/test/app/GRPCServerTLS_test.cpp @@ -479,8 +479,7 @@ public: } catch (std::runtime_error const& e) { - BEAST_EXPECT( - std::string(e.what()).find("Incomplete TLS configuration") != std::string::npos); + BEAST_EXPECT(std::string(e.what()).contains("Incomplete TLS configuration")); } } @@ -505,8 +504,7 @@ public: } catch (std::runtime_error const& e) { - BEAST_EXPECT( - std::string(e.what()).find("Incomplete TLS configuration") != std::string::npos); + BEAST_EXPECT(std::string(e.what()).contains("Incomplete TLS configuration")); } } @@ -533,8 +531,8 @@ public: catch (std::runtime_error const& e) { BEAST_EXPECT( - std::string(e.what()).find( - "ssl_client_ca requires both ssl_cert and ssl_key") != std::string::npos); + std::string(e.what()).contains( + "ssl_client_ca requires both ssl_cert and ssl_key")); } } @@ -556,9 +554,7 @@ public: { // This should fail with "Incomplete TLS configuration" first // because ssl_cert is specified without ssl_key - BEAST_EXPECT( - std::string(e.what()).find("Incomplete TLS configuration") != - std::string::npos); + BEAST_EXPECT(std::string(e.what()).contains("Incomplete TLS configuration")); } } @@ -580,9 +576,7 @@ public: { // This should fail with "Incomplete TLS configuration" first // because ssl_key is specified without ssl_cert - BEAST_EXPECT( - std::string(e.what()).find("Incomplete TLS configuration") != - std::string::npos); + BEAST_EXPECT(std::string(e.what()).contains("Incomplete TLS configuration")); } } } @@ -610,8 +604,8 @@ public: catch (std::runtime_error const& e) { BEAST_EXPECT( - std::string(e.what()).find( - "ssl_cert_chain requires both ssl_cert and ssl_key") != std::string::npos); + std::string(e.what()).contains( + "ssl_cert_chain requires both ssl_cert and ssl_key")); } } @@ -633,9 +627,7 @@ public: { // This should fail with "Incomplete TLS configuration" first // because ssl_cert is specified without ssl_key - BEAST_EXPECT( - std::string(e.what()).find("Incomplete TLS configuration") != - std::string::npos); + BEAST_EXPECT(std::string(e.what()).contains("Incomplete TLS configuration")); } } } diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 3654036869..6d53d25661 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -216,7 +216,7 @@ class Invariants_test : public beast::unit_test::Suite // std::cerr << messages << '\n'; for (auto const& m : expectLogs) { - BEAST_EXPECTS(messages.find(m) != std::string::npos, m); + BEAST_EXPECTS(messages.contains(m), m); } } } @@ -2187,8 +2187,7 @@ class Invariants_test : public beast::unit_test::Suite BEAST_EXPECT(!invariant.finalize( makeOfferCreateTx(), tesSUCCESS, XRPAmount{}, view, missingRootJlog)); BEAST_EXPECT( - missingRootSink.messages().str().find("book directory root missing") != - std::string::npos); + missingRootSink.messages().str().contains("book directory root missing")); } { // delete diff --git a/src/test/app/MultiSign_test.cpp b/src/test/app/MultiSign_test.cpp index 5092cafef9..f161226210 100644 --- a/src/test/app/MultiSign_test.cpp +++ b/src/test/app/MultiSign_test.cpp @@ -1121,8 +1121,8 @@ public: // Signature should fail. auto const info = submitSTTx(local); BEAST_EXPECT( - info[jss::result][jss::error_exception].asString().find( - "Invalid signature on account r") != std::string::npos); + info[jss::result][jss::error_exception].asString().contains( + "Invalid signature on account r")); } { // Multisign with an empty signers array should fail. diff --git a/src/test/app/NFTokenBurn_test.cpp b/src/test/app/NFTokenBurn_test.cpp index 482d275d51..46b02d03cf 100644 --- a/src/test/app/NFTokenBurn_test.cpp +++ b/src/test/app/NFTokenBurn_test.cpp @@ -794,9 +794,8 @@ class NFTokenBurn_test : public beast::unit_test::Suite BEAST_EXPECT(sink.messages().str().starts_with("Invariant failed:")); // uncomment to log the invariant failure message // log << " --> " << sink.messages().str() << std::endl; - BEAST_EXPECT( - sink.messages().str().find( - "Last NFT page deleted with non-empty directory") != std::string::npos); + BEAST_EXPECT(sink.messages().str().contains( + "Last NFT page deleted with non-empty directory")); } } { @@ -831,8 +830,7 @@ class NFTokenBurn_test : public beast::unit_test::Suite BEAST_EXPECT(sink.messages().str().starts_with("Invariant failed:")); // uncomment to log the invariant failure message // log << " --> " << sink.messages().str() << std::endl; - BEAST_EXPECT( - sink.messages().str().find("Lost NextMinPage link") != std::string::npos); + BEAST_EXPECT(sink.messages().str().contains("Lost NextMinPage link")); } } } diff --git a/src/test/app/NetworkOPs_test.cpp b/src/test/app/NetworkOPs_test.cpp index d0a692b894..c5c8d33706 100644 --- a/src/test/app/NetworkOPs_test.cpp +++ b/src/test/app/NetworkOPs_test.cpp @@ -54,7 +54,7 @@ public: env.close(); } - BEAST_EXPECT(logs.find("No transaction to process!") != std::string::npos); + BEAST_EXPECT(logs.contains("No transaction to process!")); } }; diff --git a/src/test/app/ValidatorSite_test.cpp b/src/test/app/ValidatorSite_test.cpp index e3e3ec27df..8400f2d794 100644 --- a/src/test/app/ValidatorSite_test.cpp +++ b/src/test/app/ValidatorSite_test.cpp @@ -239,7 +239,7 @@ private: json::Value myStatus; for (auto const& vs : jv[jss::validator_sites]) { - if (vs[jss::uri].asString().find(u.uri) != std::string::npos) + if (vs[jss::uri].asString().contains(u.uri)) myStatus = vs; } BEAST_EXPECTS( @@ -248,9 +248,7 @@ private: if (!u.cfg.msg.empty()) { - BEAST_EXPECTS( - sink.messages().str().find(u.cfg.msg) != std::string::npos, - sink.messages().str()); + BEAST_EXPECTS(sink.messages().str().contains(u.cfg.msg), sink.messages().str()); } if (u.cfg.expectedRefreshMin != 0) @@ -324,7 +322,7 @@ private: json::Value myStatus; for (auto const& vs : jv[jss::validator_sites]) { - if (vs[jss::uri].asString().find(u.uri) != std::string::npos) + if (vs[jss::uri].asString().contains(u.uri)) myStatus = vs; } BEAST_EXPECTS( @@ -332,9 +330,7 @@ private: to_string(myStatus)); if (u.shouldFail) { - BEAST_EXPECTS( - sink.messages().str().find(u.expectMsg) != std::string::npos, - sink.messages().str()); + BEAST_EXPECTS(sink.messages().str().contains(u.expectMsg), sink.messages().str()); } } } diff --git a/src/test/basics/Expected_test.cpp b/src/test/basics/Expected_test.cpp deleted file mode 100644 index c8e570835b..0000000000 --- a/src/test/basics/Expected_test.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#if BOOST_VERSION >= 107500 -#endif // BOOST_VERSION -#include - -namespace xrpl::test { - -struct Expected_test : beast::unit_test::Suite -{ - void - run() override - { - // Test non-error const construction. - { - auto const expected = []() -> Expected { return "Valid value"; }(); - BEAST_EXPECT(expected); - BEAST_EXPECT(expected.has_value()); - BEAST_EXPECT(expected.value() == "Valid value"); - BEAST_EXPECT(*expected == "Valid value"); - BEAST_EXPECT(expected->at(0) == 'V'); - - bool throwOccurred = false; - try - { - // There's no error, so should throw. - [[maybe_unused]] TER const t = expected.error(); - } - catch (std::runtime_error const& e) - { - BEAST_EXPECT(e.what() == std::string("bad expected access")); - throwOccurred = true; - } - BEAST_EXPECT(throwOccurred); - } - // Test non-error non-const construction. - { - auto expected = []() -> Expected { return "Valid value"; }(); - BEAST_EXPECT(expected); - BEAST_EXPECT(expected.has_value()); - BEAST_EXPECT(expected.value() == "Valid value"); - BEAST_EXPECT(*expected == "Valid value"); - BEAST_EXPECT(expected->at(0) == 'V'); - std::string const mv = std::move(*expected); - BEAST_EXPECT(mv == "Valid value"); - - bool throwOccurred = false; - try - { - // There's no error, so should throw. - [[maybe_unused]] TER const t = expected.error(); - } - catch (std::runtime_error const& e) - { - BEAST_EXPECT(e.what() == std::string("bad expected access")); - throwOccurred = true; - } - BEAST_EXPECT(throwOccurred); - } - // Test non-error overlapping type construction. - { - auto expected = []() -> Expected { return 1; }(); - BEAST_EXPECT(expected); - BEAST_EXPECT(expected.has_value()); - BEAST_EXPECT(expected.value() == 1); - BEAST_EXPECT(*expected == 1); - - bool throwOccurred = false; - try - { - // There's no error, so should throw. - [[maybe_unused]] std::uint16_t const t = expected.error(); - } - catch (std::runtime_error const& e) - { - BEAST_EXPECT(e.what() == std::string("bad expected access")); - throwOccurred = true; - } - BEAST_EXPECT(throwOccurred); - } - // Test error construction from rvalue. - { - auto const expected = []() -> Expected { - return Unexpected(telLOCAL_ERROR); - }(); - BEAST_EXPECT(!expected); - BEAST_EXPECT(!expected.has_value()); - BEAST_EXPECT(expected.error() == telLOCAL_ERROR); - - bool throwOccurred = false; - try - { - // There's no result, so should throw. - [[maybe_unused]] std::string const s = *expected; - } - catch (std::runtime_error const& e) - { - BEAST_EXPECT(e.what() == std::string("bad expected access")); - throwOccurred = true; - } - BEAST_EXPECT(throwOccurred); - } - // Test error construction from lvalue. - { - auto const err(telLOCAL_ERROR); - auto expected = [&err]() -> Expected { return Unexpected(err); }(); - BEAST_EXPECT(!expected); - BEAST_EXPECT(!expected.has_value()); - BEAST_EXPECT(expected.error() == telLOCAL_ERROR); - - bool throwOccurred = false; - try - { - // There's no result, so should throw. - [[maybe_unused]] std::size_t const s = expected->size(); - } - catch (std::runtime_error const& e) - { - BEAST_EXPECT(e.what() == std::string("bad expected access")); - throwOccurred = true; - } - BEAST_EXPECT(throwOccurred); - } - // Test error construction from const char*. - { - auto const expected = []() -> Expected { - return Unexpected("Not what is expected!"); - }(); - BEAST_EXPECT(!expected); - BEAST_EXPECT(!expected.has_value()); - BEAST_EXPECT(expected.error() == std::string("Not what is expected!")); - } - // Test error construction of string from const char*. - { - auto expected = []() -> Expected { - return Unexpected("Not what is expected!"); - }(); - BEAST_EXPECT(!expected); - BEAST_EXPECT(!expected.has_value()); - BEAST_EXPECT(expected.error() == "Not what is expected!"); - std::string const s(std::move(expected.error())); - BEAST_EXPECT(s == "Not what is expected!"); - } - // Test non-error const construction of Expected. - { - auto const expected = []() -> Expected { return {}; }(); - BEAST_EXPECT(expected); - bool throwOccurred = false; - try - { - // There's no error, so should throw. - [[maybe_unused]] std::size_t const s = expected.error().size(); - } - catch (std::runtime_error const& e) - { - BEAST_EXPECT(e.what() == std::string("bad expected access")); - throwOccurred = true; - } - BEAST_EXPECT(throwOccurred); - } - // Test non-error non-const construction of Expected. - { - auto expected = []() -> Expected { return {}; }(); - BEAST_EXPECT(expected); - bool throwOccurred = false; - try - { - // There's no error, so should throw. - [[maybe_unused]] std::size_t const s = expected.error().size(); - } - catch (std::runtime_error const& e) - { - BEAST_EXPECT(e.what() == std::string("bad expected access")); - throwOccurred = true; - } - BEAST_EXPECT(throwOccurred); - } - // Test error const construction of Expected. - { - auto const expected = []() -> Expected { - return Unexpected("Not what is expected!"); - }(); - BEAST_EXPECT(!expected); - BEAST_EXPECT(expected.error() == "Not what is expected!"); - } - // Test error non-const construction of Expected. - { - auto expected = []() -> Expected { - return Unexpected("Not what is expected!"); - }(); - BEAST_EXPECT(!expected); - BEAST_EXPECT(expected.error() == "Not what is expected!"); - std::string const s(std::move(expected.error())); - BEAST_EXPECT(s == "Not what is expected!"); - } - // Test a case that previously unintentionally returned an array. -#if BOOST_VERSION >= 107500 - { - auto expected = []() -> Expected { - return boost::json::object{{"oops", "me array now"}}; - }(); - BEAST_EXPECT(expected); - BEAST_EXPECT(!expected.value().is_array()); - } -#endif // BOOST_VERSION - } -}; - -BEAST_DEFINE_TESTSUITE(Expected, basics, xrpl); - -} // namespace xrpl::test diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index 6cc8d4fde1..629a97f0ea 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -1296,14 +1296,11 @@ public: 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.contains("voting "s + (ourVote ? "YES" : "NO")), s, __FILE__, line); expect( - s.find("for "s + std::to_string(ourTime) + " rounds."s) != s.npos, - s, - __FILE__, - line); + s.contains("for "s + std::to_string(ourTime) + " rounds."s), s, __FILE__, line); expect( - s.find("votes in "s + std::to_string(peerTime) + " rounds.") != s.npos, + s.contains("votes in "s + std::to_string(peerTime) + " rounds."), s, __FILE__, line); diff --git a/src/test/jtx/CheckMessageLogs.h b/src/test/jtx/CheckMessageLogs.h index 2cc8c661eb..4bbdec6f06 100644 --- a/src/test/jtx/CheckMessageLogs.h +++ b/src/test/jtx/CheckMessageLogs.h @@ -24,7 +24,7 @@ class CheckMessageLogs : public Logs void write(beast::Severity level, std::string const& text) override { - if (text.find(owner_.msg_) != std::string::npos) + if (text.contains(owner_.msg_)) *owner_.pFound_ = true; } diff --git a/src/test/jtx/directory.h b/src/test/jtx/directory.h index 9a87266802..c63640ae1f 100644 --- a/src/test/jtx/directory.h +++ b/src/test/jtx/directory.h @@ -2,11 +2,11 @@ #include -#include #include #include #include +#include #include /** Directory operations. */ @@ -34,7 +34,7 @@ bumpLastPage( Env& env, std::uint64_t newLastPage, Keylet directory, - std::function adjust) -> Expected; + std::function adjust) -> std::expected; /// Implementation of adjust for the most common ledger entry, i.e. one where /// page index is stored in sfOwnerNode (and only there). Pass this function diff --git a/src/test/jtx/impl/directory.cpp b/src/test/jtx/impl/directory.cpp index 7a92c2bf25..9fb06e25df 100644 --- a/src/test/jtx/impl/directory.cpp +++ b/src/test/jtx/impl/directory.cpp @@ -2,7 +2,6 @@ #include -#include #include #include #include @@ -15,6 +14,7 @@ #include #include +#include #include #include @@ -26,9 +26,9 @@ bumpLastPage( Env& env, std::uint64_t newLastPage, Keylet directory, - std::function adjust) -> Expected + std::function adjust) -> std::expected { - Expected res{}; + std::expected res{}; env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) -> bool { Sandbox sb(&view, TapNone); @@ -36,7 +36,7 @@ bumpLastPage( auto sleRoot = sb.peek(directory); if (!sleRoot) { - res = Unexpected(Error::DirectoryRootNotFound); + res = std::unexpected(Error::DirectoryRootNotFound); return false; } @@ -44,26 +44,26 @@ bumpLastPage( auto const lastIndex = sleRoot->getFieldU64(sfIndexPrevious); if (lastIndex == 0) { - res = Unexpected(Error::DirectoryTooSmall); + res = std::unexpected(Error::DirectoryTooSmall); return false; } if (sb.exists(keylet::page(directory, newLastPage))) { - res = Unexpected(Error::DirectoryPageDuplicate); + res = std::unexpected(Error::DirectoryPageDuplicate); return false; } if (lastIndex >= newLastPage) { - res = Unexpected(Error::InvalidLastPage); + res = std::unexpected(Error::InvalidLastPage); return false; } auto slePage = sb.peek(keylet::page(directory, lastIndex)); if (!slePage) { - res = Unexpected(Error::DirectoryPageNotFound); + res = std::unexpected(Error::DirectoryPageNotFound); return false; } @@ -94,7 +94,7 @@ bumpLastPage( auto slePrev = sb.peek(keylet::page(directory, *prevIndex)); if (!slePrev) { - res = Unexpected(Error::DirectoryPageNotFound); + res = std::unexpected(Error::DirectoryPageNotFound); return false; } slePrev->setFieldU64(sfIndexNext, newLastPage); @@ -109,7 +109,7 @@ bumpLastPage( { if (!adjust(sb, key, newLastPage)) { - res = Unexpected(Error::AdjustmentError); + res = std::unexpected(Error::AdjustmentError); return false; } } diff --git a/src/test/nodestore/NuDBFactory_test.cpp b/src/test/nodestore/NuDBFactory_test.cpp index 3ca3fa6838..d0675b3893 100644 --- a/src/test/nodestore/NuDBFactory_test.cpp +++ b/src/test/nodestore/NuDBFactory_test.cpp @@ -88,7 +88,7 @@ private: auto backend = Manager::instance().makeBackend(params, megabytes(4), scheduler, journal); std::string const logOutput = sink.messages().str(); - BEAST_EXPECT(logOutput.find(expectedMessage) != std::string::npos); + BEAST_EXPECT(logOutput.contains(expectedMessage)); } // Helper function to test power of two validation @@ -105,7 +105,7 @@ private: auto backend = Manager::instance().makeBackend(params, megabytes(4), scheduler, journal); std::string const logOutput = sink.messages().str(); - bool const hasWarning = logOutput.find("Invalid nudb_block_size") != std::string::npos; + bool const hasWarning = logOutput.contains("Invalid nudb_block_size"); BEAST_EXPECT(hasWarning == !shouldWork); } @@ -221,10 +221,8 @@ public: catch (std::exception const& e) { std::string const logOutput{e.what()}; - BEAST_EXPECT(logOutput.find("Invalid nudb_block_size: 5000") != std::string::npos); - BEAST_EXPECT( - logOutput.find("Must be power of 2 between 4096 and 32768") != - std::string::npos); + BEAST_EXPECT(logOutput.contains("Invalid nudb_block_size: 5000")); + BEAST_EXPECT(logOutput.contains("Must be power of 2 between 4096 and 32768")); } } @@ -247,8 +245,7 @@ public: catch (std::exception const& e) { std::string const logOutput{e.what()}; - BEAST_EXPECT( - logOutput.find("Invalid nudb_block_size value: invalid") != std::string::npos); + BEAST_EXPECT(logOutput.contains("Invalid nudb_block_size value: invalid")); } } } @@ -291,7 +288,7 @@ public: catch (std::exception const& e) { std::string const logOutput{e.what()}; - BEAST_EXPECT(logOutput.find("Invalid nudb_block_size") != std::string::npos); + BEAST_EXPECT(logOutput.contains("Invalid nudb_block_size")); } } } @@ -355,8 +352,7 @@ public: // Should log success message for valid values std::string const logOutput = sink.messages().str(); - bool const hasSuccessMessage = - logOutput.find("Using custom NuDB block size") != std::string::npos; + bool const hasSuccessMessage = logOutput.contains("Using custom NuDB block size"); BEAST_EXPECT(hasSuccessMessage); } diff --git a/src/test/overlay/reduce_relay_test.cpp b/src/test/overlay/reduce_relay_test.cpp index 54d8f555a2..1c9bf1f9c1 100644 --- a/src/test/overlay/reduce_relay_test.cpp +++ b/src/test/overlay/reduce_relay_test.cpp @@ -806,7 +806,7 @@ public: { auto size = max - min; std::vector s(size); - std::iota(s.begin(), s.end(), min); + std::iota(s.begin(), s.end(), min); // NOLINT(modernize-use-ranges) std::random_device d; std::mt19937 g(d()); std::shuffle(s.begin(), s.end(), g); diff --git a/src/test/server/ServerStatus_test.cpp b/src/test/server/ServerStatus_test.cpp index 5ba71962b3..4a9c12c96b 100644 --- a/src/test/server/ServerStatus_test.cpp +++ b/src/test/server/ServerStatus_test.cpp @@ -801,7 +801,7 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En if (!BEAST_EXPECTS(!ec, ec.message())) return; BEAST_EXPECT(resp.result() == boost::beast::http::status::ok); - BEAST_EXPECT(resp.body().find("connectivity is working.") != std::string::npos); + BEAST_EXPECT(resp.body().contains("connectivity is working.")); // mark the Network as having an Amendment Warning, but won't fail env.app().getOPs().setAmendmentWarned(); @@ -846,7 +846,7 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En if (!BEAST_EXPECTS(!ec, ec.message())) return; BEAST_EXPECT(resp.result() == boost::beast::http::status::ok); - BEAST_EXPECT(resp.body().find("connectivity is working.") != std::string::npos); + BEAST_EXPECT(resp.body().contains("connectivity is working.")); // with ELB_SUPPORT, status still does not indicate a problem env.app().config().elbSupport = true; @@ -866,7 +866,7 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En if (!BEAST_EXPECTS(!ec, ec.message())) return; BEAST_EXPECT(resp.result() == boost::beast::http::status::ok); - BEAST_EXPECT(resp.body().find("connectivity is working.") != std::string::npos); + BEAST_EXPECT(resp.body().contains("connectivity is working.")); } void @@ -929,7 +929,7 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En if (!BEAST_EXPECTS(!ec, ec.message())) return; BEAST_EXPECT(resp.result() == boost::beast::http::status::ok); - BEAST_EXPECT(resp.body().find("connectivity is working.") != std::string::npos); + BEAST_EXPECT(resp.body().contains("connectivity is working.")); // mark the Network as Amendment Blocked, but still won't fail until // ELB is enabled (next step) @@ -977,7 +977,7 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En if (!BEAST_EXPECTS(!ec, ec.message())) return; BEAST_EXPECT(resp.result() == boost::beast::http::status::ok); - BEAST_EXPECT(resp.body().find("connectivity is working.") != std::string::npos); + BEAST_EXPECT(resp.body().contains("connectivity is working.")); env.app().config().elbSupport = true; @@ -996,8 +996,8 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En if (!BEAST_EXPECTS(!ec, ec.message())) return; BEAST_EXPECT(resp.result() == boost::beast::http::status::internal_server_error); - BEAST_EXPECT(resp.body().find("cannot accept clients:") != std::string::npos); - BEAST_EXPECT(resp.body().find("Server version too old") != std::string::npos); + BEAST_EXPECT(resp.body().contains("cannot accept clients:")); + BEAST_EXPECT(resp.body().contains("Server version too old")); } void diff --git a/src/test/server/Server_test.cpp b/src/test/server/Server_test.cpp index 455a365b7c..54091ef767 100644 --- a/src/test/server/Server_test.cpp +++ b/src/test/server/Server_test.cpp @@ -401,7 +401,7 @@ public: }), std::make_unique(&messages)}; }); - BEAST_EXPECT(messages.find("Missing 'ip' in [port_rpc]") != std::string::npos); + BEAST_EXPECT(messages.contains("Missing 'ip' in [port_rpc]")); except([&] { Env const env{ @@ -413,7 +413,7 @@ public: }), std::make_unique(&messages)}; }); - BEAST_EXPECT(messages.find("Missing 'port' in [port_rpc]") != std::string::npos); + BEAST_EXPECT(messages.contains("Missing 'port' in [port_rpc]")); except([&] { Env const env{ @@ -426,8 +426,7 @@ public: }), std::make_unique(&messages)}; }); - BEAST_EXPECT( - messages.find("Invalid value '0' for key 'port' in [port_rpc]") == std::string::npos); + BEAST_EXPECT(!messages.contains("Invalid value '0' for key 'port' in [port_rpc]")); except([&] { Env const env{ @@ -438,8 +437,7 @@ public: }), std::make_unique(&messages)}; }); - BEAST_EXPECT( - messages.find("Invalid value '0' for key 'port' in [server]") != std::string::npos); + BEAST_EXPECT(messages.contains("Invalid value '0' for key 'port' in [server]")); except([&] { Env const env{ @@ -453,7 +451,7 @@ public: }), std::make_unique(&messages)}; }); - BEAST_EXPECT(messages.find("Missing 'protocol' in [port_rpc]") != std::string::npos); + BEAST_EXPECT(messages.contains("Missing 'protocol' in [port_rpc]")); except([&] // this creates a standard test config without the server // section @@ -482,7 +480,7 @@ public: }), std::make_unique(&messages)}; }); - BEAST_EXPECT(messages.find("Required section [server] is missing") != std::string::npos); + BEAST_EXPECT(messages.contains("Required section [server] is missing")); except([&] // this creates a standard test config without some of the // port sections @@ -503,7 +501,7 @@ public: }), std::make_unique(&messages)}; }); - BEAST_EXPECT(messages.find("Missing section: [port_peer]") != std::string::npos); + BEAST_EXPECT(messages.contains("Missing section: [port_peer]")); } void diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 325f8ba038..323dc14673 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -783,7 +783,7 @@ PeerImp::onShutdown(error_code ec) // - broken_pipe: the peer is gone bool const shouldLog = (ec != boost::asio::error::eof && ec != boost::asio::error::operation_aborted && - ec.message().find("application data after close notify") == std::string::npos); + !ec.message().contains("application data after close notify")); if (shouldLog) { diff --git a/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp b/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp index 4d0d10a66b..38827dc93c 100644 --- a/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include #include @@ -24,6 +23,7 @@ #include #include +#include #include namespace xrpl::RPC { @@ -381,7 +381,7 @@ lookupLedger(std::shared_ptr& ledger, JsonContext const& context return result; } -Expected, json::Value> +std::expected, json::Value> getOrAcquireLedger(RPC::JsonContext const& context) { auto const hasHash = context.params.isMember(jss::ledger_hash); @@ -393,7 +393,7 @@ getOrAcquireLedger(RPC::JsonContext const& context) if ((static_cast(hasHash) + static_cast(hasIndex)) != 1) { - return Unexpected( + return std::unexpected( RPC::makeParamError( "Exactly one of 'ledger_hash' or " "'ledger_index' can be specified.")); @@ -403,29 +403,29 @@ getOrAcquireLedger(RPC::JsonContext const& context) { auto const& jsonHash = context.params.get(jss::ledger_hash, json::ValueType::Null); if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString())) - return Unexpected(RPC::expectedFieldError(jss::ledger_hash, "hex string")); + return std::unexpected(RPC::expectedFieldError(jss::ledger_hash, "hex string")); } else { auto const& jsonIndex = context.params.get(jss::ledger_index, json::ValueType::Null); if (!jsonIndex.isInt() && !jsonIndex.isUInt()) - return Unexpected(RPC::expectedFieldError(jss::ledger_index, "number")); + return std::unexpected(RPC::expectedFieldError(jss::ledger_index, "number")); // We need a validated ledger to get the hash from the sequence if (ledgerMaster.getValidatedLedgerAge() > RPC::Tuning::kMaxValidatedLedgerAge) { if (context.apiVersion == 1) - return Unexpected(rpcError(RpcNoCurrent)); - return Unexpected(rpcError(RpcNotSynced)); + return std::unexpected(rpcError(RpcNoCurrent)); + return std::unexpected(rpcError(RpcNotSynced)); } ledgerIndex = jsonIndex.asInt(); auto ledger = ledgerMaster.getValidatedLedger(); if (ledgerIndex >= ledger->header().seq) - return Unexpected(RPC::makeParamError("Ledger index too large")); + return std::unexpected(RPC::makeParamError("Ledger index too large")); if (ledgerIndex <= 0) - return Unexpected(RPC::makeParamError("Ledger index too small")); + return std::unexpected(RPC::makeParamError("Ledger index too small")); auto const j = context.app.getJournal("RPCHandler"); // Try to get the hash of the desired ledger from the validated @@ -452,7 +452,7 @@ getOrAcquireLedger(RPC::JsonContext const& context) json::Value jvResult = RPC::makeError( RpcLgrNotFound, "acquiring ledger containing requested index"); jvResult[jss::acquiring] = getJson(LedgerFill(*il, &context)); - return Unexpected(jvResult); + return std::unexpected(jvResult); } if (auto il = context.app.getInboundLedgers().find(*refHash)) @@ -461,11 +461,11 @@ getOrAcquireLedger(RPC::JsonContext const& context) json::Value jvResult = RPC::makeError( RpcLgrNotFound, "acquiring ledger containing requested index"); jvResult[jss::acquiring] = il->getJson(0); - return Unexpected(jvResult); + return std::unexpected(jvResult); } // Likely the app is shutting down - return Unexpected(json::Value()); + return std::unexpected(json::Value()); } neededHash = hashOfSeq(*ledger, ledgerIndex, j); @@ -487,9 +487,10 @@ getOrAcquireLedger(RPC::JsonContext const& context) return ledger; if (auto il = context.app.getInboundLedgers().find(ledgerHash)) - return Unexpected(il->getJson(0)); + return std::unexpected(il->getJson(0)); - return Unexpected(RPC::makeError(RpcNotReady, "findCreate failed to return an inbound ledger")); + return std::unexpected( + RPC::makeError(RpcNotReady, "findCreate failed to return an inbound ledger")); } } // namespace xrpl::RPC diff --git a/src/xrpld/rpc/detail/RPCLedgerHelpers.h b/src/xrpld/rpc/detail/RPCLedgerHelpers.h index faec4ae069..9ec2a6673c 100644 --- a/src/xrpld/rpc/detail/RPCLedgerHelpers.h +++ b/src/xrpld/rpc/detail/RPCLedgerHelpers.h @@ -10,6 +10,7 @@ #include #include +#include #include namespace xrpl { @@ -163,11 +164,11 @@ ledgerFromSpecifier( * * @param context The RPC JsonContext containing request parameters and * environment. - * @return Expected, json::Value> + * @return std::expected, json::Value> * On success, contains a shared pointer to the requested Ledger. * On failure, contains a json::Value describing the error. */ -Expected, json::Value> +std::expected, json::Value> getOrAcquireLedger(RPC::JsonContext const& context); } // namespace RPC diff --git a/src/xrpld/rpc/detail/TransactionSign.cpp b/src/xrpld/rpc/detail/TransactionSign.cpp index 86d895fa1b..8c3a5ea245 100644 --- a/src/xrpld/rpc/detail/TransactionSign.cpp +++ b/src/xrpld/rpc/detail/TransactionSign.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include #include @@ -57,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -407,23 +407,23 @@ checkTxJsonFields( return ret; } -static Expected +static std::expected checkNetworkID(json::Value const& txJson, uint32_t appNetworkId) { if (appNetworkId > 1024) { if (!txJson.isMember(jss::NetworkID)) { - return Unexpected( + return std::unexpected( RPC::makeError(RpcInvalidParams, RPC::missingFieldMessage("tx_json.NetworkID"))); } if (!txJson[jss::NetworkID].isIntegral() || txJson[jss::NetworkID].asUInt() != appNetworkId) { - return Unexpected( + return std::unexpected( RPC::makeError(RpcInvalidParams, RPC::invalidFieldMessage("tx_json.NetworkID"))); } } - return Expected(); + return std::expected(); } //------------------------------------------------------------------------------ diff --git a/src/xrpld/rpc/handlers/ledger/Ledger.cpp b/src/xrpld/rpc/handlers/ledger/Ledger.cpp index 1b096002bd..5938c8c9c5 100644 --- a/src/xrpld/rpc/handlers/ledger/Ledger.cpp +++ b/src/xrpld/rpc/handlers/ledger/Ledger.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include #include @@ -28,6 +27,7 @@ #include #include +#include #include #include #include @@ -44,14 +44,14 @@ LedgerHandler::check() { auto const& params = context_.params; - auto getBool = [&](json::StaticString const& field) -> Expected { + auto getBool = [&](json::StaticString const& field) -> std::expected { if (!params.isMember(field)) { return false; } if (!params[field].isBool()) { - return Unexpected(RpcInvalidParams); + return std::unexpected(RpcInvalidParams); } return params[field].asBool(); diff --git a/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp b/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp index 9a9119d2ba..236712f0c2 100644 --- a/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -27,6 +26,7 @@ #include #include +#include #include #include #include @@ -34,12 +34,12 @@ namespace xrpl { -using FunctionType = std::function( +using FunctionType = std::function( json::Value const&, json::StaticString const, unsigned const apiVersion)>; -static Expected +static std::expected parseFixed( Keylet const& keylet, json::Value const& params, @@ -55,12 +55,12 @@ fixed(Keylet const& keylet) return [keylet]( json::Value const& params, json::StaticString const fieldName, - unsigned const apiVersion) -> Expected { + unsigned const apiVersion) -> std::expected { return parseFixed(keylet, params, fieldName, apiVersion); }; } -static Expected +static std::expected parseObjectID( json::Value const& params, json::StaticString const fieldName, @@ -73,7 +73,7 @@ parseObjectID( return LedgerEntryHelpers::invalidFieldError("malformedRequest", fieldName, expectedType); } -static Expected +static std::expected parseIndex(json::Value const& params, json::StaticString const fieldName, unsigned const apiVersion) { if (apiVersion > 2u && params.isString()) @@ -95,7 +95,7 @@ parseIndex(json::Value const& params, json::StaticString const fieldName, unsign return parseObjectID(params, fieldName, "hex string"); } -static Expected +static std::expected parseAccountRoot( json::Value const& params, json::StaticString const fieldName, @@ -111,7 +111,7 @@ parseAccountRoot( auto const parseAmendments = fixed(keylet::amendments()); -static Expected +static std::expected parseAMM( json::Value const& params, json::StaticString const fieldName, @@ -125,21 +125,21 @@ parseAMM( if (auto const value = LedgerEntryHelpers::hasRequired(params, {jss::asset, jss::asset2}); !value) { - return Unexpected(value.error()); + return std::unexpected(value.error()); } auto const asset = LedgerEntryHelpers::requiredAsset(params, jss::asset, "malformedRequest"); if (!asset) - return Unexpected(asset.error()); + return std::unexpected(asset.error()); auto const asset2 = LedgerEntryHelpers::requiredAsset(params, jss::asset2, "malformedRequest"); if (!asset2) - return Unexpected(asset2.error()); + return std::unexpected(asset2.error()); return keylet::amm(*asset, *asset2).key; } -static Expected +static std::expected parseBridge( json::Value const& params, json::StaticString const fieldName, @@ -147,7 +147,7 @@ parseBridge( { if (!params.isMember(jss::bridge)) { - return Unexpected(LedgerEntryHelpers::missingFieldError(jss::bridge)); + return std::unexpected(LedgerEntryHelpers::missingFieldError(jss::bridge)); } if (params[jss::bridge].isString()) @@ -157,12 +157,12 @@ parseBridge( auto const bridge = LedgerEntryHelpers::parseBridgeFields(params[jss::bridge]); if (!bridge) - return Unexpected(bridge.error()); + return std::unexpected(bridge.error()); auto const account = LedgerEntryHelpers::requiredAccountID( params, jss::bridge_account, "malformedBridgeAccount"); if (!account) - return Unexpected(account.error()); + return std::unexpected(account.error()); STXChainBridge::ChainType const chainType = STXChainBridge::srcChain(account.value() == bridge->lockingChainDoor()); @@ -172,7 +172,7 @@ parseBridge( return keylet::bridge(*bridge, chainType).key; } -static Expected +static std::expected parseCheck( json::Value const& params, json::StaticString const fieldName, @@ -181,7 +181,7 @@ parseCheck( return parseObjectID(params, fieldName, "hex string"); } -static Expected +static std::expected parseCredential( json::Value const& cred, json::StaticString const fieldName, @@ -195,22 +195,22 @@ parseCredential( auto const subject = LedgerEntryHelpers::requiredAccountID(cred, jss::subject, "malformedRequest"); if (!subject) - return Unexpected(subject.error()); + return std::unexpected(subject.error()); auto const issuer = LedgerEntryHelpers::requiredAccountID(cred, jss::issuer, "malformedRequest"); if (!issuer) - return Unexpected(issuer.error()); + return std::unexpected(issuer.error()); auto const credType = LedgerEntryHelpers::requiredHexBlob( cred, jss::credential_type, kMaxCredentialTypeLength, "malformedRequest"); if (!credType) - return Unexpected(credType.error()); + return std::unexpected(credType.error()); return keylet::credential(*subject, *issuer, Slice(credType->data(), credType->size())).key; } -static Expected +static std::expected parseDelegate( json::Value const& params, json::StaticString const fieldName, @@ -224,17 +224,17 @@ parseDelegate( auto const account = LedgerEntryHelpers::requiredAccountID(params, jss::account, "malformedAddress"); if (!account) - return Unexpected(account.error()); + return std::unexpected(account.error()); auto const authorize = LedgerEntryHelpers::requiredAccountID(params, jss::authorize, "malformedAddress"); if (!authorize) - return Unexpected(authorize.error()); + return std::unexpected(authorize.error()); return keylet::delegate(*account, *authorize).key; } -static Expected +static std::expected parseAuthorizeCredentials(json::Value const& jv) { if (!jv.isArray()) @@ -246,7 +246,7 @@ parseAuthorizeCredentials(json::Value const& jv) std::uint32_t const n = jv.size(); if (n > kMaxCredentialsArraySize) { - return Unexpected( + return std::unexpected( LedgerEntryHelpers::malformedError( "malformedAuthorizedCredentials", "Invalid field '" + std::string(jss::authorized_credentials) + @@ -255,7 +255,7 @@ parseAuthorizeCredentials(json::Value const& jv) if (n == 0) { - return Unexpected( + return std::unexpected( LedgerEntryHelpers::malformedError( "malformedAuthorizedCredentials", "Invalid field '" + std::string(jss::authorized_credentials) + "', array empty.")); @@ -274,18 +274,18 @@ parseAuthorizeCredentials(json::Value const& jv) jo, {jss::issuer, jss::credential_type}, "malformedAuthorizedCredentials"); !value) { - return Unexpected(value.error()); + return std::unexpected(value.error()); } auto const issuer = LedgerEntryHelpers::requiredAccountID( jo, jss::issuer, "malformedAuthorizedCredentials"); if (!issuer) - return Unexpected(issuer.error()); + return std::unexpected(issuer.error()); auto const credentialType = LedgerEntryHelpers::requiredHexBlob( jo, jss::credential_type, kMaxCredentialTypeLength, "malformedAuthorizedCredentials"); if (!credentialType) - return Unexpected(credentialType.error()); + return std::unexpected(credentialType.error()); auto credential = STObject::makeInnerObject(sfCredential); credential.setAccountID(sfIssuer, *issuer); @@ -296,7 +296,7 @@ parseAuthorizeCredentials(json::Value const& jv) return arr; } -static Expected +static std::expected parseDepositPreauth( json::Value const& dp, json::StaticString const fieldName, @@ -318,7 +318,7 @@ parseDepositPreauth( auto const owner = LedgerEntryHelpers::requiredAccountID(dp, jss::owner, "malformedOwner"); if (!owner) { - return Unexpected(owner.error()); + return std::unexpected(owner.error()); } if (dp.isMember(jss::authorized)) @@ -334,7 +334,7 @@ parseDepositPreauth( auto const& ac(dp[jss::authorized_credentials]); auto const arr = parseAuthorizeCredentials(ac); if (!arr.has_value()) - return Unexpected(arr.error()); + return std::unexpected(arr.error()); auto const& sorted = credentials::makeSorted(arr.value()); if (sorted.empty()) @@ -347,7 +347,7 @@ parseDepositPreauth( return keylet::depositPreauth(*owner, sorted).key; } -static Expected +static std::expected parseDID( json::Value const& params, json::StaticString const fieldName, @@ -362,7 +362,7 @@ parseDID( return keylet::did(*account).key; } -static Expected +static std::expected parseDirectoryNode( json::Value const& params, json::StaticString const fieldName, @@ -413,7 +413,7 @@ parseDirectoryNode( return LedgerEntryHelpers::malformedError("malformedRequest", ""); } -static Expected +static std::expected parseEscrow( json::Value const& params, json::StaticString const fieldName, @@ -426,17 +426,17 @@ parseEscrow( auto const id = LedgerEntryHelpers::requiredAccountID(params, jss::owner, "malformedOwner"); if (!id) - return Unexpected(id.error()); + return std::unexpected(id.error()); auto const seq = LedgerEntryHelpers::requiredUInt32(params, jss::seq, "malformedSeq"); if (!seq) - return Unexpected(seq.error()); + return std::unexpected(seq.error()); return keylet::escrow(*id, *seq).key; } auto const parseFeeSettings = fixed(keylet::fees()); -static Expected +static std::expected parseFixed( Keylet const& keylet, json::Value const& params, @@ -455,7 +455,7 @@ parseFixed( return keylet.key; } -static Expected +static std::expected parseLedgerHashes( json::Value const& params, json::StaticString const fieldName, @@ -475,7 +475,7 @@ parseLedgerHashes( return parseFixed(keylet::skip(), params, fieldName, apiVersion); } -static Expected +static std::expected parseLoanBroker( json::Value const& params, json::StaticString const fieldName, @@ -488,15 +488,15 @@ parseLoanBroker( auto const id = LedgerEntryHelpers::requiredAccountID(params, jss::owner, "malformedOwner"); if (!id) - return Unexpected(id.error()); + return std::unexpected(id.error()); auto const seq = LedgerEntryHelpers::requiredUInt32(params, jss::seq, "malformedSeq"); if (!seq) - return Unexpected(seq.error()); + return std::unexpected(seq.error()); return keylet::loanbroker(*id, *seq).key; } -static Expected +static std::expected parseLoan( json::Value const& params, json::StaticString const fieldName, @@ -510,15 +510,15 @@ parseLoan( auto const id = LedgerEntryHelpers::requiredUInt256(params, jss::loan_broker_id, "malformedBroker"); if (!id) - return Unexpected(id.error()); + return std::unexpected(id.error()); auto const seq = LedgerEntryHelpers::requiredUInt32(params, jss::loan_seq, "malformedSeq"); if (!seq) - return Unexpected(seq.error()); + return std::unexpected(seq.error()); return keylet::loan(*id, *seq).key; } -static Expected +static std::expected parseMPToken( json::Value const& params, json::StaticString const fieldName, @@ -532,17 +532,17 @@ parseMPToken( auto const mptIssuanceID = LedgerEntryHelpers::requiredUInt192(params, jss::mpt_issuance_id, "malformedMPTIssuanceID"); if (!mptIssuanceID) - return Unexpected(mptIssuanceID.error()); + return std::unexpected(mptIssuanceID.error()); auto const account = LedgerEntryHelpers::requiredAccountID(params, jss::account, "malformedAccount"); if (!account) - return Unexpected(account.error()); + return std::unexpected(account.error()); return keylet::mptoken(*mptIssuanceID, *account).key; } -static Expected +static std::expected parseMPTokenIssuance( json::Value const& params, json::StaticString const fieldName, @@ -558,7 +558,7 @@ parseMPTokenIssuance( return keylet::mptIssuance(*mptIssuanceID).key; } -static Expected +static std::expected parseNFTokenOffer( json::Value const& params, json::StaticString const fieldName, @@ -567,7 +567,7 @@ parseNFTokenOffer( return parseObjectID(params, fieldName, "hex string"); } -static Expected +static std::expected parseNFTokenPage( json::Value const& params, json::StaticString const fieldName, @@ -578,7 +578,7 @@ parseNFTokenPage( auto const parseNegativeUNL = fixed(keylet::negativeUNL()); -static Expected +static std::expected parseOffer( json::Value const& params, json::StaticString const fieldName, @@ -591,16 +591,16 @@ parseOffer( auto const id = LedgerEntryHelpers::requiredAccountID(params, jss::account, "malformedAddress"); if (!id) - return Unexpected(id.error()); + return std::unexpected(id.error()); auto const seq = LedgerEntryHelpers::requiredUInt32(params, jss::seq, "malformedRequest"); if (!seq) - return Unexpected(seq.error()); + return std::unexpected(seq.error()); return keylet::offer(*id, *seq).key; } -static Expected +static std::expected parseOracle( json::Value const& params, json::StaticString const fieldName, @@ -613,17 +613,17 @@ parseOracle( auto const id = LedgerEntryHelpers::requiredAccountID(params, jss::account, "malformedAccount"); if (!id) - return Unexpected(id.error()); + return std::unexpected(id.error()); auto const seq = LedgerEntryHelpers::requiredUInt32(params, jss::oracle_document_id, "malformedDocumentID"); if (!seq) - return Unexpected(seq.error()); + return std::unexpected(seq.error()); return keylet::oracle(*id, *seq).key; } -static Expected +static std::expected parsePayChannel( json::Value const& params, json::StaticString const fieldName, @@ -632,7 +632,7 @@ parsePayChannel( return parseObjectID(params, fieldName, "hex string"); } -static Expected +static std::expected parsePermissionedDomain( json::Value const& pd, json::StaticString const fieldName, @@ -652,16 +652,16 @@ parsePermissionedDomain( auto const account = LedgerEntryHelpers::requiredAccountID(pd, jss::account, "malformedAddress"); if (!account) - return Unexpected(account.error()); + return std::unexpected(account.error()); auto const seq = LedgerEntryHelpers::requiredUInt32(pd, jss::seq, "malformedRequest"); if (!seq) - return Unexpected(seq.error()); + return std::unexpected(seq.error()); return keylet::permissionedDomain(*account, pd[jss::seq].asUInt()).key; } -static Expected +static std::expected parseRippleState( json::Value const& jvRippleState, json::StaticString const fieldName, @@ -678,7 +678,7 @@ parseRippleState( LedgerEntryHelpers::hasRequired(jvRippleState, {jss::currency, jss::accounts}); !value) { - return Unexpected(value.error()); + return std::unexpected(value.error()); } if (!jvRippleState[jss::accounts].isArray() || jvRippleState[jss::accounts].size() != 2) @@ -710,7 +710,7 @@ parseRippleState( return keylet::line(*id1, *id2, uCurrency).key; } -static Expected +static std::expected parseSignerList( json::Value const& params, json::StaticString const fieldName, @@ -719,7 +719,7 @@ parseSignerList( return parseObjectID(params, fieldName, "hex string"); } -static Expected +static std::expected parseTicket( json::Value const& params, json::StaticString const fieldName, @@ -732,17 +732,17 @@ parseTicket( auto const id = LedgerEntryHelpers::requiredAccountID(params, jss::account, "malformedAddress"); if (!id) - return Unexpected(id.error()); + return std::unexpected(id.error()); auto const seq = LedgerEntryHelpers::requiredUInt32(params, jss::ticket_seq, "malformedRequest"); if (!seq) - return Unexpected(seq.error()); + return std::unexpected(seq.error()); return getTicketIndex(*id, *seq); } -static Expected +static std::expected parseVault( json::Value const& params, json::StaticString const fieldName, @@ -755,16 +755,16 @@ parseVault( auto const id = LedgerEntryHelpers::requiredAccountID(params, jss::owner, "malformedOwner"); if (!id) - return Unexpected(id.error()); + return std::unexpected(id.error()); auto const seq = LedgerEntryHelpers::requiredUInt32(params, jss::seq, "malformedRequest"); if (!seq) - return Unexpected(seq.error()); + return std::unexpected(seq.error()); return keylet::vault(*id, *seq).key; } -static Expected +static std::expected parseXChainOwnedClaimID( json::Value const& claimId, json::StaticString const fieldName, @@ -777,20 +777,20 @@ parseXChainOwnedClaimID( auto const bridgeSpec = LedgerEntryHelpers::parseBridgeFields(claimId); if (!bridgeSpec) - return Unexpected(bridgeSpec.error()); + return std::unexpected(bridgeSpec.error()); auto const seq = LedgerEntryHelpers::requiredUInt32( claimId, jss::xchain_owned_claim_id, "malformedXChainOwnedClaimID"); if (!seq) { - return Unexpected(seq.error()); + return std::unexpected(seq.error()); } - Keylet keylet = keylet::xChainClaimID(*bridgeSpec, *seq); + Keylet const keylet = keylet::xChainClaimID(*bridgeSpec, *seq); return keylet.key; } -static Expected +static std::expected parseXChainOwnedCreateAccountClaimID( json::Value const& claimId, json::StaticString const fieldName, @@ -803,7 +803,7 @@ parseXChainOwnedCreateAccountClaimID( auto const bridgeSpec = LedgerEntryHelpers::parseBridgeFields(claimId); if (!bridgeSpec) - return Unexpected(bridgeSpec.error()); + return std::unexpected(bridgeSpec.error()); auto const seq = LedgerEntryHelpers::requiredUInt32( claimId, @@ -811,10 +811,10 @@ parseXChainOwnedCreateAccountClaimID( "malformedXChainOwnedCreateAccountClaimID"); if (!seq) { - return Unexpected(seq.error()); + return std::unexpected(seq.error()); } - Keylet keylet = keylet::xChainCreateAccountClaimID(*bridgeSpec, *seq); + Keylet const keylet = keylet::xChainCreateAccountClaimID(*bridgeSpec, *seq); return keylet.key; } diff --git a/src/xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h b/src/xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h index e8e295ca2c..463547a90d 100644 --- a/src/xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h +++ b/src/xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h @@ -10,41 +10,42 @@ #include #include +#include #include namespace xrpl::LedgerEntryHelpers { -inline Unexpected +inline std::unexpected missingFieldError(json::StaticString const field, std::optional err = std::nullopt) { json::Value json = json::ValueType::Object; json[jss::error] = err.value_or("malformedRequest"); json[jss::error_code] = RpcInvalidParams; json[jss::error_message] = RPC::missingFieldMessage(std::string(field.cStr())); - return Unexpected(json); + return std::unexpected(json); } -inline Unexpected +inline std::unexpected invalidFieldError(std::string const& err, json::StaticString const field, std::string const& type) { json::Value json = json::ValueType::Object; json[jss::error] = err; json[jss::error_code] = RpcInvalidParams; json[jss::error_message] = RPC::expectedFieldMessage(field, type); - return Unexpected(json); + return std::unexpected(json); } -inline Unexpected +inline std::unexpected malformedError(std::string const& err, std::string const& message) { json::Value json = json::ValueType::Object; json[jss::error] = err; json[jss::error_code] = RpcInvalidParams; json[jss::error_message] = message; - return Unexpected(json); + return std::unexpected(json); } -inline Expected +inline std::expected hasRequired( json::Value const& params, std::initializer_list fields, @@ -65,7 +66,7 @@ std::optional parse(json::Value const& param); template -Expected +std::expected required( json::Value const& params, json::StaticString const fieldName, @@ -99,7 +100,7 @@ parse(json::Value const& param) return account; } -inline Expected +inline std::expected requiredAccountID( json::Value const& params, json::StaticString const fieldName, @@ -121,7 +122,7 @@ parseHexBlob(json::Value const& param, std::size_t maxLength) return blob; } -inline Expected +inline std::expected requiredHexBlob( json::Value const& params, json::StaticString const fieldName, @@ -156,7 +157,7 @@ parse(json::Value const& param) return std::nullopt; } -inline Expected +inline std::expected requiredUInt32( json::Value const& params, json::StaticString const fieldName, @@ -178,7 +179,7 @@ parse(json::Value const& param) return uNodeIndex; } -inline Expected +inline std::expected requiredUInt256( json::Value const& params, json::StaticString const fieldName, @@ -200,7 +201,7 @@ parse(json::Value const& param) return field; } -inline Expected +inline std::expected requiredUInt192( json::Value const& params, json::StaticString const fieldName, @@ -223,13 +224,13 @@ parse(json::Value const& param) } } -inline Expected +inline std::expected requiredAsset(json::Value const& params, json::StaticString const fieldName, std::string const& err) { return required(params, fieldName, err, "Asset"); } -inline Expected +inline std::expected parseBridgeFields(json::Value const& params) { if (auto const value = hasRequired( @@ -240,21 +241,21 @@ parseBridgeFields(json::Value const& params) jss::IssuingChainIssue}); !value) { - return Unexpected(value.error()); + return std::unexpected(value.error()); } auto const lockingChainDoor = requiredAccountID(params, jss::LockingChainDoor, "malformedLockingChainDoor"); if (!lockingChainDoor) { - return Unexpected(lockingChainDoor.error()); + return std::unexpected(lockingChainDoor.error()); } auto const issuingChainDoor = requiredAccountID(params, jss::IssuingChainDoor, "malformedIssuingChainDoor"); if (!issuingChainDoor) { - return Unexpected(issuingChainDoor.error()); + return std::unexpected(issuingChainDoor.error()); } Issue lockingChainIssue; diff --git a/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp b/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp index b9f4a42880..2c2f96b0e8 100644 --- a/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp +++ b/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -18,7 +17,7 @@ #include #include #include -#include +#include // IWYU pragma: keep #include #include #include @@ -27,6 +26,7 @@ #include #include +#include #include #include #include @@ -35,7 +35,7 @@ namespace xrpl { -Expected +std::expected getAsset(json::Value const& v, beast::Journal j) { try @@ -46,7 +46,7 @@ getAsset(json::Value const& v, beast::Journal j) { JLOG(j.debug()) << "getAsset " << ex.what(); } - return Unexpected(RpcIssueMalformed); + return std::unexpected(RpcIssueMalformed); } std::string @@ -79,7 +79,7 @@ doAMMInfo(RPC::JsonContext& context) SLE::const_pointer amm; }; - auto getValuesFromContextParams = [&]() -> Expected { + auto getValuesFromContextParams = [&]() -> std::expected { std::optional accountID; std::optional asset1; std::optional asset2; @@ -92,7 +92,7 @@ doAMMInfo(RPC::JsonContext& context) // NOTE, identical check for apVersion >= 3 below if (context.apiVersion < 3 && kInvalid(params)) - return Unexpected(RpcInvalidParams); + return std::unexpected(RpcInvalidParams); if (params.isMember(jss::asset)) { @@ -102,7 +102,7 @@ doAMMInfo(RPC::JsonContext& context) } else { - return Unexpected(i.error()); + return std::unexpected(i.error()); } } @@ -114,7 +114,7 @@ doAMMInfo(RPC::JsonContext& context) } else { - return Unexpected(i.error()); + return std::unexpected(i.error()); } } @@ -122,25 +122,25 @@ doAMMInfo(RPC::JsonContext& context) { auto const id = parseBase58((params[jss::amm_account].asString())); if (!id) - return Unexpected(RpcActMalformed); + return std::unexpected(RpcActMalformed); auto const sle = ledger->read(keylet::account(*id)); if (!sle) - return Unexpected(RpcActMalformed); + return std::unexpected(RpcActMalformed); ammID = sle->getFieldH256(sfAMMID); if (ammID->isZero()) - return Unexpected(RpcActNotFound); + return std::unexpected(RpcActNotFound); } if (params.isMember(jss::account)) { accountID = parseBase58(params[jss::account].asString()); if (!accountID || !ledger->read(keylet::account(*accountID))) - return Unexpected(RpcActMalformed); + return std::unexpected(RpcActMalformed); } // NOTE, identical check for apVersion < 3 above if (context.apiVersion >= 3 && kInvalid(params)) - return Unexpected(RpcInvalidParams); + return std::unexpected(RpcInvalidParams); XRPL_ASSERT( (asset1.has_value() == asset2.has_value()) && (asset1.has_value() != ammID.has_value()), @@ -154,7 +154,7 @@ doAMMInfo(RPC::JsonContext& context) }(); auto const amm = ledger->read(ammKeylet); if (!amm) - return Unexpected(RpcActNotFound); + return std::unexpected(RpcActNotFound); if (!asset1 && !asset2) { asset1 = (*amm)[sfAsset]; diff --git a/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp b/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp index aa23a7af26..1e9e0ddc14 100644 --- a/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp +++ b/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp @@ -65,7 +65,7 @@ ServerDefinitions::translate(std::string const& inp) }; // TODO: use string::contains with C++23 - auto contains = [&](std::string_view s) -> bool { return inp.find(s) != std::string::npos; }; + auto contains = [&](std::string_view s) -> bool { return inp.contains(s); }; if (contains("UINT")) { diff --git a/src/xrpld/rpc/handlers/transaction/Simulate.cpp b/src/xrpld/rpc/handlers/transaction/Simulate.cpp index 676f0318a2..7ee28c4886 100644 --- a/src/xrpld/rpc/handlers/transaction/Simulate.cpp +++ b/src/xrpld/rpc/handlers/transaction/Simulate.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include #include @@ -33,6 +32,7 @@ #include #include +#include #include #include #include @@ -42,7 +42,7 @@ namespace xrpl { -static Expected +static std::expected getAutofillSequence(json::Value const& txJson, RPC::JsonContext& context) { // autofill Sequence @@ -52,13 +52,13 @@ getAutofillSequence(json::Value const& txJson, RPC::JsonContext& context) { // sanity check, should fail earlier // LCOV_EXCL_START - return Unexpected(RPC::invalidFieldError("tx.Account")); + return std::unexpected(RPC::invalidFieldError("tx.Account")); // LCOV_EXCL_STOP } auto const srcAddressID = parseBase58(accountStr.asString()); if (!srcAddressID.has_value()) { - return Unexpected( + return std::unexpected( RPC::makeError(RpcSrcActMalformed, RPC::invalidFieldMessage("tx.Account"))); } SLE::const_pointer const sle = @@ -69,7 +69,7 @@ getAutofillSequence(json::Value const& txJson, RPC::JsonContext& context) << "Failed to find source account " << "in current ledger: " << toBase58(*srcAddressID); - return Unexpected(rpcError(RpcSrcActNotFound)); + return std::unexpected(rpcError(RpcSrcActNotFound)); } return hasTicketSeq ? 0 : context.app.getTxQ().nextQueuableSeq(sle).value(); diff --git a/src/xrpld/rpc/handlers/transaction/Submit.cpp b/src/xrpld/rpc/handlers/transaction/Submit.cpp index f0c4cb2391..79f3680684 100644 --- a/src/xrpld/rpc/handlers/transaction/Submit.cpp +++ b/src/xrpld/rpc/handlers/transaction/Submit.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -21,17 +20,18 @@ #include #include +#include #include #include namespace xrpl { -static Expected +static std::expected getFailHard(RPC::JsonContext const& context) { if (context.params.isMember(jss::fail_hard) && !context.params[jss::fail_hard].isBool()) { - return Unexpected(RPC::expectedFieldError(jss::fail_hard, "boolean")); + return std::unexpected(RPC::expectedFieldError(jss::fail_hard, "boolean")); } return NetworkOPs::doFailHard( context.params.isMember(jss::fail_hard) && context.params[jss::fail_hard].asBool()); From 2cbc3c139e0469c98e49d4fe9453de2d15f6f4e4 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Tue, 9 Jun 2026 13:46:56 -0400 Subject: [PATCH 076/135] fix: Fix Number comparison operator (#7406) --- include/xrpl/basics/Number.h | 35 ++++++----- src/test/basics/Number_test.cpp | 104 ++++++++++++++++++++++++++++++-- 2 files changed, 121 insertions(+), 18 deletions(-) diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index 93bef82a8c..cee0c45355 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -408,33 +408,40 @@ public: } friend constexpr bool - operator<(Number const& x, Number const& y) noexcept + operator<(Number const& l, Number const& r) noexcept { + bool const lneg = l.negative_; + bool const rneg = r.negative_; + // If the two amounts have different signs (zero is treated as positive) // then the comparison is true iff the left is negative. - bool const lneg = x.negative_; - bool const rneg = y.negative_; - if (lneg != rneg) return lneg; - // Both have same sign and the left is zero: the right must be - // greater than 0. - if (x.mantissa_ == 0) - return y.mantissa_ > 0; + // Both have same sign and the left is zero: both must be non-negative. + // If the right is greater than 0, then it is larger, so the comparison is true. + if (l.mantissa_ == 0) + return r.mantissa_ > 0; - // Both have same sign, the right is zero and the left is non-zero. - if (y.mantissa_ == 0) + // Both have same sign, the right is zero and the left is non-zero, so the left must be + // positive, and thus is larger, so the comparison is false. + if (r.mantissa_ == 0) return false; // Both have the same sign, compare by exponents: - if (x.exponent_ > y.exponent_) + if (l.exponent_ > r.exponent_) return lneg; - if (x.exponent_ < y.exponent_) + if (l.exponent_ < r.exponent_) return !lneg; - // If equal exponents, compare mantissas - return x.mantissa_ < y.mantissa_; + // If equal signs and exponents, compare mantissas. + if (lneg) + { + // If negative, the operator is reversed. + return l.mantissa_ > r.mantissa_; + } + + return l.mantissa_ < r.mantissa_; } /** Return the sign of the amount */ diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index 81019970ad..f4bd1c9d66 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,8 @@ #include #include #include +#include +#include namespace xrpl { @@ -1386,10 +1389,103 @@ public: testRelationals() { testcase << "test_relationals " << to_string(Number::getMantissaScale()); - BEAST_EXPECT(!(Number{100} < Number{10})); - BEAST_EXPECT(Number{100} > Number{10}); - BEAST_EXPECT(Number{100} >= Number{10}); - BEAST_EXPECT(!(Number{100} <= Number{10})); + + { + auto test = [this](auto const& nums) { + BEAST_EXPECT(std::ranges::is_sorted(nums)); + + for (auto iter1 = nums.begin(); iter1 != nums.end(); ++iter1) + { + auto iter2 = iter1; + for (++iter2; iter2 != nums.end(); ++iter2) + { + Number const& smaller = *iter1; + Number const& larger = *iter2; + std::stringstream ss; + ss << smaller << " < " << larger; + auto const str = ss.str(); + + // The ==/!= operators use a completely different code path than <, etc. + // This helps detect a breakage in one but not the other. It also helps + // verify that the values are being ordered correctly. + BEAST_EXPECTS(smaller != larger, str + " (!=)"); + BEAST_EXPECTS(!(smaller == larger), str + " (==)"); + + // true results using operator< and derived operators + BEAST_EXPECTS(smaller < larger, str + " (<)"); + BEAST_EXPECTS(larger > smaller, str + " (>)"); + BEAST_EXPECTS(larger >= smaller, str + " (>=)"); + BEAST_EXPECTS(smaller <= larger, str + " (<=)"); + + // false results using operator< and derived operators + BEAST_EXPECTS(!(larger < smaller), str + " (! <)"); + BEAST_EXPECTS(!(smaller > larger), str + " (! >)"); + BEAST_EXPECTS(!(smaller >= larger), str + " (! >=)"); + BEAST_EXPECTS(!(larger <= smaller), str + " (! <=)"); + } + } + }; + + auto const intNums = [this]() { + // Inequality test cases are built from a list of sorted integers + auto const values = + std::to_array({-100, -50, -20, -10, -1, 0, 1, 10, 20, 50, 100}); + // Check this list is sorted before converting it to Numbers. + // That way if any of the other tests fail, we know it's because of code and not the + // source data. + BEAST_EXPECT(std::ranges::is_sorted(values)); + + std::vector result; + result.reserve(values.size()); + for (auto const v : values) + result.emplace_back(v); + return result; + }(); + + auto const otherNums = std::to_array({ + Number{-5, 100}, + Number{-1, 100}, + Number{-7, -10}, + Number{-2, -10}, + Number{0}, + Number{2, -10}, + Number{7, -10}, + Number{1, 100}, + Number{5, 100}, + }); + + test(intNums); + test(otherNums); + } + + { + // Equality test cases are . Number will be compared against itself + using Case = std::pair; + auto const c = std::to_array({ + {700, __LINE__}, + {50, __LINE__}, + {1, __LINE__}, + {0, __LINE__}, + {-1, __LINE__}, + {-30, __LINE__}, + {-600, __LINE__}, + }); + for (auto const& [n, line] : c) + { + auto const str = to_string(n); + + // NOLINTBEGIN(misc-redundant-expression) Explicitly testing operators with + // equivalent values + expect(n == n, str + " ==", __FILE__, line); + expect(!(n != n), str + " !=", __FILE__, line); + + expect(!(n < n), str + " < ", __FILE__, line); + expect(!(n > n), str + " >", __FILE__, line); + expect(n >= n, str + " >=", __FILE__, line); + expect(n <= n, str + " <=", __FILE__, line); + // NOLINTEND(misc-redundant-expression) + } + } } void From 8617eaeb26f07e7c671b8f86fb639179a5998f71 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 10 Jun 2026 01:00:19 +0100 Subject: [PATCH 077/135] ci: Launch upload-conan-deps on profile change (#7442) --- .github/workflows/upload-conan-deps.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 1a52ceee63..7ca9d13007 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -30,6 +30,7 @@ on: - ".github/scripts/strategy-matrix/**" - conanfile.py - conan.lock + - conan/profiles/** env: CONAN_REMOTE_NAME: xrplf From 742aa0878bbccdce385db4c8b6b77f2da19fad4a Mon Sep 17 00:00:00 2001 From: Bart Date: Wed, 10 Jun 2026 05:16:53 -0400 Subject: [PATCH 078/135] test: Do not create data directory for memory databases (#7323) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- src/xrpld/app/misc/SHAMapStoreImp.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/xrpld/app/misc/SHAMapStoreImp.cpp b/src/xrpld/app/misc/SHAMapStoreImp.cpp index 7259c233e4..33bbf1c613 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.cpp +++ b/src/xrpld/app/misc/SHAMapStoreImp.cpp @@ -387,8 +387,12 @@ void SHAMapStoreImp::dbPaths() { Section const section{app_.config().section(Sections::kNodeDatabase)}; - boost::filesystem::path dbPath = get(section, Keys::kPath); + // Skip creating the directory when an in-memory database is used. + if (boost::iequals(get(section, Keys::kType), "memory")) + return; + + boost::filesystem::path dbPath = get(section, Keys::kPath); if (boost::filesystem::exists(dbPath)) { if (!boost::filesystem::is_directory(dbPath)) From 8a4bf2dee60e4082b584c1702eabf93225f8da40 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 10 Jun 2026 11:16:03 +0100 Subject: [PATCH 079/135] refactor: Retire fixUniversalNumber amendment (#5962) Signed-off-by: Pratik Mankawde Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) --- include/xrpl/protocol/AMMCore.h | 2 +- include/xrpl/protocol/IOUAmount.h | 33 ---- include/xrpl/protocol/Rules.h | 2 - include/xrpl/protocol/detail/features.macro | 2 +- src/libxrpl/protocol/AMMCore.cpp | 2 +- src/libxrpl/protocol/IOUAmount.cpp | 104 +---------- src/libxrpl/protocol/Rules.cpp | 4 - src/libxrpl/protocol/STAmount.cpp | 188 +++----------------- src/libxrpl/tx/Transactor.cpp | 3 - src/libxrpl/tx/apply.cpp | 2 - src/libxrpl/tx/applySteps.cpp | 4 +- src/test/app/AMM_test.cpp | 7 +- src/test/app/NFToken_test.cpp | 14 +- src/test/app/OfferMPT_test.cpp | 1 - src/test/app/Offer_test.cpp | 60 +++---- src/test/basics/Number_test.cpp | 1 - src/xrpld/app/misc/detail/TxQ.cpp | 5 - 17 files changed, 55 insertions(+), 379 deletions(-) diff --git a/include/xrpl/protocol/AMMCore.h b/include/xrpl/protocol/AMMCore.h index ced84c4c87..a83c8bfa84 100644 --- a/include/xrpl/protocol/AMMCore.h +++ b/include/xrpl/protocol/AMMCore.h @@ -65,7 +65,7 @@ invalidAMMAssetPair( std::optional ammAuctionTimeSlot(std::uint64_t current, STObject const& auctionSlot); -/** Return true if required AMM amendments are enabled +/** Return true if required AMM amendment is enabled */ bool ammEnabled(Rules const&); diff --git a/include/xrpl/protocol/IOUAmount.h b/include/xrpl/protocol/IOUAmount.h index 9e0fbe38eb..b057f1c245 100644 --- a/include/xrpl/protocol/IOUAmount.h +++ b/include/xrpl/protocol/IOUAmount.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -179,36 +178,4 @@ to_string(IOUAmount const& amount); IOUAmount mulRatio(IOUAmount const& amt, std::uint32_t num, std::uint32_t den, bool roundUp); -// Since many uses of the number class do not have access to a ledger, -// getSTNumberSwitchover needs to be globally accessible. - -bool -getSTNumberSwitchover(); - -void -setSTNumberSwitchover(bool v); - -/** RAII class to set and restore the Number switchover. - */ - -class NumberSO -{ - bool saved_; - -public: - ~NumberSO() - { - setSTNumberSwitchover(saved_); - } - - NumberSO(NumberSO const&) = delete; - NumberSO& - operator=(NumberSO const&) = delete; - - explicit NumberSO(bool v) : saved_(getSTNumberSwitchover()) - { - setSTNumberSwitchover(v); - } -}; - } // namespace xrpl diff --git a/include/xrpl/protocol/Rules.h b/include/xrpl/protocol/Rules.h index 9c17ff2391..47b20756db 100644 --- a/include/xrpl/protocol/Rules.h +++ b/include/xrpl/protocol/Rules.h @@ -122,7 +122,6 @@ private: std::optional saved_; }; -class NumberSO; class NumberMantissaScaleGuard; bool @@ -131,7 +130,6 @@ useRulesGuards(Rules const& rules); void createGuards( Rules const& rules, - std::optional& stNumberSO, std::optional& rulesGuard, std::optional& mantissaScaleGuard); diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index c99c1e5ce8..2b2f24ba53 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -64,7 +64,6 @@ XRPL_FIX (DisallowIncomingV1, Supported::Yes, VoteBehavior::DefaultNo XRPL_FEATURE(XChainBridge, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FEATURE(AMM, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FEATURE(Clawback, Supported::Yes, VoteBehavior::DefaultNo) -XRPL_FIX (UniversalNumber, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FEATURE(XRPFees, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FIX (RemoveNFTokenAutoTrustLine, Supported::Yes, VoteBehavior::DefaultYes) @@ -112,6 +111,7 @@ XRPL_RETIRE_FIX(RmSmallIncreasedQOffers) XRPL_RETIRE_FIX(STAmountCanonicalize) XRPL_RETIRE_FIX(TakerDryOfferRemoval) XRPL_RETIRE_FIX(TrustLinesToSelf) +XRPL_RETIRE_FIX(UniversalNumber) XRPL_RETIRE_FEATURE(Checks) XRPL_RETIRE_FEATURE(CheckCashMakesTrustLine) diff --git a/src/libxrpl/protocol/AMMCore.cpp b/src/libxrpl/protocol/AMMCore.cpp index eccb581c6d..e58ab29257 100644 --- a/src/libxrpl/protocol/AMMCore.cpp +++ b/src/libxrpl/protocol/AMMCore.cpp @@ -127,7 +127,7 @@ ammAuctionTimeSlot(std::uint64_t current, STObject const& auctionSlot) bool ammEnabled(Rules const& rules) { - return rules.enabled(featureAMM) && rules.enabled(fixUniversalNumber); + return rules.enabled(featureAMM); } } // namespace xrpl diff --git a/src/libxrpl/protocol/IOUAmount.cpp b/src/libxrpl/protocol/IOUAmount.cpp index d214995809..acbf6724e1 100644 --- a/src/libxrpl/protocol/IOUAmount.cpp +++ b/src/libxrpl/protocol/IOUAmount.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include @@ -17,29 +16,6 @@ namespace xrpl { -namespace { - -// Use a static inside a function to help prevent order-of-initialization issues -LocalValue& -getStaticSTNumberSwitchover() -{ - static LocalValue kR{true}; - return kR; -} -} // namespace - -bool -getSTNumberSwitchover() -{ - return *getStaticSTNumberSwitchover(); -} - -void -setSTNumberSwitchover(bool v) -{ - *getStaticSTNumberSwitchover() = v; -} - /* The range for the mantissa when normalized */ // log(2^63,10) ~ 18.96 // @@ -75,56 +51,20 @@ IOUAmount::normalize() return; } - if (getSTNumberSwitchover()) - { - Number const v{mantissa_, exponent_}; - *this = fromNumber(v); - if (exponent_ > kMaxExponent) - Throw("value overflow"); - if (exponent_ < kMinExponent) - *this = beast::kZero; - return; - } - - bool const negative = (mantissa_ < 0); - - if (negative) - mantissa_ = -mantissa_; - - while ((mantissa_ < kMinMantissa) && (exponent_ > kMinExponent)) - { - mantissa_ *= 10; - --exponent_; - } - - while (mantissa_ > kMaxMantissa) - { - if (exponent_ >= kMaxExponent) - Throw("IOUAmount::normalize"); - - mantissa_ /= 10; - ++exponent_; - } - - if ((exponent_ < kMinExponent) || (mantissa_ < kMinMantissa)) - { - *this = beast::kZero; - return; - } - - if (exponent_ > kMaxExponent) - Throw("value overflow"); - - if (negative) - mantissa_ = -mantissa_; + Number const v{mantissa_, exponent_}; + *this = IOUAmount(v); } IOUAmount::IOUAmount(Number const& other) : IOUAmount(fromNumber(other)) { if (exponent_ > kMaxExponent) + { Throw("value overflow"); + } if (exponent_ < kMinExponent) + { *this = beast::kZero; + } } IOUAmount& @@ -139,37 +79,7 @@ IOUAmount::operator+=(IOUAmount const& other) return *this; } - if (getSTNumberSwitchover()) - { - *this = IOUAmount{Number{*this} + Number{other}}; - return *this; - } - auto m = other.mantissa_; - auto e = other.exponent_; - - while (exponent_ < e) - { - mantissa_ /= 10; - ++exponent_; - } - - while (e < exponent_) - { - m /= 10; - ++e; - } - - // This addition cannot overflow an std::int64_t but we may throw from - // normalize if the result isn't representable. - mantissa_ += m; - - if (mantissa_ >= -10 && mantissa_ <= 10) - { - *this = beast::kZero; - return *this; - } - - normalize(); + *this = IOUAmount{Number{*this} + Number{other}}; return *this; } diff --git a/src/libxrpl/protocol/Rules.cpp b/src/libxrpl/protocol/Rules.cpp index 08a95145eb..e0968ea868 100644 --- a/src/libxrpl/protocol/Rules.cpp +++ b/src/libxrpl/protocol/Rules.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -83,15 +82,12 @@ useRulesGuards(Rules const& rules) void createGuards( Rules const& rules, - std::optional& stNumberSO, std::optional& rulesGuard, std::optional& mantissaScaleGuard) { if (useRulesGuards(rules)) { // raii classes for the current ledger rules. - // fixUniversalNumber predates the rulesGuard and should be replaced. - stNumberSO.emplace(rules.enabled(fixUniversalNumber)); rulesGuard.emplace(rules); } else diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index 1ba9cd042f..449efc9543 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -388,47 +388,9 @@ operator+(STAmount const& v1, STAmount const& v2) if (v1.holds()) return {v1.asset_, v1.mpt().value() + v2.mpt().value()}; - if (getSTNumberSwitchover()) - { - auto x = v1; - x = v1.iou() + v2.iou(); - return x; - } - - int ov1 = v1.exponent(), ov2 = v2.exponent(); - std::int64_t vv1 = static_cast(v1.mantissa()); - std::int64_t vv2 = static_cast(v2.mantissa()); - - if (v1.negative()) - vv1 = -vv1; - - if (v2.negative()) - vv2 = -vv2; - - while (ov1 < ov2) - { - vv1 /= 10; - ++ov1; - } - - while (ov2 < ov1) - { - vv2 /= 10; - ++ov2; - } - - // This addition cannot overflow an std::int64_t. It can overflow an - // STAmount and the constructor will throw. - - std::int64_t const fv = vv1 + vv2; - - if ((fv >= -10) && (fv <= 10)) - return {v1.getFName(), v1.asset()}; - - if (fv >= 0) - return STAmount{v1.getFName(), v1.asset(), static_cast(fv), ov1, false}; - - return STAmount{v1.getFName(), v1.asset(), static_cast(-fv), ov1, true}; + auto x = v1; + x = v1.iou() + v2.iou(); + return x; } STAmount @@ -877,53 +839,25 @@ STAmount::canonicalize() if (asset_.holds() && offset_ > 18) Throw("MPT amount out of range"); - if (getSTNumberSwitchover()) + Number const num(isNegative_, value_, offset_, Number::Unchecked{}); + auto set = [&](auto const& val) { + auto const value = val.value(); + isNegative_ = value < 0; + value_ = isNegative_ ? -value : value; + }; + if (native()) { - Number const num(isNegative_, value_, offset_, Number::Unchecked{}); - auto set = [&](auto const& val) { - auto const value = val.value(); - isNegative_ = value < 0; - value_ = isNegative_ ? -value : value; - }; - if (native()) - { - set(XRPAmount{num}); - } - else if (asset_.holds()) - { - set(MPTAmount{num}); - } - else - { - Throw("Unknown integral asset type"); - } - offset_ = 0; + set(XRPAmount{num}); + } + else if (asset_.holds()) + { + set(MPTAmount{num}); } else { - while (offset_ < 0) - { - value_ /= 10; - ++offset_; - } - - while (offset_ > 0) - { - // N.B. do not move the overflow check to after the - // multiplication - if (native() && value_ > kMaxNativeN) - { - Throw("Native currency amount out of range"); - } - else if (!native() && value_ > kMaxMpTokenAmount) - { - Throw("MPT amount out of range"); - } - - value_ *= 10; - --offset_; - } + Throw("Unknown integral asset type"); // LCOV_EXCL_LINE } + offset_ = 0; if (native() && value_ > kMaxNativeN) { @@ -937,53 +871,7 @@ STAmount::canonicalize() return; } - if (getSTNumberSwitchover()) - { - *this = iou(); - return; - } - - if (value_ == 0) - { - offset_ = -100; - isNegative_ = false; - return; - } - - while ((value_ < kMinValue) && (offset_ > kMinOffset)) - { - value_ *= 10; - --offset_; - } - - while (value_ > kMaxValue) - { - if (offset_ >= kMaxOffset) - Throw("value overflow"); - - value_ /= 10; - ++offset_; - } - - if ((offset_ < kMinOffset) || (value_ < kMinValue)) - { - value_ = 0; - isNegative_ = false; - offset_ = -100; - return; - } - - if (offset_ > kMaxOffset) - Throw("value overflow"); - - XRPL_ASSERT( - (value_ == 0) || ((value_ >= kMinValue) && (value_ <= kMaxValue)), - "xrpl::STAmount::canonicalize : value inside range"); - XRPL_ASSERT( - (value_ == 0) || ((offset_ >= kMinOffset) && (offset_ <= kMaxOffset)), - "xrpl::STAmount::canonicalize : offset inside range"); - XRPL_ASSERT( - (value_ != 0) || (offset_ != -100), "xrpl::STAmount::canonicalize : value or offset set"); + *this = iou(); } void @@ -1395,44 +1283,8 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset) return STAmount(asset, minV * maxV); } - if (getSTNumberSwitchover()) - { - auto const r = Number{v1} * Number{v2}; - return STAmount{asset, r}; - } - - std::uint64_t value1 = v1.mantissa(); - std::uint64_t value2 = v2.mantissa(); - int offset1 = v1.exponent(); - int offset2 = v2.exponent(); - - if (v1.integral()) - { - while (value1 < STAmount::kMinValue) - { - value1 *= 10; - --offset1; - } - } - - if (v2.integral()) - { - while (value2 < STAmount::kMinValue) - { - value2 *= 10; - --offset2; - } - } - - // We multiply the two mantissas (each is between 10^15 - // and 10^16), so their product is in the 10^30 to 10^32 - // range. Dividing their product by 10^14 maintains the - // precision, by scaling the result to 10^16 to 10^18. - return STAmount( - asset, - muldiv(value1, value2, kTenTO14) + 7, - offset1 + offset2 + 14, - v1.negative() != v2.negative()); + auto const r = Number{v1} * Number{v2}; + return STAmount{asset, r}; } // This is the legacy version of canonicalizeRound. It's been in use diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index 51541cc2e3..68d3f36916 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -1199,8 +1198,6 @@ Transactor::operator()() // with_txn_type(). // // raii classes for the current ledger rules. - // fixUniversalNumber predate the rulesGuard and should be replaced. - NumberSO const stNumberSO{view().rules().enabled(fixUniversalNumber)}; CurrentTransactionRulesGuard const currentTransactionRulesGuard(view().rules()); #ifdef DEBUG diff --git a/src/libxrpl/tx/apply.cpp b/src/libxrpl/tx/apply.cpp index 0fc0275eb0..b70cb0d345 100644 --- a/src/libxrpl/tx/apply.cpp +++ b/src/libxrpl/tx/apply.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -133,7 +132,6 @@ template ApplyResult apply(ServiceRegistry& registry, OpenView& view, PreflightChecks&& preflightChecks) { - NumberSO const stNumberSO{view.rules().enabled(fixUniversalNumber)}; return doApply(preclaim(preflightChecks(), registry, view), registry, view); } diff --git a/src/libxrpl/tx/applySteps.cpp b/src/libxrpl/tx/applySteps.cpp index 217fdd717f..336bb2004b 100644 --- a/src/libxrpl/tx/applySteps.cpp +++ b/src/libxrpl/tx/applySteps.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -70,10 +69,9 @@ withTxnType(Rules const& rules, TxType txnType, F&& f) // // See also Transactor::operator(). // - std::optional stNumberSO; std::optional rulesGuard; std::optional mantissaScaleGuard; - createGuards(rules, stNumberSO, rulesGuard, mantissaScaleGuard); + createGuards(rules, rulesGuard, mantissaScaleGuard); switch (txnType) { diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index 64972c24ab..e3a1cc935f 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -4333,15 +4333,10 @@ private: testAmendment() { testcase("Amendment"); - FeatureBitset const all{testableAmendments()}; - FeatureBitset const noAMM{all - featureAMM}; - FeatureBitset const noNumber{all - fixUniversalNumber}; - FeatureBitset const noAMMAndNumber{all - featureAMM - fixUniversalNumber}; using namespace jtx; + Env env{*this, testableAmendments() - featureAMM}; - for (auto const& feature : {noAMM, noNumber, noAMMAndNumber}) { - Env env{*this, feature}; fund(env, gw_, {alice_}, {USD(1'000)}, Fund::All); AMM amm(env, alice_, XRP(1'000), USD(1'000), Ter(temDISABLED)); diff --git a/src/test/app/NFToken_test.cpp b/src/test/app/NFToken_test.cpp index 269bc72c53..ba8f09c449 100644 --- a/src/test/app/NFToken_test.cpp +++ b/src/test/app/NFToken_test.cpp @@ -2236,17 +2236,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite // See the impact of rounding when the nft is sold for small amounts // of drops. - for (auto numberSwitchOver : {true}) { - if (numberSwitchOver) - { - env.enableFeature(fixUniversalNumber); - } - else - { - env.disableFeature(fixUniversalNumber); - } - // An nft with a transfer fee of 1 basis point. uint256 const nftID = token::getNextID(env, alice, 0u, tfTransferable, 1); env(token::mint(alice), Txflags(tfTransferable), token::XferFee(1)); @@ -2268,7 +2258,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite // minter sells to carol. The payment is just small enough that // alice does not get any transfer fee. - auto pmt = numberSwitchOver ? drops(50000) : drops(99999); + auto pmt = drops(50000); STAmount carolBalance = env.balance(carol); uint256 const minterSellOfferIndex = keylet::nftoffer(minter, env.seq(minter)).key; env(token::createOffer(minter, nftID, pmt), Txflags(tfSellNFToken)); @@ -2285,7 +2275,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite // transfer that enables a transfer fee of 1 basis point. STAmount beckyBalance = env.balance(becky); uint256 const beckyBuyOfferIndex = keylet::nftoffer(becky, env.seq(becky)).key; - pmt = numberSwitchOver ? drops(50001) : drops(100000); + pmt = drops(50001); env(token::createOffer(becky, nftID, pmt), token::Owner(carol)); env.close(); env(token::acceptBuyOffer(carol, beckyBuyOfferIndex)); diff --git a/src/test/app/OfferMPT_test.cpp b/src/test/app/OfferMPT_test.cpp index e0f2f4eab0..ed0b2ffbe3 100644 --- a/src/test/app/OfferMPT_test.cpp +++ b/src/test/app/OfferMPT_test.cpp @@ -1827,7 +1827,6 @@ public: using namespace jtx; Env env{*this, features}; - env.enableFeature(fixUniversalNumber); auto const gw = Account{"gateway"}; auto const alice = Account{"alice"}; diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index 83c58884e0..7382f4f090 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -1973,54 +1973,36 @@ public: using namespace jtx; - for (auto numberSwitchOver : {false, true}) - { - Env env{*this, features}; - if (numberSwitchOver) - { - env.enableFeature(fixUniversalNumber); - } - else - { - env.disableFeature(fixUniversalNumber); - } + Env env{*this, features}; - auto const gw = Account{"gateway"}; - auto const alice = Account{"alice"}; - auto const bob = Account{"bob"}; - auto const usd = gw["USD"]; + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const usd = gw["USD"]; - env.fund(XRP(10000), gw, alice, bob); - env.close(); + env.fund(XRP(10000), gw, alice, bob); + env.close(); - env(rate(gw, 1.005)); + env(rate(gw, 1.005)); - env(trust(alice, usd(1000))); - env(trust(bob, usd(1000))); - env(trust(gw, alice["USD"](50))); + env(trust(alice, usd(1000))); + env(trust(bob, usd(1000))); + env(trust(gw, alice["USD"](50))); - env(pay(gw, bob, bob["USD"](1))); - env(pay(alice, gw, usd(50))); + env(pay(gw, bob, bob["USD"](1))); + env(pay(alice, gw, usd(50))); - env(trust(gw, alice["USD"](0))); + env(trust(gw, alice["USD"](0))); - env(offer(alice, usd(50), XRP(150000))); - env(offer(bob, XRP(100), usd(0.1))); + env(offer(alice, usd(50), XRP(150000))); + env(offer(bob, XRP(100), usd(0.1))); - auto jrr = ledgerEntryState(env, alice, gw, "USD"); - BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "49.96666666666667"); + auto jrr = ledgerEntryState(env, alice, gw, "USD"); + BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "49.96666666666667"); - jrr = ledgerEntryState(env, bob, gw, "USD"); - json::Value const bobUSD = jrr[jss::node][sfBalance.fieldName][jss::value]; - if (!numberSwitchOver) - { - BEAST_EXPECT(bobUSD == "-0.966500000033334"); - } - else - { - BEAST_EXPECT(bobUSD == "-0.9665000000333333"); - } - } + jrr = ledgerEntryState(env, bob, gw, "USD"); + json::Value const bobUSD = jrr[jss::node][sfBalance.fieldName][jss::value]; + BEAST_EXPECT(bobUSD == "-0.9665000000333333"); } void diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index f4bd1c9d66..2d2745de14 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -1514,7 +1514,6 @@ public: void testToStAmount() { - NumberSO const stNumberSO{true}; Issue const issue; Number const n{7'518'783'80596, -5}; SaveNumberRoundMode const save{Number::setround(Number::RoundingMode::ToNearest)}; diff --git a/src/xrpld/app/misc/detail/TxQ.cpp b/src/xrpld/app/misc/detail/TxQ.cpp index f98580ede4..0326828a70 100644 --- a/src/xrpld/app/misc/detail/TxQ.cpp +++ b/src/xrpld/app/misc/detail/TxQ.cpp @@ -16,8 +16,6 @@ #include #include #include -#include -#include #include #include #include @@ -306,7 +304,6 @@ TxQ::MaybeTx::apply(Application& app, OpenView& view, beast::Journal j) { // If the rules or flags change, preflight again XRPL_ASSERT(pfResult, "xrpl::TxQ::MaybeTx::apply : preflight result is set"); - NumberSO const stNumberSO{view.rules().enabled(fixUniversalNumber)}; // NOLINTBEGIN(bugprone-unchecked-optional-access) assert above if (pfResult->rules != view.rules() || pfResult->flags != flags) @@ -731,8 +728,6 @@ TxQ::apply( ApplyFlags flags, beast::Journal j) { - NumberSO const stNumberSO{view.rules().enabled(fixUniversalNumber)}; - // See if the transaction is valid, properly formed, // etc. before doing potentially expensive queue // replace and multi-transaction operations. From 97ca7d57bcedab341e7887cd7950299d2bde9598 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Wed, 10 Jun 2026 13:44:57 +0200 Subject: [PATCH 080/135] perf: Dispatch "hasInvalidAmount()" on type tag instead of dynamic_cast (#7402) --- src/libxrpl/protocol/STAmount.cpp | 32 ++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index 449efc9543..748d00f25a 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -1138,16 +1138,34 @@ hasInvalidAmount(STBase const& field, int depth, beast::Journal j) return true; } - if (auto const amount = dynamic_cast(&field)) - return !isLegalMPT(*amount) || !isLegalNet(*amount); + // Dispatch on the serialized type tag rather than RTTI: this is on the invariant-checking path + // and a dynamic_cast chain over every field of every modified entry is measurably expensive. + // The object-like tags below all denote STObject subclasses (STLedgerEntry, STTx), so the + // downcast is sound; nested fields are only ever plain STI_OBJECT / STI_ARRAY containers. + // safeDowncast keeps a dynamic_cast validity assert in debug builds while compiling to + // static_cast in release. + switch (field.getSType()) + { + case STI_AMOUNT: { + auto const& amount = safeDowncast(field); + return !isLegalMPT(amount) || !isLegalNet(amount); + } - if (auto const object = dynamic_cast(&field)) - return hasInvalidAmount(*object, depth + 1, j); + case STI_OBJECT: + case STI_LEDGERENTRY: + case STI_TRANSACTION: + return hasInvalidAmount(safeDowncast(field), depth + 1, j); - if (auto const array = dynamic_cast(&field)) - return hasInvalidAmount(*array, depth + 1, j); + case STI_ARRAY: + return hasInvalidAmount(safeDowncast(field), depth + 1, j); - return false; + default: { + XRPL_ASSERT( + dynamic_cast(&field) == nullptr, + "xrpl::hasInvalidAmount : unhandled STObject type"); + return false; + } + } } bool From 83cc5df72e9fa2f4d9d91a674908afcd6a50302a Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:05:53 +0200 Subject: [PATCH 081/135] fix: Disable transaction invariants (#7409) --- src/libxrpl/tx/Transactor.cpp | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index 68d3f36916..aa7b81c015 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -1171,21 +1171,17 @@ Transactor::checkTransactionInvariants(TER result, XRPAmount fee) [[nodiscard]] TER Transactor::checkInvariants(TER result, XRPAmount fee) { - // Transaction invariants first (more specific). These check post-conditions of the specific - // transaction. If these fail, the transaction's core logic is wrong. - auto const txResult = checkTransactionInvariants(result, fee); - - // Protocol invariants second (broader). These check properties that must hold regardless of - // transaction type. - auto const protoResult = ctx_.checkInvariants(result, fee); - - // Fail if either check failed. tef (fatal) takes priority over tec. - if (protoResult == tefINVARIANT_FAILED) - return tefINVARIANT_FAILED; - if (txResult == tecINVARIANT_FAILED || protoResult == tecINVARIANT_FAILED) - return tecINVARIANT_FAILED; - - return result; + /* + * DISABLED for 3.2.0 — Must be re-introduced for 3.3.0 + * + * Transaction invariants are disabled due to a performance regression: + * the two-pass design (transaction-specific invariants + protocol invariants) + * iterates over modified ledger entries twice per transaction. + * + * Until resolved, only protocol invariants are checked (delegated to ctx_). + * This is safe because all transaction invariants in 3.2.0 are no-ops. + */ + return ctx_.checkInvariants(result, fee); } //------------------------------------------------------------------------------ ApplyResult From dd0b6754d4c33d906d54f317dcc9f4da800488ec Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 10 Jun 2026 15:45:51 +0100 Subject: [PATCH 082/135] ci: Add `gh` and `file` to nix packages (#7444) --- cspell.config.yaml | 1 + flake.lock | 6 +++--- nix/docker/check-tools.sh | 2 ++ nix/packages.nix | 2 ++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cspell.config.yaml b/cspell.config.yaml index cab2fc3da6..926ac06596 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -84,6 +84,7 @@ words: - coro - coros - cowid + - cpack - cryptocondition - cryptoconditional - cryptoconditions diff --git a/flake.lock b/flake.lock index 2013cfabd4..f8553af703 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1780243769, - "narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=", + "lastModified": 1780749050, + "narHash": "sha256-3av0pIjlOWQ6rDbNOmpUSvbNnJkGORQKKjb4LtCZsIY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "331800de5053fcebacf6813adb5db9c9dca22a0c", + "rev": "a799d3e3886da994fa307f817a6bc705ae538eeb", "type": "github" }, "original": { diff --git a/nix/docker/check-tools.sh b/nix/docker/check-tools.sh index 67bcdff8a9..276e5977ff 100755 --- a/nix/docker/check-tools.sh +++ b/nix/docker/check-tools.sh @@ -10,10 +10,12 @@ cmake --version conan --version curl --version doxygen --version +file --version g++ --version gcc --version gcov --version gcovr --version +gh --version git --version git-cliff --version gpg --version diff --git a/nix/packages.nix b/nix/packages.nix index d40472634b..fc4eff679e 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -13,7 +13,9 @@ in conan curlMinimal # needed for codecov/codecov-action doxygen + file # needed for cpack in Clio gcovr + gh git git-cliff gnumake From 1f359f719cbf475e6c010ad0fcf5b4044c6768d1 Mon Sep 17 00:00:00 2001 From: Shi Cheng <97218929+shichengripple001@users.noreply.github.com> Date: Thu, 11 Jun 2026 01:24:44 +0800 Subject: [PATCH 083/135] fix: Add [[maybe_unused]] to fix320Enabled for assert=OFF builds (#7446) Co-authored-by: Claude Sonnet 4.6 --- src/libxrpl/ledger/helpers/LendingHelpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libxrpl/ledger/helpers/LendingHelpers.cpp b/src/libxrpl/ledger/helpers/LendingHelpers.cpp index 676b473132..f7ec8a8bc3 100644 --- a/src/libxrpl/ledger/helpers/LendingHelpers.cpp +++ b/src/libxrpl/ledger/helpers/LendingHelpers.cpp @@ -813,7 +813,7 @@ doOverpayment( // 3. The overpayment's penalty interest part (= untrackedInterest // for the overpayment path; see computeOverpaymentComponents): // trackedInterestPart() - bool const fix320Enabled = rules.enabled(fixCleanup3_2_0); + [[maybe_unused]] bool const fix320Enabled = rules.enabled(fixCleanup3_2_0); XRPL_ASSERT_IF( fix320Enabled, overpaymentComponents.trackedPrincipalDelta == From 8000adfa797f3d5c81284cb3bcc8c6e9213549ec Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 10 Jun 2026 19:08:34 +0100 Subject: [PATCH 084/135] ci: Make configurations launch on certain event types (#7447) --- .github/scripts/strategy-matrix/generate.py | 57 ++++++++++++++++--- .github/scripts/strategy-matrix/linux.json | 3 +- .github/scripts/strategy-matrix/macos.json | 3 +- .github/scripts/strategy-matrix/windows.json | 6 +- .../workflows/reusable-strategy-matrix.yml | 3 +- 5 files changed, 61 insertions(+), 11 deletions(-) diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index aaf84a51d0..6353567f27 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -27,6 +27,19 @@ def get_cmake_args(build_type: str, extra_args: str) -> str: return " ".join(args) +def runs_on_event(exclude_event_types: list[str], event: str | None) -> bool: + """Whether a config should run for the current event. + + 'exclude_event_types' is a list of GitHub event names (e.g. + ["pull_request"]) on which the config should NOT run; an empty list means + the config runs on every event. When no event is given (event is None), no + filtering is applied. + """ + if event is None: + return True + return event not in exclude_event_types + + # --------------------------------------------------------------------------- # Input types — shapes of the JSON config files # --------------------------------------------------------------------------- @@ -43,6 +56,9 @@ class LinuxConfig: suffix: str = "" extra_cmake_args: str = "" image: str = "" # only used by package_configs entries + # List of GitHub event names (e.g. "pull_request") on which this config + # should NOT run. Empty means it runs on every event. + exclude_event_types: list[str] = dataclasses.field(default_factory=list) @dataclasses.dataclass @@ -77,6 +93,9 @@ class PlatformConfig: build_type: list[str] build_only: bool = False # if true, skip tests (e.g. macos/Windows Debug) extra_cmake_args: str = "" + # List of GitHub event names (e.g. "pull_request") on which this config + # should NOT run. Empty means it runs on every event. + exclude_event_types: list[str] = dataclasses.field(default_factory=list) def __post_init__(self) -> None: if isinstance(self.build_type, str): @@ -151,16 +170,21 @@ _ARCHS: dict[str, Architecture] = { } -def expand_linux_matrix(linux: LinuxFile) -> list[MatrixEntry]: +def expand_linux_matrix( + linux: LinuxFile, event: str | None = None +) -> list[MatrixEntry]: """Expand a LinuxFile into a flat list of matrix entries. Each config entry is expanded over the cross-product of its - compiler, build_type, sanitizers, and architecture lists. + compiler, build_type, sanitizers, and architecture lists. Configs that + exclude the current event are skipped. """ entries: list[MatrixEntry] = [] for distro, configs in linux.configs.items(): for cfg in configs: + if not runs_on_event(cfg.exclude_event_types, event): + continue # An empty sanitizers list means "one entry with no sanitizer". effective_sanitizers = cfg.sanitizers or [""] effective_archs = {arch: _ARCHS[arch] for arch in cfg.arch} @@ -218,13 +242,20 @@ def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]: return entries -def expand_platform_matrix(pf: PlatformFile) -> list[MatrixEntry]: - """Expand a PlatformFile (macOS or Windows) into matrix entries.""" +def expand_platform_matrix( + pf: PlatformFile, event: str | None = None +) -> list[MatrixEntry]: + """Expand a PlatformFile (macOS or Windows) into matrix entries. + + Configs that exclude the current event are skipped. + """ platform_name, arch = pf.platform.split("/") is_windows = platform_name == "windows" entries: list[MatrixEntry] = [] for cfg in pf.configs: + if not runs_on_event(cfg.exclude_event_types, event): + continue for build_type in cfg.build_type: entries.append( MatrixEntry( @@ -262,6 +293,14 @@ if __name__ == "__main__": help="Emit the Linux packaging matrix instead of the build/test matrix.", action="store_true", ) + parser.add_argument( + "-e", + "--event", + help="The GitHub event name that triggered the workflow (e.g. 'push', " + "'pull_request'). Configs are filtered by their 'event_type'. If " + "omitted, no filtering is applied.", + default=None, + ) args = parser.parse_args() matrix: list[MatrixEntry] | list[PackagingEntry] = [] @@ -270,12 +309,16 @@ if __name__ == "__main__": matrix = expand_linux_packaging(LinuxFile.load(THIS_DIR / "linux.json")) else: if args.config in ("linux", None): - matrix += expand_linux_matrix(LinuxFile.load(THIS_DIR / "linux.json")) + matrix += expand_linux_matrix( + LinuxFile.load(THIS_DIR / "linux.json"), args.event + ) if args.config in ("macos", None): - matrix += expand_platform_matrix(PlatformFile.load(THIS_DIR / "macos.json")) + matrix += expand_platform_matrix( + PlatformFile.load(THIS_DIR / "macos.json"), args.event + ) if args.config in ("windows", None): matrix += expand_platform_matrix( - PlatformFile.load(THIS_DIR / "windows.json") + PlatformFile.load(THIS_DIR / "windows.json"), args.event ) print(f"matrix={json.dumps({'include': [dataclasses.asdict(e) for e in matrix]})}") diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index edacdbde4c..e6c807ac95 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -41,7 +41,8 @@ "build_type": ["Debug"], "arch": ["amd64"], "suffix": "unity", - "extra_cmake_args": "-Dunity=ON" + "extra_cmake_args": "-Dunity=ON", + "exclude_event_types": ["pull_request"] } ], diff --git a/.github/scripts/strategy-matrix/macos.json b/.github/scripts/strategy-matrix/macos.json index 5b9e32f88e..66d7a55a43 100644 --- a/.github/scripts/strategy-matrix/macos.json +++ b/.github/scripts/strategy-matrix/macos.json @@ -9,7 +9,8 @@ { "build_type": "Debug", "extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5", - "build_only": true + "build_only": true, + "exclude_event_types": ["pull_request"] } ] } diff --git a/.github/scripts/strategy-matrix/windows.json b/.github/scripts/strategy-matrix/windows.json index e4678b60db..e25f9ad131 100644 --- a/.github/scripts/strategy-matrix/windows.json +++ b/.github/scripts/strategy-matrix/windows.json @@ -3,6 +3,10 @@ "runner": ["self-hosted", "Windows", "devbox"], "configs": [ { "build_type": "Release" }, - { "build_type": "Debug", "build_only": true } + { + "build_type": "Debug", + "build_only": true, + "exclude_event_types": ["pull_request"] + } ] } diff --git a/.github/workflows/reusable-strategy-matrix.yml b/.github/workflows/reusable-strategy-matrix.yml index ea134b43b2..4518a8ffef 100644 --- a/.github/workflows/reusable-strategy-matrix.yml +++ b/.github/workflows/reusable-strategy-matrix.yml @@ -35,4 +35,5 @@ jobs: id: generate env: GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}', inputs.os) || '' }} - run: ./generate.py ${GENERATE_CONFIG} >>"${GITHUB_OUTPUT}" + GENERATE_EVENT: ${{ github.event_name }} + run: ./generate.py ${GENERATE_CONFIG} --event="${GENERATE_EVENT}" >>"${GITHUB_OUTPUT}" From 2f6b466feb716198bf62dacd55b23332dd09e16c Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 10 Jun 2026 19:24:34 +0100 Subject: [PATCH 085/135] ci: Make sanitizer flags lists in the profile, not a string (#7449) --- conan/profiles/sanitizers | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/conan/profiles/sanitizers b/conan/profiles/sanitizers index 4a05fda734..083807ea9e 100644 --- a/conan/profiles/sanitizers +++ b/conan/profiles/sanitizers @@ -52,52 +52,50 @@ include(default) {% endif %} {# Frame pointer required for meaningful stack traces; -O1 for reasonable performance #} -{% set compile_flags = ["-fno-omit-frame-pointer", "-O1"] %} +{% set sanitizer_compiler_flags = ["-fno-omit-frame-pointer", "-O1"] %} {% if compiler == "gcc" %} {# Suppress false positive warnings with GCC #} - {% set _ = compile_flags.append("-Wno-stringop-overflow") %} + {% set _ = sanitizer_compiler_flags.append("-Wno-stringop-overflow") %} {% set relocation_flags = [] %} {% if arch == "x86_64" and enable_asan %} {# Large code model prevents relocation errors in instrumented ASAN binaries #} - {% set _ = compile_flags.append("-mcmodel=large") %} + {% set _ = sanitizer_compiler_flags.append("-mcmodel=large") %} {% set _ = relocation_flags.append("-mcmodel=large") %} {% elif enable_tsan %} {# GCC doesn't support atomic_thread_fence with TSAN; suppress warnings #} - {% set _ = compile_flags.append("-Wno-tsan") %} + {% set _ = sanitizer_compiler_flags.append("-Wno-tsan") %} {% if arch == "x86_64" %} {# Medium code model for TSAN; large is incompatible #} - {% set _ = compile_flags.append("-mcmodel=medium") %} + {% set _ = sanitizer_compiler_flags.append("-mcmodel=medium") %} {% set _ = relocation_flags.append("-mcmodel=medium") %} {% endif %} {% endif %} {% set fsanitize = "-fsanitize=" ~ ",".join(sanitizer_types) %} - {% set _ = compile_flags.append(fsanitize) %} + {% set _ = sanitizer_compiler_flags.append(fsanitize) %} {% set _ = relocation_flags.append(fsanitize) %} - {% set sanitizer_compiler_flags = " ".join(compile_flags) %} - {% set sanitizer_linker_flags = " ".join(relocation_flags) %} + {% set sanitizer_linker_flags = relocation_flags %} {% elif compiler == "clang" or compiler == "apple-clang" %} {% set fsanitize = "-fsanitize=" ~ ",".join(sanitizer_types) %} - {% set _ = compile_flags.append(fsanitize) %} + {% set _ = sanitizer_compiler_flags.append(fsanitize) %} - {% set sanitizer_compiler_flags = " ".join(compile_flags) %} - {% set sanitizer_linker_flags = fsanitize %} + {% set sanitizer_linker_flags = [fsanitize] %} {% endif %} [conf] tools.build:defines+={{defines}} -tools.build:cxxflags+=['{{sanitizer_compiler_flags}}'] -tools.build:sharedlinkflags+=['{{sanitizer_linker_flags}}'] -tools.build:exelinkflags+=['{{sanitizer_linker_flags}}'] +tools.build:cxxflags+={{sanitizer_compiler_flags}} +tools.build:sharedlinkflags+={{sanitizer_linker_flags}} +tools.build:exelinkflags+={{sanitizer_linker_flags}} tools.info.package_id:confs+=["tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags", "tools.build:defines"] # &: means "apply only to the consumer/root package" -&:tools.cmake.cmaketoolchain:extra_variables={"SANITIZERS": "{{sanitizers}}", "SANITIZERS_COMPILER_FLAGS": "{{sanitizer_compiler_flags}}", "SANITIZERS_LINKER_FLAGS": "{{sanitizer_linker_flags}}"} +&:tools.cmake.cmaketoolchain:extra_variables={"SANITIZERS": "{{sanitizers}}", "SANITIZERS_COMPILER_FLAGS": "{{sanitizer_compiler_flags | join(' ')}}", "SANITIZERS_LINKER_FLAGS": "{{sanitizer_linker_flags | join(' ')}}"} [options] {% if enable_asan %} From 09c36d066ec6e1ffdcc6b98075c991e202873d5f Mon Sep 17 00:00:00 2001 From: Zhiyuan Wang <96991820+Kassaking7@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:42:41 -0400 Subject: [PATCH 086/135] fix: Correct hybrid offer deletion on credential expiry (#6843) Co-authored-by: Bart --- include/xrpl/protocol/detail/features.macro | 1 + src/libxrpl/tx/paths/OfferStream.cpp | 9 +- src/test/app/PermissionedDEX_test.cpp | 801 ++++++++++++-------- 3 files changed, 475 insertions(+), 336 deletions(-) diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 2b2f24ba53..d3500ab144 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -15,6 +15,7 @@ // Add new amendments to the top of this list. // Keep it sorted in reverse chronological order. +XRPL_FIX (Cleanup3_3_0, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FIX (Cleanup3_2_0, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo) XRPL_FIX (Cleanup3_1_3, Supported::Yes, VoteBehavior::DefaultYes) diff --git a/src/libxrpl/tx/paths/OfferStream.cpp b/src/libxrpl/tx/paths/OfferStream.cpp index b7defb4df8..ecc8416a2b 100644 --- a/src/libxrpl/tx/paths/OfferStream.cpp +++ b/src/libxrpl/tx/paths/OfferStream.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -249,7 +250,13 @@ TOfferStreamBase::step() continue; } - if (entry->isFieldPresent(sfDomainID) && + // Pre-fixCleanup3_3_0: validate domain membership for any book. + // Post-fixCleanup3_3_0: only validate when walking a domain book. + // Hybrid offers carry sfDomainID but also participate in the open + // book; expiry of the owner's domain credential should not evict + // the offer from the open book. + if ((!view_.rules().enabled(fixCleanup3_3_0) || book_.domain.has_value()) && + entry->isFieldPresent(sfDomainID) && !permissioned_dex::offerInDomain( view_, entry->key(), entry->getFieldH256(sfDomainID), j_)) { diff --git a/src/test/app/PermissionedDEX_test.cpp b/src/test/app/PermissionedDEX_test.cpp index d534f20248..99e69ce482 100644 --- a/src/test/app/PermissionedDEX_test.cpp +++ b/src/test/app/PermissionedDEX_test.cpp @@ -185,15 +185,15 @@ class PermissionedDEX_test : public beast::unit_test::Suite // test preflight { Env env(*this, features - featurePermissionedDEX); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - env(offer(bob_, XRP(10), USD(10)), Domain(domainID), Ter(temDISABLED)); + env(offer(bob, XRP(10), USD(10)), Domain(domainID), Ter(temDISABLED)); env.close(); env.enableFeature(featurePermissionedDEX); env.close(); - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); } @@ -214,7 +214,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite // preclaim - someone outside of the domain cannot create domain offer { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); // create devin account who is not part of the domain @@ -223,7 +223,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); env.trust(USD(1000), devin); env.close(); - env(pay(gw_, devin, USD(100))); + env(pay(gw, devin, USD(100))); env.close(); env(offer(devin, XRP(10), USD(10)), Domain(domainID), Ter(tecNO_PERMISSION)); @@ -247,7 +247,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite // preclaim - someone with expired cred cannot create domain offer { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); // create devin account who is not part of the domain @@ -256,7 +256,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); env.trust(USD(1000), devin); env.close(); - env(pay(gw_, devin, USD(100))); + env(pay(gw, devin, USD(100))); env.close(); auto jv = credentials::create(devin, domainOwner, credType); @@ -282,13 +282,13 @@ class PermissionedDEX_test : public beast::unit_test::Suite // preclaim - cannot create an offer in a non existent domain { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); uint256 const badDomain{ "F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E3370F3649CE134" "E5"}; - env(offer(bob_, XRP(10), USD(10)), Domain(badDomain), Ter(tecNO_PERMISSION)); + env(offer(bob, XRP(10), USD(10)), Domain(badDomain), Ter(tecNO_PERMISSION)); env.close(); } @@ -296,68 +296,68 @@ class PermissionedDEX_test : public beast::unit_test::Suite // domain { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - env(credentials::deleteCred(domainOwner, gw_, domainOwner, credType)); + env(credentials::deleteCred(domainOwner, gw, domainOwner, credType)); env.close(); - auto const bobOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + auto const bobOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, bobOfferSeq, XRP(10), USD(10), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, bobOfferSeq, XRP(10), USD(10), 0, true)); } // apply - offer can be created even if takerpays issuer is not in // domain { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - env(credentials::deleteCred(domainOwner, gw_, domainOwner, credType)); + env(credentials::deleteCred(domainOwner, gw, domainOwner, credType)); env.close(); - auto const bobOfferSeq{env.seq(bob_)}; - env(offer(bob_, USD(10), XRP(10)), Domain(domainID)); + auto const bobOfferSeq{env.seq(bob)}; + env(offer(bob, USD(10), XRP(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, bobOfferSeq, USD(10), XRP(10), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, bobOfferSeq, USD(10), XRP(10), 0, true)); } // apply - two domain offers cross with each other { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - auto const bobOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + auto const bobOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, bobOfferSeq, XRP(10), USD(10), 0, true)); - BEAST_EXPECT(ownerCount(env, bob_) == 3); + BEAST_EXPECT(checkOffer(env, bob, bobOfferSeq, XRP(10), USD(10), 0, true)); + BEAST_EXPECT(ownerCount(env, bob) == 3); // a non domain offer cannot cross with domain offer - env(offer(carol_, USD(10), XRP(10))); + env(offer(carol, USD(10), XRP(10))); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, bobOfferSeq, XRP(10), USD(10), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, bobOfferSeq, XRP(10), USD(10), 0, true)); - auto const aliceOfferSeq{env.seq(alice_)}; - env(offer(alice_, USD(10), XRP(10)), Domain(domainID)); + auto const aliceOfferSeq{env.seq(alice)}; + env(offer(alice, USD(10), XRP(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(!offerExists(env, alice_, aliceOfferSeq)); - BEAST_EXPECT(!offerExists(env, bob_, bobOfferSeq)); - BEAST_EXPECT(ownerCount(env, alice_) == 2); + BEAST_EXPECT(!offerExists(env, alice, aliceOfferSeq)); + BEAST_EXPECT(!offerExists(env, bob, bobOfferSeq)); + BEAST_EXPECT(ownerCount(env, alice) == 2); } // apply - create lots of domain offers { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); std::vector offerSeqs; @@ -365,19 +365,19 @@ class PermissionedDEX_test : public beast::unit_test::Suite for (size_t i = 0; i <= 100; i++) { - auto const bobOfferSeq{env.seq(bob_)}; + auto const bobOfferSeq{env.seq(bob)}; offerSeqs.emplace_back(bobOfferSeq); - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, bobOfferSeq, XRP(10), USD(10), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, bobOfferSeq, XRP(10), USD(10), 0, true)); } for (auto const offerSeq : offerSeqs) { - env(offerCancel(bob_, offerSeq)); + env(offerCancel(bob, offerSeq)); env.close(); - BEAST_EXPECT(!offerExists(env, bob_, offerSeq)); + BEAST_EXPECT(!offerExists(env, bob, offerSeq)); } } } @@ -390,10 +390,10 @@ class PermissionedDEX_test : public beast::unit_test::Suite // test preflight - without enabling featurePermissionedDEX amendment { Env env(*this, features - featurePermissionedDEX); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - env(pay(bob_, alice_, USD(10)), + env(pay(bob, alice, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID), @@ -403,10 +403,10 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.enableFeature(featurePermissionedDEX); env.close(); - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); - env(pay(bob_, alice_, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); + env(pay(bob, alice, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); env.close(); } @@ -431,13 +431,13 @@ class PermissionedDEX_test : public beast::unit_test::Suite // preclaim - cannot send payment with non existent domain { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); uint256 const badDomain{ "F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E3370F3649CE134" "E5"}; - env(pay(bob_, alice_, USD(10)), + env(pay(bob, alice, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(badDomain), @@ -448,10 +448,10 @@ class PermissionedDEX_test : public beast::unit_test::Suite // preclaim - payment with non-domain destination fails { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); // create devin account who is not part of the domain @@ -460,11 +460,11 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); env.trust(USD(1000), devin); env.close(); - env(pay(gw_, devin, USD(100))); + env(pay(gw, devin, USD(100))); env.close(); // devin is not part of domain - env(pay(alice_, devin, USD(10)), + env(pay(alice, devin, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID), @@ -476,7 +476,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); // devin has not yet accepted cred - env(pay(alice_, devin, USD(10)), + env(pay(alice, devin, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID), @@ -487,17 +487,17 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); // devin can now receive payment after he is in domain - env(pay(alice_, devin, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); + env(pay(alice, devin, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); env.close(); } // preclaim - non-domain sender cannot send payment { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); // create devin account who is not part of the domain @@ -506,11 +506,11 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); env.trust(USD(1000), devin); env.close(); - env(pay(gw_, devin, USD(100))); + env(pay(gw, devin, USD(100))); env.close(); // devin tries to send domain payment - env(pay(devin, alice_, USD(10)), + env(pay(devin, alice, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID), @@ -522,7 +522,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); // devin has not yet accepted cred - env(pay(devin, alice_, USD(10)), + env(pay(devin, alice, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID), @@ -533,28 +533,28 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); // devin can now send payment after he is in domain - env(pay(devin, alice_, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); + env(pay(devin, alice, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); env.close(); } // apply - domain owner can always send and receive domain payment { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); // domain owner can always be destination - env(pay(alice_, domainOwner, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); + env(pay(alice, domainOwner, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); env.close(); - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); // domain owner can send - env(pay(domainOwner, alice_, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); + env(pay(domainOwner, alice, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); env.close(); } } @@ -567,22 +567,22 @@ class PermissionedDEX_test : public beast::unit_test::Suite // test domain cross currency payment consuming one offer { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); // create a regular offer without domain - auto const regularOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10))); + auto const regularOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10))); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, regularOfferSeq, XRP(10), USD(10))); + BEAST_EXPECT(checkOffer(env, bob, regularOfferSeq, XRP(10), USD(10))); - auto const regularDirKey = getDefaultOfferDirKey(env, bob_, regularOfferSeq); + auto const regularDirKey = getDefaultOfferDirKey(env, bob, regularOfferSeq); BEAST_EXPECT(regularDirKey); BEAST_EXPECT(checkDirectorySize( env, *regularDirKey, 1)); // NOLINT(bugprone-unchecked-optional-access) // a domain payment cannot consume regular offers - env(pay(alice_, carol_, USD(10)), + env(pay(alice, carol, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID), @@ -590,23 +590,23 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); // create a domain offer - auto const domainOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + auto const domainOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, domainOfferSeq, XRP(10), USD(10), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, domainOfferSeq, XRP(10), USD(10), 0, true)); - auto const domainDirKey = getDefaultOfferDirKey(env, bob_, domainOfferSeq); + auto const domainDirKey = getDefaultOfferDirKey(env, bob, domainOfferSeq); BEAST_EXPECT(domainDirKey); BEAST_EXPECT(checkDirectorySize( env, *domainDirKey, 1)); // NOLINT(bugprone-unchecked-optional-access) // cross-currency permissioned payment consumed // domain offer instead of regular offer - env(pay(alice_, carol_, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); + env(pay(alice, carol, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(!offerExists(env, bob_, domainOfferSeq)); - BEAST_EXPECT(checkOffer(env, bob_, regularOfferSeq, XRP(10), USD(10))); + BEAST_EXPECT(!offerExists(env, bob, domainOfferSeq)); + BEAST_EXPECT(checkOffer(env, bob, regularOfferSeq, XRP(10), USD(10))); // domain directory is empty BEAST_EXPECT(checkDirectorySize( @@ -618,79 +618,79 @@ class PermissionedDEX_test : public beast::unit_test::Suite // test domain payment consuming two offers in the path { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - auto const eur = gw_["EUR"]; - env.trust(eur(1000), alice_); + auto const eur = gw["EUR"]; + env.trust(eur(1000), alice); env.close(); - env.trust(eur(1000), bob_); + env.trust(eur(1000), bob); env.close(); - env.trust(eur(1000), carol_); + env.trust(eur(1000), carol); env.close(); - env(pay(gw_, bob_, eur(100))); + env(pay(gw, bob, eur(100))); env.close(); // create XRP/USD domain offer - auto const usdOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + auto const usdOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, usdOfferSeq, XRP(10), USD(10), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, usdOfferSeq, XRP(10), USD(10), 0, true)); // payment fail because there isn't eur offer - env(pay(alice_, carol_, eur(10)), + env(pay(alice, carol, eur(10)), Path(~USD, ~eur), Sendmax(XRP(10)), Domain(domainID), Ter(tecPATH_PARTIAL)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, usdOfferSeq, XRP(10), USD(10), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, usdOfferSeq, XRP(10), USD(10), 0, true)); - // bob_ creates a regular USD/EUR offer - auto const regularOfferSeq{env.seq(bob_)}; - env(offer(bob_, USD(10), eur(10))); + // bob creates a regular USD/EUR offer + auto const regularOfferSeq{env.seq(bob)}; + env(offer(bob, USD(10), eur(10))); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, regularOfferSeq, USD(10), eur(10))); + BEAST_EXPECT(checkOffer(env, bob, regularOfferSeq, USD(10), eur(10))); - // alice_ tries to pay again, but still fails because the regular + // alice tries to pay again, but still fails because the regular // offer cannot be consumed - env(pay(alice_, carol_, eur(10)), + env(pay(alice, carol, eur(10)), Path(~USD, ~eur), Sendmax(XRP(10)), Domain(domainID), Ter(tecPATH_PARTIAL)); env.close(); - // bob_ creates a domain USD/EUR offer - auto const eurOfferSeq{env.seq(bob_)}; - env(offer(bob_, USD(10), eur(10)), Domain(domainID)); + // bob creates a domain USD/EUR offer + auto const eurOfferSeq{env.seq(bob)}; + env(offer(bob, USD(10), eur(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, eurOfferSeq, USD(10), eur(10), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, eurOfferSeq, USD(10), eur(10), 0, true)); - // alice_ successfully consume two domain offers: xrp/usd and usd/eur - env(pay(alice_, carol_, eur(5)), Sendmax(XRP(5)), Domain(domainID), Path(~USD, ~eur)); + // alice successfully consume two domain offers: xrp/usd and usd/eur + env(pay(alice, carol, eur(5)), Sendmax(XRP(5)), Domain(domainID), Path(~USD, ~eur)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, usdOfferSeq, XRP(5), USD(5), 0, true)); - BEAST_EXPECT(checkOffer(env, bob_, eurOfferSeq, USD(5), eur(5), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, usdOfferSeq, XRP(5), USD(5), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, eurOfferSeq, USD(5), eur(5), 0, true)); - // alice_ successfully consume two domain offers and deletes them + // alice successfully consume two domain offers and deletes them // we compute path this time using `paths` - env(pay(alice_, carol_, eur(5)), Sendmax(XRP(5)), Domain(domainID), Paths(XRP)); + env(pay(alice, carol, eur(5)), Sendmax(XRP(5)), Domain(domainID), Paths(XRP)); env.close(); - BEAST_EXPECT(!offerExists(env, bob_, usdOfferSeq)); - BEAST_EXPECT(!offerExists(env, bob_, eurOfferSeq)); + BEAST_EXPECT(!offerExists(env, bob, usdOfferSeq)); + BEAST_EXPECT(!offerExists(env, bob, eurOfferSeq)); // regular offer is not consumed - BEAST_EXPECT(checkOffer(env, bob_, regularOfferSeq, USD(10), eur(10))); + BEAST_EXPECT(checkOffer(env, bob, regularOfferSeq, USD(10), eur(10))); } // domain payment cannot consume offer from another domain { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); // Fund devin and create USD trustline @@ -700,7 +700,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); env.trust(USD(1000), devin); env.close(); - env(pay(gw_, devin, USD(100))); + env(pay(gw, devin, USD(100))); env.close(); auto const badCredType = "badCred"; @@ -720,24 +720,24 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); // domain payment can't consume an offer from another domain - env(pay(alice_, carol_, USD(10)), + env(pay(alice, carol, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID), Ter(tecPATH_PARTIAL)); env.close(); - // bob_ creates an offer under the right domain - auto const bobOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + // bob creates an offer under the right domain + auto const bobOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, bobOfferSeq, XRP(10), USD(10), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, bobOfferSeq, XRP(10), USD(10), 0, true)); // domain payment now consumes from the right domain - env(pay(alice_, carol_, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); + env(pay(alice, carol, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(!offerExists(env, bob_, bobOfferSeq)); + BEAST_EXPECT(!offerExists(env, bob, bobOfferSeq)); } // sanity check: devin, who is part of the domain but doesn't have a @@ -745,10 +745,10 @@ class PermissionedDEX_test : public beast::unit_test::Suite // offer { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); // fund devin but don't create a USD trustline with gateway @@ -764,14 +764,14 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); // successful payment because offer is consumed - env(pay(devin, alice_, USD(10)), Sendmax(XRP(10)), Domain(domainID)); + env(pay(devin, alice, USD(10)), Sendmax(XRP(10)), Domain(domainID)); env.close(); } // offer becomes unfunded when offer owner's cred expires { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); // create devin account who is not part of the domain @@ -780,7 +780,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); env.trust(USD(1000), devin); env.close(); - env(pay(gw_, devin, USD(100))); + env(pay(gw, devin, USD(100))); env.close(); auto jv = credentials::create(devin, domainOwner, credType); @@ -797,7 +797,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); // devin's offer can still be consumed while his cred isn't expired - env(pay(alice_, carol_, USD(5)), Path(~USD), Sendmax(XRP(5)), Domain(domainID)); + env(pay(alice, carol, USD(5)), Path(~USD), Sendmax(XRP(5)), Domain(domainID)); env.close(); BEAST_EXPECT(checkOffer(env, devin, offerSeq, XRP(5), USD(5), 0, true)); @@ -805,7 +805,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(std::chrono::seconds(20)); // devin's offer is unfunded now due to expired cred - env(pay(alice_, carol_, USD(5)), + env(pay(alice, carol, USD(5)), Path(~USD), Sendmax(XRP(5)), Domain(domainID), @@ -817,30 +817,30 @@ class PermissionedDEX_test : public beast::unit_test::Suite // offer becomes unfunded when offer owner's cred is removed { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - auto const offerSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + auto const offerSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); - // bob_'s offer can still be consumed while his cred exists - env(pay(alice_, carol_, USD(5)), Path(~USD), Sendmax(XRP(5)), Domain(domainID)); + // bob's offer can still be consumed while his cred exists + env(pay(alice, carol, USD(5)), Path(~USD), Sendmax(XRP(5)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, offerSeq, XRP(5), USD(5), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, offerSeq, XRP(5), USD(5), 0, true)); - // remove bob_'s cred - env(credentials::deleteCred(domainOwner, bob_, domainOwner, credType)); + // remove bob's cred + env(credentials::deleteCred(domainOwner, bob, domainOwner, credType)); env.close(); - // bob_'s offer is unfunded now due to expired cred - env(pay(alice_, carol_, USD(5)), + // bob's offer is unfunded now due to expired cred + env(pay(alice, carol, USD(5)), Path(~USD), Sendmax(XRP(5)), Domain(domainID), Ter(tecPATH_PARTIAL)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, offerSeq, XRP(5), USD(5), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, offerSeq, XRP(5), USD(5), 0, true)); } } @@ -853,34 +853,34 @@ class PermissionedDEX_test : public beast::unit_test::Suite // payment. If the domain wishes to control who is allowed to ripple // through, they should set the rippling individually Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - auto const eura = alice_["EUR"]; - auto const eurb = bob_["EUR"]; + auto const eura = alice["EUR"]; + auto const eurb = bob["EUR"]; - env.trust(eura(100), bob_); - env.trust(eurb(100), carol_); + env.trust(eura(100), bob); + env.trust(eurb(100), carol); env.close(); - // remove bob_ from domain - env(credentials::deleteCred(domainOwner, bob_, domainOwner, credType)); + // remove bob from domain + env(credentials::deleteCred(domainOwner, bob, domainOwner, credType)); env.close(); - // alice_ can still ripple through bob_ even though he's not part + // alice can still ripple through bob even though he's not part // of the domain, this is intentional - env(pay(alice_, carol_, eurb(10)), Paths(eura), Domain(domainID)); + env(pay(alice, carol, eurb(10)), Paths(eura), Domain(domainID)); env.close(); - env.require(Balance(bob_, eura(10)), Balance(carol_, eurb(10))); + env.require(Balance(bob, eura(10)), Balance(carol, eurb(10))); - // carol_ sets no ripple on bob_ - env(trust(carol_, bob_["EUR"](0), bob_, tfSetNoRipple)); + // carol sets no ripple on bob + env(trust(carol, bob["EUR"](0), bob, tfSetNoRipple)); env.close(); - // payment no longer works because carol_ has no ripple on bob_ - env(pay(alice_, carol_, eurb(5)), Paths(eura), Domain(domainID), Ter(tecPATH_DRY)); + // payment no longer works because carol has no ripple on bob + env(pay(alice, carol, eurb(5)), Paths(eura), Domain(domainID), Ter(tecPATH_DRY)); env.close(); - env.require(Balance(bob_, eura(10)), Balance(carol_, eurb(10))); + env.require(Balance(bob, eura(10)), Balance(carol, eurb(10))); } void @@ -891,37 +891,37 @@ class PermissionedDEX_test : public beast::unit_test::Suite // whether the issuer is in the domain should NOT affect whether an // offer can be consumed in domain payment Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); // create an xrp/usd offer with usd as takergets - auto const bobOffer1Seq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + auto const bobOffer1Seq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); // create an usd/xrp offer with usd as takerpays - auto const bobOffer2Seq{env.seq(bob_)}; - env(offer(bob_, USD(10), XRP(10)), Domain(domainID), Txflags(tfPassive)); + auto const bobOffer2Seq{env.seq(bob)}; + env(offer(bob, USD(10), XRP(10)), Domain(domainID), Txflags(tfPassive)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, bobOffer1Seq, XRP(10), USD(10), 0, true)); - BEAST_EXPECT(checkOffer(env, bob_, bobOffer2Seq, USD(10), XRP(10), lsfPassive, true)); + BEAST_EXPECT(checkOffer(env, bob, bobOffer1Seq, XRP(10), USD(10), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, bobOffer2Seq, USD(10), XRP(10), lsfPassive, true)); // remove gateway from domain - env(credentials::deleteCred(domainOwner, gw_, domainOwner, credType)); + env(credentials::deleteCred(domainOwner, gw, domainOwner, credType)); env.close(); // payment succeeds even if issuer is not in domain // xrp/usd offer is consumed - env(pay(alice_, carol_, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); + env(pay(alice, carol, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(!offerExists(env, bob_, bobOffer1Seq)); + BEAST_EXPECT(!offerExists(env, bob, bobOffer1Seq)); // payment succeeds even if issuer is not in domain // usd/xrp offer is consumed - env(pay(alice_, carol_, XRP(10)), Path(~XRP), Sendmax(USD(10)), Domain(domainID)); + env(pay(alice, carol, XRP(10)), Path(~XRP), Sendmax(USD(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(!offerExists(env, bob_, bobOffer2Seq)); + BEAST_EXPECT(!offerExists(env, bob, bobOffer2Seq)); } void @@ -932,36 +932,36 @@ class PermissionedDEX_test : public beast::unit_test::Suite // checking that an unfunded offer will be implicitly removed by a // successful payment tx Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - auto const aliceOfferSeq{env.seq(alice_)}; - env(offer(alice_, XRP(100), USD(100)), Domain(domainID)); + auto const aliceOfferSeq{env.seq(alice)}; + env(offer(alice, XRP(100), USD(100)), Domain(domainID)); env.close(); - auto const bobOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(20), USD(20)), Domain(domainID)); + auto const bobOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(20), USD(20)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, bobOfferSeq, XRP(20), USD(20), 0, true)); - BEAST_EXPECT(checkOffer(env, alice_, aliceOfferSeq, XRP(100), USD(100), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, bobOfferSeq, XRP(20), USD(20), 0, true)); + BEAST_EXPECT(checkOffer(env, alice, aliceOfferSeq, XRP(100), USD(100), 0, true)); - auto const domainDirKey = getDefaultOfferDirKey(env, bob_, bobOfferSeq); + auto const domainDirKey = getDefaultOfferDirKey(env, bob, bobOfferSeq); BEAST_EXPECT(domainDirKey); BEAST_EXPECT(checkDirectorySize( env, *domainDirKey, 2)); // NOLINT(bugprone-unchecked-optional-access) - // remove alice_ from domain and thus alice_'s offer becomes unfunded - env(credentials::deleteCred(domainOwner, alice_, domainOwner, credType)); + // remove alice from domain and thus alice's offer becomes unfunded + env(credentials::deleteCred(domainOwner, alice, domainOwner, credType)); env.close(); - env(pay(gw_, carol_, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); + env(pay(gw, carol, USD(10)), Path(~USD), Sendmax(XRP(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, bobOfferSeq, XRP(10), USD(10), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, bobOfferSeq, XRP(10), USD(10), 0, true)); - // alice_'s unfunded offer is removed implicitly - BEAST_EXPECT(!offerExists(env, alice_, aliceOfferSeq)); + // alice's unfunded offer is removed implicitly + BEAST_EXPECT(!offerExists(env, alice, aliceOfferSeq)); BEAST_EXPECT(checkDirectorySize( env, *domainDirKey, 1)); // NOLINT(bugprone-unchecked-optional-access) } @@ -972,12 +972,12 @@ class PermissionedDEX_test : public beast::unit_test::Suite testcase("AMM not used"); Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - AMM const amm(env, alice_, XRP(10), USD(50)); + AMM const amm(env, alice, XRP(10), USD(50)); // a domain payment isn't able to consume AMM - env(pay(bob_, carol_, USD(5)), + env(pay(bob, carol, USD(5)), Path(~USD), Sendmax(XRP(5)), Domain(domainID), @@ -985,7 +985,7 @@ class PermissionedDEX_test : public beast::unit_test::Suite env.close(); // a non domain payment can use AMM - env(pay(bob_, carol_, USD(5)), Path(~USD), Sendmax(XRP(5))); + env(pay(bob, carol, USD(5)), Path(~USD), Sendmax(XRP(5))); env.close(); // USD amount in AMM is changed @@ -1001,126 +1001,126 @@ class PermissionedDEX_test : public beast::unit_test::Suite // test preflight - invalid hybrid flag { Env env(*this, features - featurePermissionedDEX); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - env(offer(bob_, XRP(10), USD(10)), + env(offer(bob, XRP(10), USD(10)), Domain(domainID), Txflags(tfHybrid), Ter(temDISABLED)); env.close(); - env(offer(bob_, XRP(10), USD(10)), Txflags(tfHybrid), Ter(temINVALID_FLAG)); + env(offer(bob, XRP(10), USD(10)), Txflags(tfHybrid), Ter(temINVALID_FLAG)); env.close(); env.enableFeature(featurePermissionedDEX); env.close(); // hybrid offer must have domainID - env(offer(bob_, XRP(10), USD(10)), Txflags(tfHybrid), Ter(temINVALID_FLAG)); + env(offer(bob, XRP(10), USD(10)), Txflags(tfHybrid), Ter(temINVALID_FLAG)); env.close(); // hybrid offer must have domainID - auto const offerSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10)), Txflags(tfHybrid), Domain(domainID)); + auto const offerSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), Txflags(tfHybrid), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, offerSeq, XRP(10), USD(10), lsfHybrid, true)); + BEAST_EXPECT(checkOffer(env, bob, offerSeq, XRP(10), USD(10), lsfHybrid, true)); } // apply - domain offer can cross with hybrid { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - auto const bobOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10)), Txflags(tfHybrid), Domain(domainID)); + auto const bobOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), Txflags(tfHybrid), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, bobOfferSeq, XRP(10), USD(10), lsfHybrid, true)); - BEAST_EXPECT(offerExists(env, bob_, bobOfferSeq)); - BEAST_EXPECT(ownerCount(env, bob_) == 3); + BEAST_EXPECT(checkOffer(env, bob, bobOfferSeq, XRP(10), USD(10), lsfHybrid, true)); + BEAST_EXPECT(offerExists(env, bob, bobOfferSeq)); + BEAST_EXPECT(ownerCount(env, bob) == 3); - auto const aliceOfferSeq{env.seq(alice_)}; - env(offer(alice_, USD(10), XRP(10)), Domain(domainID)); + auto const aliceOfferSeq{env.seq(alice)}; + env(offer(alice, USD(10), XRP(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(!offerExists(env, alice_, aliceOfferSeq)); - BEAST_EXPECT(!offerExists(env, bob_, bobOfferSeq)); - BEAST_EXPECT(ownerCount(env, alice_) == 2); + BEAST_EXPECT(!offerExists(env, alice, aliceOfferSeq)); + BEAST_EXPECT(!offerExists(env, bob, bobOfferSeq)); + BEAST_EXPECT(ownerCount(env, alice) == 2); } // apply - open offer can cross with hybrid { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - auto const bobOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10)), Txflags(tfHybrid), Domain(domainID)); + auto const bobOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), Txflags(tfHybrid), Domain(domainID)); env.close(); - BEAST_EXPECT(offerExists(env, bob_, bobOfferSeq)); - BEAST_EXPECT(ownerCount(env, bob_) == 3); - BEAST_EXPECT(checkOffer(env, bob_, bobOfferSeq, XRP(10), USD(10), lsfHybrid, true)); + BEAST_EXPECT(offerExists(env, bob, bobOfferSeq)); + BEAST_EXPECT(ownerCount(env, bob) == 3); + BEAST_EXPECT(checkOffer(env, bob, bobOfferSeq, XRP(10), USD(10), lsfHybrid, true)); - auto const aliceOfferSeq{env.seq(alice_)}; - env(offer(alice_, USD(10), XRP(10))); + auto const aliceOfferSeq{env.seq(alice)}; + env(offer(alice, USD(10), XRP(10))); env.close(); - BEAST_EXPECT(!offerExists(env, alice_, aliceOfferSeq)); - BEAST_EXPECT(!offerExists(env, bob_, bobOfferSeq)); - BEAST_EXPECT(ownerCount(env, alice_) == 2); + BEAST_EXPECT(!offerExists(env, alice, aliceOfferSeq)); + BEAST_EXPECT(!offerExists(env, bob, bobOfferSeq)); + BEAST_EXPECT(ownerCount(env, alice) == 2); } // apply - by default, hybrid offer tries to cross with offers in the // domain book { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - auto const bobOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + auto const bobOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, bobOfferSeq, XRP(10), USD(10), 0, true)); - BEAST_EXPECT(ownerCount(env, bob_) == 3); + BEAST_EXPECT(checkOffer(env, bob, bobOfferSeq, XRP(10), USD(10), 0, true)); + BEAST_EXPECT(ownerCount(env, bob) == 3); // hybrid offer auto crosses with domain offer - auto const aliceOfferSeq{env.seq(alice_)}; - env(offer(alice_, USD(10), XRP(10)), Domain(domainID), Txflags(tfHybrid)); + auto const aliceOfferSeq{env.seq(alice)}; + env(offer(alice, USD(10), XRP(10)), Domain(domainID), Txflags(tfHybrid)); env.close(); - BEAST_EXPECT(!offerExists(env, alice_, aliceOfferSeq)); - BEAST_EXPECT(!offerExists(env, bob_, bobOfferSeq)); - BEAST_EXPECT(ownerCount(env, alice_) == 2); + BEAST_EXPECT(!offerExists(env, alice, aliceOfferSeq)); + BEAST_EXPECT(!offerExists(env, bob, bobOfferSeq)); + BEAST_EXPECT(ownerCount(env, alice) == 2); } // apply - hybrid offer does not automatically cross with open offers // because by default, it only tries to cross domain offers { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - auto const bobOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10))); + auto const bobOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10))); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, bobOfferSeq, XRP(10), USD(10), 0, false)); - BEAST_EXPECT(ownerCount(env, bob_) == 3); + BEAST_EXPECT(checkOffer(env, bob, bobOfferSeq, XRP(10), USD(10), 0, false)); + BEAST_EXPECT(ownerCount(env, bob) == 3); // hybrid offer auto crosses with domain offer - auto const aliceOfferSeq{env.seq(alice_)}; - env(offer(alice_, USD(10), XRP(10)), Domain(domainID), Txflags(tfHybrid)); + auto const aliceOfferSeq{env.seq(alice)}; + env(offer(alice, USD(10), XRP(10)), Domain(domainID), Txflags(tfHybrid)); env.close(); - BEAST_EXPECT(offerExists(env, alice_, aliceOfferSeq)); - BEAST_EXPECT(offerExists(env, bob_, bobOfferSeq)); - BEAST_EXPECT(checkOffer(env, bob_, bobOfferSeq, XRP(10), USD(10), 0, false)); - BEAST_EXPECT(checkOffer(env, alice_, aliceOfferSeq, USD(10), XRP(10), lsfHybrid, true)); - BEAST_EXPECT(ownerCount(env, alice_) == 3); + BEAST_EXPECT(offerExists(env, alice, aliceOfferSeq)); + BEAST_EXPECT(offerExists(env, bob, bobOfferSeq)); + BEAST_EXPECT(checkOffer(env, bob, bobOfferSeq, XRP(10), USD(10), 0, false)); + BEAST_EXPECT(checkOffer(env, alice, aliceOfferSeq, USD(10), XRP(10), lsfHybrid, true)); + BEAST_EXPECT(ownerCount(env, alice) == 3); } } @@ -1129,58 +1129,97 @@ class PermissionedDEX_test : public beast::unit_test::Suite { testcase("Hybrid invalid offer"); - // bob_ has a hybrid offer and then he is removed from domain. - // in this case, the hybrid offer will be considered as unfunded even in - // a regular payment + // bob has a hybrid offer and then he is removed from the domain. + // Domain payments must not consume the offer; regular open-book + // payments follow the fixCleanup3_3_0 behavior checked below. Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - auto const hybridOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(50), USD(50)), Txflags(tfHybrid), Domain(domainID)); + auto const hybridOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(50), USD(50)), Txflags(tfHybrid), Domain(domainID)); env.close(); - // remove bob_ from domain - env(credentials::deleteCred(domainOwner, bob_, domainOwner, credType)); + // remove bob from domain + env(credentials::deleteCred(domainOwner, bob, domainOwner, credType)); env.close(); - // bob_'s hybrid offer is unfunded and can not be consumed in a domain + // bob's hybrid offer is unfunded and can not be consumed in a domain // payment - env(pay(alice_, carol_, USD(5)), + env(pay(alice, carol, USD(5)), Path(~USD), Sendmax(XRP(5)), Domain(domainID), Ter(tecPATH_PARTIAL)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, hybridOfferSeq, XRP(50), USD(50), lsfHybrid, true)); + BEAST_EXPECT(checkOffer(env, bob, hybridOfferSeq, XRP(50), USD(50), lsfHybrid, true)); - // bob_'s unfunded hybrid offer can't be consumed even with a regular - // payment - env(pay(alice_, carol_, USD(5)), Path(~USD), Sendmax(XRP(5)), Ter(tecPATH_PARTIAL)); - env.close(); - BEAST_EXPECT(checkOffer(env, bob_, hybridOfferSeq, XRP(50), USD(50), lsfHybrid, true)); + if (features[fixCleanup3_3_0]) + { + // Post-fixCleanup3_3_0: hybrid offer can still be consumed via a regular + // open-book payment even though the domain credential was revoked. + auto const carolBalBefore = env.balance(carol, USD); + env(pay(alice, carol, USD(5)), Path(~USD), Sendmax(XRP(5))); + env.close(); + BEAST_EXPECT(env.balance(carol, USD) - carolBalBefore == USD(5)); + BEAST_EXPECT(checkOffer(env, bob, hybridOfferSeq, XRP(45), USD(45), lsfHybrid, true)); - // create a regular offer - auto const regularOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10))); - env.close(); - BEAST_EXPECT(offerExists(env, bob_, regularOfferSeq)); - BEAST_EXPECT(checkOffer(env, bob_, regularOfferSeq, XRP(10), USD(10))); + // create a regular offer alongside the hybrid one + auto const regularOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10))); + env.close(); + BEAST_EXPECT(checkOffer(env, bob, regularOfferSeq, XRP(10), USD(10))); - auto const sleHybridOffer = env.le(keylet::offer(bob_.id(), hybridOfferSeq)); - BEAST_EXPECT(sleHybridOffer); - auto const openDir = - sleHybridOffer->getFieldArray(sfAdditionalBooks)[0].getFieldH256(sfBookDirectory); - BEAST_EXPECT(checkDirectorySize(env, openDir, 2)); + auto const sleHybridOffer = env.le(keylet::offer(bob.id(), hybridOfferSeq)); + if (!BEAST_EXPECT(sleHybridOffer)) + return; + auto const openDir = + sleHybridOffer->getFieldArray(sfAdditionalBooks)[0].getFieldH256(sfBookDirectory); + // both offers are in the open book directory + BEAST_EXPECT(checkDirectorySize(env, openDir, 2)); - // this normal payment should consume the regular offer and remove the - // unfunded hybrid offer - env(pay(alice_, carol_, USD(5)), Path(~USD), Sendmax(XRP(5))); - env.close(); + // A regular payment crosses the hybrid offer first (FIFO, older + // offer), then stops; the regular offer is untouched. + env(pay(alice, carol, USD(5)), Path(~USD), Sendmax(XRP(5))); + env.close(); - BEAST_EXPECT(!offerExists(env, bob_, hybridOfferSeq)); - BEAST_EXPECT(checkOffer(env, bob_, regularOfferSeq, XRP(5), USD(5))); - BEAST_EXPECT(checkDirectorySize(env, openDir, 1)); + BEAST_EXPECT(checkOffer(env, bob, hybridOfferSeq, XRP(40), USD(40), lsfHybrid, true)); + BEAST_EXPECT(checkOffer(env, bob, regularOfferSeq, XRP(10), USD(10))); + BEAST_EXPECT(checkDirectorySize(env, openDir, 2)); + } + else + { + // Pre-fixCleanup3_3_0: the open-book traversal + // also runs the offerInDomain eviction check, so the hybrid offer + // is treated as unfunded and the regular payment fails. + env(pay(alice, carol, USD(5)), Path(~USD), Sendmax(XRP(5)), Ter(tecPATH_PARTIAL)); + env.close(); + BEAST_EXPECT(checkOffer(env, bob, hybridOfferSeq, XRP(50), USD(50), lsfHybrid, true)); + + // create a regular offer + auto const regularOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10))); + env.close(); + BEAST_EXPECT(offerExists(env, bob, regularOfferSeq)); + BEAST_EXPECT(checkOffer(env, bob, regularOfferSeq, XRP(10), USD(10))); + + auto const sleHybridOffer = env.le(keylet::offer(bob.id(), hybridOfferSeq)); + if (!BEAST_EXPECT(sleHybridOffer)) + return; + auto const openDir = + sleHybridOffer->getFieldArray(sfAdditionalBooks)[0].getFieldH256(sfBookDirectory); + BEAST_EXPECT(checkDirectorySize(env, openDir, 2)); + + // This payment crosses the regular offer and permanently evicts the + // hybrid offer from the open book (since the payment succeeds, the + // sandbox, including the hybrid eviction, is committed). + env(pay(alice, carol, USD(5)), Path(~USD), Sendmax(XRP(5))); + env.close(); + + BEAST_EXPECT(!offerExists(env, bob, hybridOfferSeq)); + BEAST_EXPECT(checkOffer(env, bob, regularOfferSeq, XRP(5), USD(5))); + BEAST_EXPECT(checkDirectorySize(env, openDir, 1)); + } } void @@ -1191,29 +1230,29 @@ class PermissionedDEX_test : public beast::unit_test::Suite // both non domain and domain payments can consume hybrid offer { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - auto const hybridOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10)), Txflags(tfHybrid), Domain(domainID)); + auto const hybridOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), Txflags(tfHybrid), Domain(domainID)); env.close(); - env(pay(alice_, carol_, USD(5)), Path(~USD), Sendmax(XRP(5)), Domain(domainID)); + env(pay(alice, carol, USD(5)), Path(~USD), Sendmax(XRP(5)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, hybridOfferSeq, XRP(5), USD(5), lsfHybrid, true)); + BEAST_EXPECT(checkOffer(env, bob, hybridOfferSeq, XRP(5), USD(5), lsfHybrid, true)); - // hybrid offer can't be consumed since bob_ is not in domain anymore - env(pay(alice_, carol_, USD(5)), Path(~USD), Sendmax(XRP(5))); + // hybrid offer can't be consumed since bob is not in domain anymore + env(pay(alice, carol, USD(5)), Path(~USD), Sendmax(XRP(5))); env.close(); - BEAST_EXPECT(!offerExists(env, bob_, hybridOfferSeq)); + BEAST_EXPECT(!offerExists(env, bob, hybridOfferSeq)); } // someone from another domain can't cross hybrid if they specified // wrong domainID { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); // Fund accounts @@ -1235,8 +1274,8 @@ class PermissionedDEX_test : public beast::unit_test::Suite env(credentials::accept(devin, badDomainOwner, badCredType)); env.close(); - auto const hybridOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10)), Txflags(tfHybrid), Domain(domainID)); + auto const hybridOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), Txflags(tfHybrid), Domain(domainID)); env.close(); // other domains can't consume the offer @@ -1246,107 +1285,197 @@ class PermissionedDEX_test : public beast::unit_test::Suite Domain(badDomainID), Ter(tecPATH_DRY)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, hybridOfferSeq, XRP(10), USD(10), lsfHybrid, true)); + BEAST_EXPECT(checkOffer(env, bob, hybridOfferSeq, XRP(10), USD(10), lsfHybrid, true)); - env(pay(alice_, carol_, USD(5)), Path(~USD), Sendmax(XRP(5)), Domain(domainID)); + env(pay(alice, carol, USD(5)), Path(~USD), Sendmax(XRP(5)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, hybridOfferSeq, XRP(5), USD(5), lsfHybrid, true)); + BEAST_EXPECT(checkOffer(env, bob, hybridOfferSeq, XRP(5), USD(5), lsfHybrid, true)); - // hybrid offer can't be consumed since bob_ is not in domain anymore - env(pay(alice_, carol_, USD(5)), Path(~USD), Sendmax(XRP(5))); + // hybrid offer can't be consumed since bob is not in domain anymore + env(pay(alice, carol, USD(5)), Path(~USD), Sendmax(XRP(5))); env.close(); - BEAST_EXPECT(!offerExists(env, bob_, hybridOfferSeq)); + BEAST_EXPECT(!offerExists(env, bob, hybridOfferSeq)); } // test domain payment consuming two offers w/ hybrid offer { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - auto const eur = gw_["EUR"]; - env.trust(eur(1000), alice_); + auto const eur = gw["EUR"]; + env.trust(eur(1000), alice); env.close(); - env.trust(eur(1000), bob_); + env.trust(eur(1000), bob); env.close(); - env.trust(eur(1000), carol_); + env.trust(eur(1000), carol); env.close(); - env(pay(gw_, bob_, eur(100))); + env(pay(gw, bob, eur(100))); env.close(); - auto const usdOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10)), Domain(domainID)); + auto const usdOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, usdOfferSeq, XRP(10), USD(10), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, usdOfferSeq, XRP(10), USD(10), 0, true)); // payment fail because there isn't eur offer - env(pay(alice_, carol_, eur(5)), + env(pay(alice, carol, eur(5)), Path(~USD, ~eur), Sendmax(XRP(5)), Domain(domainID), Ter(tecPATH_PARTIAL)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, usdOfferSeq, XRP(10), USD(10), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, usdOfferSeq, XRP(10), USD(10), 0, true)); - // bob_ creates a hybrid eur offer - auto const eurOfferSeq{env.seq(bob_)}; - env(offer(bob_, USD(10), eur(10)), Domain(domainID), Txflags(tfHybrid)); + // bob creates a hybrid eur offer + auto const eurOfferSeq{env.seq(bob)}; + env(offer(bob, USD(10), eur(10)), Domain(domainID), Txflags(tfHybrid)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, eurOfferSeq, USD(10), eur(10), lsfHybrid, true)); + BEAST_EXPECT(checkOffer(env, bob, eurOfferSeq, USD(10), eur(10), lsfHybrid, true)); - // alice_ successfully consume two domain offers: xrp/usd and usd/eur - env(pay(alice_, carol_, eur(5)), Path(~USD, ~eur), Sendmax(XRP(5)), Domain(domainID)); + // alice successfully consume two domain offers: xrp/usd and usd/eur + env(pay(alice, carol, eur(5)), Path(~USD, ~eur), Sendmax(XRP(5)), Domain(domainID)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, usdOfferSeq, XRP(5), USD(5), 0, true)); - BEAST_EXPECT(checkOffer(env, bob_, eurOfferSeq, USD(5), eur(5), lsfHybrid, true)); + BEAST_EXPECT(checkOffer(env, bob, usdOfferSeq, XRP(5), USD(5), 0, true)); + BEAST_EXPECT(checkOffer(env, bob, eurOfferSeq, USD(5), eur(5), lsfHybrid, true)); } // test regular payment using a regular offer and a hybrid offer { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - auto const eur = gw_["EUR"]; - env.trust(eur(1000), alice_); + auto const eur = gw["EUR"]; + env.trust(eur(1000), alice); env.close(); - env.trust(eur(1000), bob_); + env.trust(eur(1000), bob); env.close(); - env.trust(eur(1000), carol_); + env.trust(eur(1000), carol); env.close(); - env(pay(gw_, bob_, eur(100))); + env(pay(gw, bob, eur(100))); env.close(); - // bob_ creates a regular usd offer - auto const usdOfferSeq{env.seq(bob_)}; - env(offer(bob_, XRP(10), USD(10))); + // bob creates a regular usd offer + auto const usdOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10))); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, usdOfferSeq, XRP(10), USD(10), 0, false)); + BEAST_EXPECT(checkOffer(env, bob, usdOfferSeq, XRP(10), USD(10), 0, false)); - // bob_ creates a hybrid eur offer - auto const eurOfferSeq{env.seq(bob_)}; - env(offer(bob_, USD(10), eur(10)), Domain(domainID), Txflags(tfHybrid)); + // bob creates a hybrid eur offer + auto const eurOfferSeq{env.seq(bob)}; + env(offer(bob, USD(10), eur(10)), Domain(domainID), Txflags(tfHybrid)); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, eurOfferSeq, USD(10), eur(10), lsfHybrid, true)); + BEAST_EXPECT(checkOffer(env, bob, eurOfferSeq, USD(10), eur(10), lsfHybrid, true)); - // alice_ successfully consume two offers: xrp/usd and usd/eur - env(pay(alice_, carol_, eur(5)), Path(~USD, ~eur), Sendmax(XRP(5))); + // alice successfully consume two offers: xrp/usd and usd/eur + env(pay(alice, carol, eur(5)), Path(~USD, ~eur), Sendmax(XRP(5))); env.close(); - BEAST_EXPECT(checkOffer(env, bob_, usdOfferSeq, XRP(5), USD(5), 0, false)); - BEAST_EXPECT(checkOffer(env, bob_, eurOfferSeq, USD(5), eur(5), lsfHybrid, true)); + BEAST_EXPECT(checkOffer(env, bob, usdOfferSeq, XRP(5), USD(5), 0, false)); + BEAST_EXPECT(checkOffer(env, bob, eurOfferSeq, USD(5), eur(5), lsfHybrid, true)); } } + // Test that a hybrid offer remains crossable in the open book after the + // owner's domain credential expires. A domain payment after expiry should + // fail (domain book evicts the offer in its sandbox), but the open book + // remains usable. + void + testHybridOpenBookAfterCredentialExpiry(FeatureBitset features) + { + testcase("Hybrid open book after credential expiry"); + + Env env(*this, features); + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = + PermissionedDEX(env); + + Account const devin("devin"); + env.fund(XRP(100000), devin); + env.close(); + env.trust(USD(1000), devin); + env.close(); + env(pay(gw, devin, USD(100))); + env.close(); + + // Give devin a credential that expires far enough in the future to + // survive the setup env.close() calls. + auto jv = credentials::create(devin, domainOwner, credType); + uint32_t const t = env.current()->header().parentCloseTime.time_since_epoch().count(); + jv[sfExpiration.jsonName] = t + 100; + env(jv); + env.close(); + env(credentials::accept(devin, domainOwner, credType)); + env.close(); + + // Devin creates a hybrid offer: sell USD(10) for XRP(10). + // The offer is placed in both the domain book and the open book. + auto const hybridOfferSeq{env.seq(devin)}; + env(offer(devin, XRP(10), USD(10)), Txflags(tfHybrid), Domain(domainID)); + env.close(); + + BEAST_EXPECT(checkOffer(env, devin, hybridOfferSeq, XRP(10), USD(10), lsfHybrid, true)); + + // A non-domain open-book payment partially crosses the offer while + // devin's credential is still valid. + auto carolBalance = env.balance(carol, USD); + env(pay(alice, carol, USD(5)), Path(~USD), Sendmax(XRP(5))); + env.close(); + BEAST_EXPECT(env.balance(carol, USD) - carolBalance == USD(5)); + BEAST_EXPECT(checkOffer(env, devin, hybridOfferSeq, XRP(5), USD(5), lsfHybrid, true)); + + // Advance time so that devin's credential expires. + env.close(std::chrono::seconds(100)); + + // Confirm devin can no longer create domain offers. + env(offer(devin, XRP(1), USD(1)), Domain(domainID), Ter(tecNO_PERMISSION)); + env.close(); + + // The hybrid offer must still exist in the open book after expiry. + BEAST_EXPECT(offerExists(env, devin, hybridOfferSeq)); + + // A non-domain open-book payment must cross (not evict) the + // remaining portion of devin's hybrid offer. + carolBalance = env.balance(carol, USD); + env(pay(alice, carol, USD(2)), Path(~USD), Sendmax(XRP(2))); + env.close(); + + // Carol received USD; the offer was crossed, not evicted. + BEAST_EXPECT(env.balance(carol, USD) - carolBalance == USD(2)); + // Offer still exists with 3 USD / 3 XRP remaining. + BEAST_EXPECT(checkOffer(env, devin, hybridOfferSeq, XRP(3), USD(3), lsfHybrid, true)); + + // A domain payment now fails because the domain book evicts devin's + // offer (his credential has expired). The eviction is rolled back with + // the failed sandbox, so the offer is NOT permanently removed. + env(pay(alice, carol, USD(1)), + Path(~USD), + Sendmax(XRP(1)), + Domain(domainID), + Ter(tecPATH_PARTIAL)); + env.close(); + + // Offer still intact in the open book; domain payment did not + // permanently delete it. + BEAST_EXPECT(checkOffer(env, devin, hybridOfferSeq, XRP(3), USD(3), lsfHybrid, true)); + + // The open book can still fully consume the remaining portion. + carolBalance = env.balance(carol, USD); + env(pay(alice, carol, USD(3)), Path(~USD), Sendmax(XRP(3))); + env.close(); + BEAST_EXPECT(env.balance(carol, USD) - carolBalance == USD(3)); + BEAST_EXPECT(!offerExists(env, devin, hybridOfferSeq)); + } + void testHybridOfferDirectories(FeatureBitset features) { Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); std::vector offerSeqs; @@ -1362,12 +1491,12 @@ class PermissionedDEX_test : public beast::unit_test::Suite for (size_t i = 1; i <= dirCnt; i++) { - auto const bobOfferSeq{env.seq(bob_)}; + auto const bobOfferSeq{env.seq(bob)}; offerSeqs.emplace_back(bobOfferSeq); - env(offer(bob_, XRP(10), USD(10)), Txflags(tfHybrid), Domain(domainID)); + env(offer(bob, XRP(10), USD(10)), Txflags(tfHybrid), Domain(domainID)); env.close(); - auto const sleOffer = env.le(keylet::offer(bob_.id(), bobOfferSeq)); + auto const sleOffer = env.le(keylet::offer(bob.id(), bobOfferSeq)); BEAST_EXPECT(sleOffer); BEAST_EXPECT(sleOffer->getFieldH256(sfBookDirectory) == domainDir); BEAST_EXPECT(sleOffer->getFieldArray(sfAdditionalBooks).size() == 1); @@ -1375,17 +1504,17 @@ class PermissionedDEX_test : public beast::unit_test::Suite sleOffer->getFieldArray(sfAdditionalBooks)[0].getFieldH256(sfBookDirectory) == openDir); - BEAST_EXPECT(checkOffer(env, bob_, bobOfferSeq, XRP(10), USD(10), lsfHybrid, true)); + BEAST_EXPECT(checkOffer(env, bob, bobOfferSeq, XRP(10), USD(10), lsfHybrid, true)); BEAST_EXPECT(checkDirectorySize(env, domainDir, i)); BEAST_EXPECT(checkDirectorySize(env, openDir, i)); } for (auto const offerSeq : offerSeqs) { - env(offerCancel(bob_, offerSeq)); + env(offerCancel(bob, offerSeq)); env.close(); dirCnt--; - BEAST_EXPECT(!offerExists(env, bob_, offerSeq)); + BEAST_EXPECT(!offerExists(env, bob, offerSeq)); BEAST_EXPECT(checkDirectorySize(env, domainDir, dirCnt)); BEAST_EXPECT(checkDirectorySize(env, openDir, dirCnt)); } @@ -1397,34 +1526,34 @@ class PermissionedDEX_test : public beast::unit_test::Suite testcase("Auto bridge"); Env env(*this, features); - auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); - auto const eur = gw_["EUR"]; + auto const eur = gw["EUR"]; - for (auto const& account : {alice_, bob_, carol_}) + for (auto const& account : {alice, bob, carol}) { env(trust(account, eur(10000))); env.close(); } - env(pay(gw_, carol_, eur(1))); + env(pay(gw, carol, eur(1))); env.close(); - auto const aliceOfferSeq{env.seq(alice_)}; - auto const bobOfferSeq{env.seq(bob_)}; - env(offer(alice_, XRP(100), USD(1)), Domain(domainID)); - env(offer(bob_, eur(1), XRP(100)), Domain(domainID)); + auto const aliceOfferSeq{env.seq(alice)}; + auto const bobOfferSeq{env.seq(bob)}; + env(offer(alice, XRP(100), USD(1)), Domain(domainID)); + env(offer(bob, eur(1), XRP(100)), Domain(domainID)); env.close(); - // carol_'s offer should cross bob_ and alice_'s offers due to auto + // carol's offer should cross bob and alice's offers due to auto // bridging - auto const carolOfferSeq{env.seq(carol_)}; - env(offer(carol_, USD(1), eur(1)), Domain(domainID)); + auto const carolOfferSeq{env.seq(carol)}; + env(offer(carol, USD(1), eur(1)), Domain(domainID)); env.close(); - BEAST_EXPECT(!offerExists(env, bob_, aliceOfferSeq)); - BEAST_EXPECT(!offerExists(env, bob_, bobOfferSeq)); - BEAST_EXPECT(!offerExists(env, bob_, carolOfferSeq)); + BEAST_EXPECT(!offerExists(env, bob, aliceOfferSeq)); + BEAST_EXPECT(!offerExists(env, bob, bobOfferSeq)); + BEAST_EXPECT(!offerExists(env, bob, carolOfferSeq)); } void @@ -1819,7 +1948,9 @@ public: // Test hybrid offers testHybridOfferCreate(all); testHybridBookStep(all); + testHybridInvalidOffer(all - fixCleanup3_3_0); testHybridInvalidOffer(all); + testHybridOpenBookAfterCredentialExpiry(all); testHybridOfferDirectories(all); testHybridMalformedOffer(all); testHybridMalformedOffer(all - fixCleanup3_1_3); From cee157485e1e08181a37afc579bdd6f8fabf263e Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 11 Jun 2026 13:59:22 +0100 Subject: [PATCH 087/135] ci: Run sanitizers on release builds too (#7527) --- .github/scripts/strategy-matrix/linux.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index e6c807ac95..4f45216cda 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -10,7 +10,7 @@ { "compiler": ["gcc", "clang"], - "build_type": ["Debug"], + "build_type": ["Debug", "Release"], "arch": ["amd64"], "sanitizers": ["address", "undefinedbehavior"] }, From 8e618d68cd254d44b40bb41ce588d58b8b34c2c3 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 11 Jun 2026 18:36:33 +0100 Subject: [PATCH 088/135] ci: Patch conan recipe for Nix to be able to use on macOS (#7532) --- cspell.config.yaml | 2 ++ nix/devshell.nix | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/cspell.config.yaml b/cspell.config.yaml index 926ac06596..77f0e9df7a 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -233,8 +233,10 @@ words: - pyenv - pyparsing - qalloc + - qbsprofile - queuable - Raphson + - rcflags - replayer - rerere - retriable diff --git a/nix/devshell.nix b/nix/devshell.nix index 1bd7ea4c0c..105033eb06 100644 --- a/nix/devshell.nix +++ b/nix/devshell.nix @@ -1,6 +1,26 @@ { pkgs, ... }: let - inherit (import ./packages.nix { inherit pkgs; }) commonPackages; + # conan is in the binary cache for Linux but not for Darwin, so on Darwin + # it is always built from source — and its bundled test suite is unreliable + # in the sandbox: `test_qbsprofile_rcflags` needs gcc (absent on Darwin, see + # https://github.com/NixOS/nixpkgs/pull/528995) and the patch tests are + # flaky from source. We only use conan as a build tool, so skip its tests on + # Darwin. Scoped to the dev shell (not the CI env, which builds conan on + # Linux from the cache). Drop once the fix reaches nixos-unstable and the + # lock is bumped. + pkgs_patched = + if pkgs.stdenv.isDarwin then + pkgs.extend ( + final: prev: { + conan = prev.conan.overridePythonAttrs (_: { + doCheck = false; + }); + } + ) + else + pkgs; + + inherit (import ./packages.nix { pkgs = pkgs_patched; }) commonPackages; # Supported compiler versions gccVersion = pkgs.lib.range 13 15; From df395d685159717c1725219232b9a9b3b8dc45da Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Thu, 11 Jun 2026 19:05:36 +0100 Subject: [PATCH 089/135] test: Add null check unit test for `Oracle::aggregatePrice` (#7306) Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> --- src/test/rpc/GetAggregatePrice_test.cpp | 89 +++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/src/test/rpc/GetAggregatePrice_test.cpp b/src/test/rpc/GetAggregatePrice_test.cpp index 37ecc54172..214bd12183 100644 --- a/src/test/rpc/GetAggregatePrice_test.cpp +++ b/src/test/rpc/GetAggregatePrice_test.cpp @@ -3,12 +3,21 @@ #include #include +#include + #include +#include #include +#include +#include +#include #include +#include +#include #include #include +#include #include #include #include @@ -312,11 +321,91 @@ public: } } + void + testNullTxReadMeta() + { + testcase("Null txRead metadata"); + using namespace jtx; + + // Verify that iteratePriceData handles a null txRead result + // gracefully (returns early) rather than crashing with a + // nullptr dereference. This simulates local data corruption + // where a transaction referenced by sfPreviousTxnID is missing + // from the ledger's transaction map. + Env env(*this); + auto const baseFee = static_cast(env.current()->fees().base.drops()); + + Account const owner{"owner"}; + env.fund(XRP(1'000), owner); + + // Create oracle with XRP/USD and XRP/EUR + Oracle oracle( + env, + {.owner = owner, + .series = {{"XRP", "USD", 740, 1}, {"XRP", "EUR", 840, 1}}, + .fee = baseFee}); + + // Update oracle to only have XRP/EUR, pushing XRP/USD into + // history. iteratePriceData will need to read historical tx + // metadata to find the XRP/USD price. + oracle.set(UpdateArg{.series = {{"XRP", "EUR", 850, 1}}, .fee = baseFee}); + + OraclesData const oracles{{owner, oracle.documentID()}}; + + // Precondition: with an uncorrupted oracle, the historical + // traversal must succeed and produce a price for XRP/USD. + // This proves the test reaches iteratePriceData's history + // path; without it, a future change that breaks the setup + // could turn the post-corruption assertion into a vacuous + // pass (objectNotFound is reachable from many unrelated + // code paths). + { + auto const ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles); + BEAST_EXPECT(!ret.isMember(jss::error)); + BEAST_EXPECT(ret.isMember(jss::median)); + } + + // Simulate data corruption: modify the oracle SLE in the open + // ledger to have a bogus sfPreviousTxnID that doesn't exist in + // any ledger. sfPreviousTxnLgrSeq still points to a valid closed + // ledger, so getLedgerBySeq succeeds but txRead returns null. + auto const oracleKeylet = keylet::oracle(owner, oracle.documentID()); + uint256 const bogusTxnID{0xABCABCAB}; + bool const modified = env.app().getOpenLedger().modify( + [&oracleKeylet, &bogusTxnID](OpenView& view, beast::Journal) -> bool { + auto const sle = view.read(oracleKeylet); + if (!sle) + return false; + auto replacement = std::make_shared(*sle, sle->key()); + replacement->setFieldH256(sfPreviousTxnID, bogusTxnID); + view.rawReplace(replacement); + return true; + }); + + // Confirm the injection actually took effect: modify must + // report success, and re-reading the SLE must show the + // bogus hash. Otherwise the failure-mode assertion below + // would not be exercising the null-txRead path at all. + BEAST_EXPECT(modified); + if (auto const sle = env.current()->read(oracleKeylet); BEAST_EXPECT(sle)) + BEAST_EXPECT(sle->getFieldH256(sfPreviousTxnID) == bogusTxnID); + + // Query for XRP/USD using the "current" (open) ledger. + // The oracle SLE now has a bogus sfPreviousTxnID. The current + // oracle only has EUR, so iteratePriceData will try to read + // history. txRead returns null for the bogus hash, and the + // null check should cause a graceful early return instead of + // a nullptr dereference. + auto const ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles); + BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound"); + } + void run() override { testErrors(); testRpc(); + testNullTxReadMeta(); } }; From 4387aac1a5275ad987412b63b61e7aed2ef93e1a Mon Sep 17 00:00:00 2001 From: Sergey Kuznetsov Date: Mon, 15 Jun 2026 15:55:43 +0100 Subject: [PATCH 090/135] chore: Remove conan patch in nix (#7534) --- flake.lock | 13 +++++++------ flake.nix | 2 +- nix/devshell.nix | 22 +--------------------- 3 files changed, 9 insertions(+), 28 deletions(-) diff --git a/flake.lock b/flake.lock index f8553af703..80243ccf15 100644 --- a/flake.lock +++ b/flake.lock @@ -2,17 +2,18 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1780749050, - "narHash": "sha256-3av0pIjlOWQ6rDbNOmpUSvbNnJkGORQKKjb4LtCZsIY=", + "lastModified": 1781173989, + "narHash": "sha256-fnzKKPvS+oieI/pTzotA5tkoM47EB1NpaBcgk4R97hE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a799d3e3886da994fa307f817a6bc705ae538eeb", + "rev": "8c91a71d13451abc40eb9dae8910f972f979852f", "type": "github" }, "original": { - "id": "nixpkgs", - "ref": "nixos-unstable", - "type": "indirect" + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" } }, "nixpkgs-custom-glibc": { diff --git a/flake.nix b/flake.nix index 3b3ec7ea08..c52f4d050e 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "Nix related things for xrpld"; inputs = { - nixpkgs.url = "nixpkgs/nixos-unstable"; + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; # nixpkgs snapshot (2020-06-30) that shipped glibc 2.31 as the primary # version — matches the system libc on Ubuntu 20.04 LTS. Imported # manually (flake = false) because this revision predates nixpkgs' diff --git a/nix/devshell.nix b/nix/devshell.nix index 105033eb06..1bd7ea4c0c 100644 --- a/nix/devshell.nix +++ b/nix/devshell.nix @@ -1,26 +1,6 @@ { pkgs, ... }: let - # conan is in the binary cache for Linux but not for Darwin, so on Darwin - # it is always built from source — and its bundled test suite is unreliable - # in the sandbox: `test_qbsprofile_rcflags` needs gcc (absent on Darwin, see - # https://github.com/NixOS/nixpkgs/pull/528995) and the patch tests are - # flaky from source. We only use conan as a build tool, so skip its tests on - # Darwin. Scoped to the dev shell (not the CI env, which builds conan on - # Linux from the cache). Drop once the fix reaches nixos-unstable and the - # lock is bumped. - pkgs_patched = - if pkgs.stdenv.isDarwin then - pkgs.extend ( - final: prev: { - conan = prev.conan.overridePythonAttrs (_: { - doCheck = false; - }); - } - ) - else - pkgs; - - inherit (import ./packages.nix { pkgs = pkgs_patched; }) commonPackages; + inherit (import ./packages.nix { inherit pkgs; }) commonPackages; # Supported compiler versions gccVersion = pkgs.lib.range 13 15; From f5985e73ecce0de85eae73e54405d62fa16a091b Mon Sep 17 00:00:00 2001 From: Bart Date: Mon, 15 Jun 2026 10:55:56 -0400 Subject: [PATCH 091/135] fix: Always charge peer on strand (#7422) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- src/xrpld/overlay/detail/PeerImp.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 323dc14673..cda576add0 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -58,7 +58,6 @@ #include #include #include -#include #include #include #include @@ -68,6 +67,7 @@ #include #include #include +#include #include #include #include @@ -392,13 +392,15 @@ PeerImp::removeTxQueue(uint256 const& hash) void PeerImp::charge(Resource::Charge const& fee, std::string const& context) { - if ((usage_.charge(fee, context) == Resource::Disposition::Drop) && - usage_.disconnect(pJournal_) && strand_.running_in_this_thread()) - { - // Sever the connection - overlay_.incPeerDisconnectCharges(); - fail("charge: Resources"); - } + dispatch(strand_, [this, self = shared_from_this(), fee, context]() { + if (usage_.charge(fee, context) == Resource::Disposition::Drop && + usage_.disconnect(pJournal_)) + { + // Sever the connection. + overlay_.incPeerDisconnectCharges(); + fail("charge: Resources"); + } + }); } //------------------------------------------------------------------------------ From b34aa84e5aa0ba8741e38f037350677239f9da12 Mon Sep 17 00:00:00 2001 From: Zhiyuan Wang <96991820+Kassaking7@users.noreply.github.com> Date: Mon, 15 Jun 2026 11:31:22 -0400 Subject: [PATCH 092/135] fix: Check Fee-Free Division by Zero in AMMWithdraw singleWithdrawEPrice (#6989) --- .../tx/transactors/dex/AMMWithdraw.cpp | 11 +++++--- src/test/app/AMM_test.cpp | 25 +++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp index e57f8558ff..d3a6c9c74c 100644 --- a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp @@ -1091,10 +1091,13 @@ AMMWithdraw::singleWithdrawEPrice( // t = T*(T + A*E*(f - 2))/(T*f - A*E) Number const ae = amountBalance * ePrice; auto const f = getFee(tfee); - auto tokNoRoundCb = [&] { - return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae); - }; - auto tokProdCb = [&] { return (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae); }; + auto const denom = lptAMMBalance * f - ae; + // fixCleanup3_3_0: guard against division by zero + // when ePrice == lptAMMBalance*f/amountBalance + if (view.rules().enabled(fixCleanup3_3_0) && denom == beast::kZero) + return {tecAMM_FAILED, STAmount{}}; + auto tokNoRoundCb = [&] { return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) / denom; }; + auto tokProdCb = [&] { return (lptAMMBalance + ae * (f - 2)) / denom; }; auto const tokensAdj = getRoundedLPTokens(view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::No); if (tokensAdj <= beast::kZero) diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index e3a1cc935f..1b54c2aab9 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -2229,6 +2229,31 @@ private: ammAlice.withdraw(alice_, XRPAmount{9'999'999'999}); BEAST_EXPECT(ammAlice.expectBalances(XRPAmount{1}, USD(10'000), IOUAmount{100})); }); + + // singleWithdrawEPrice: crafted ePrice = lptAMMBalance*f/amountBalance + // makes the denominator (T*f - A*E) exactly zero. + // Pre-fixCleanup3_3_0: std::overflow_error escapes to the + // transactor backstop and is returned as tefEXCEPTION. + // Post-fixCleanup3_3_0: denominator check returns tecAMM_FAILED. + // + // Pool: USD(100)/EUR(100), baseFee=1000 (1%). + // Alice is the creator so her discounted fee is 100 (0.1%), f=0.001. + // ePrice = lptAMMBalance(100) * f(0.001) / amountBalance(100) = 0.001 + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const err = + env.enabled(fixCleanup3_3_0) ? Ter(tecAMM_FAILED) : Ter(tefEXCEPTION); + ammAlice.withdraw( + WithdrawArg{ + .account = alice_, + .asset1Out = USD(0), + .maxEP = IOUAmount{1, -3}, // ePrice=0.001 → denom=0 + .err = err}); + }, + {{USD(100), EUR(100)}}, + 1000, + std::nullopt, + {all - fixCleanup3_3_0, all}); } void From fe4c8ae82a76ccd853171ce7547a4e1785d0de87 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 15 Jun 2026 20:04:33 +0100 Subject: [PATCH 093/135] build: Add ClangBuildAnalyzer to Nix (#7538) Co-authored-by: Bart --- nix/docker/check-tools.sh | 1 + nix/packages.nix | 1 + 2 files changed, 2 insertions(+) diff --git a/nix/docker/check-tools.sh b/nix/docker/check-tools.sh index 276e5977ff..a46c2dd997 100755 --- a/nix/docker/check-tools.sh +++ b/nix/docker/check-tools.sh @@ -6,6 +6,7 @@ ccache --version clang --version clang++ --version clang-format --version +ClangBuildAnalyzer --version cmake --version conan --version curl --version diff --git a/nix/packages.nix b/nix/packages.nix index fc4eff679e..5a7f20ec49 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -9,6 +9,7 @@ in { commonPackages = with pkgs; [ ccache + clangbuildanalyzer cmake conan curlMinimal # needed for codecov/codecov-action From 2df96b155061bbd23a6477776ea3af3f0cd26e94 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Mon, 15 Jun 2026 20:25:37 +0100 Subject: [PATCH 094/135] fix: Silence UBSan diagnostics in the ubsan build config (#7531) Co-authored-by: Claude Opus 4.8 --- .../workflows/reusable-build-test-config.yml | 35 +++--- .../suppressions/runtime-ubsan-options.txt | 2 +- sanitizers/suppressions/ubsan.supp | 106 +++++++++++++----- src/xrpld/rpc/handlers/ledger/Ledger.cpp | 15 ++- 4 files changed, 112 insertions(+), 46 deletions(-) diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 8cb5f8c46a..28d317e4dd 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -164,6 +164,27 @@ jobs: ${CMAKE_ARGS} \ .. + # Export the sanitizer options before any instrumented binary runs. The + # protocol code-gen and build steps below invoke instrumented dependency + # tools (protoc, grpc), so setting UBSAN_OPTIONS here lets the UBSan + # suppression list silence their diagnostics too, not just at test time. + # GITHUB_WORKSPACE (not the github.workspace context) is used so the path + # resolves correctly inside the container job. + - name: Set sanitizer options + if: ${{ !inputs.build_only && env.SANITIZERS_ENABLED == 'true' }} + env: + CONFIG_NAME: ${{ inputs.config_name }} + run: | + SUPP="${GITHUB_WORKSPACE}/sanitizers/suppressions" + ASAN_OPTS="include=${SUPP}/runtime-asan-options.txt:suppressions=${SUPP}/asan.supp" + if [[ "${CONFIG_NAME}" == *gcc* ]]; then + ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0" + fi + echo "ASAN_OPTIONS=${ASAN_OPTS}" >>${GITHUB_ENV} + echo "TSAN_OPTIONS=include=${SUPP}/runtime-tsan-options.txt:suppressions=${SUPP}/tsan.supp" >>${GITHUB_ENV} + echo "UBSAN_OPTIONS=include=${SUPP}/runtime-ubsan-options.txt:suppressions=${SUPP}/ubsan.supp" >>${GITHUB_ENV} + echo "LSAN_OPTIONS=include=${SUPP}/runtime-lsan-options.txt:suppressions=${SUPP}/lsan.supp" >>${GITHUB_ENV} + - name: Check protocol autogen files are up-to-date working-directory: ${{ env.BUILD_DIR }} env: @@ -279,20 +300,6 @@ jobs: run: | ./xrpld --version | grep libvoidstar - - name: Set sanitizer options - if: ${{ !inputs.build_only && env.SANITIZERS_ENABLED == 'true' }} - env: - CONFIG_NAME: ${{ inputs.config_name }} - run: | - ASAN_OPTS="include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-asan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp" - if [[ "${CONFIG_NAME}" == *gcc* ]]; then - ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0" - fi - echo "ASAN_OPTIONS=${ASAN_OPTS}" >>${GITHUB_ENV} - echo "TSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-tsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >>${GITHUB_ENV} - echo "UBSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-ubsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/ubsan.supp" >>${GITHUB_ENV} - echo "LSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-lsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/lsan.supp" >>${GITHUB_ENV} - - name: Run the separate tests if: ${{ !inputs.build_only }} working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }} diff --git a/sanitizers/suppressions/runtime-ubsan-options.txt b/sanitizers/suppressions/runtime-ubsan-options.txt index fcfccf7bae..4b48efbe08 100644 --- a/sanitizers/suppressions/runtime-ubsan-options.txt +++ b/sanitizers/suppressions/runtime-ubsan-options.txt @@ -1 +1 @@ -halt_on_error=false +halt_on_error=true diff --git a/sanitizers/suppressions/ubsan.supp b/sanitizers/suppressions/ubsan.supp index 88d8e82e33..7e3e02f855 100644 --- a/sanitizers/suppressions/ubsan.supp +++ b/sanitizers/suppressions/ubsan.supp @@ -72,7 +72,7 @@ vptr:boost # Google protobuf - intentional overflows in hash functions undefined:protobuf -unsigned-integer-overflow:google/protobuf/stubs/stringpiece.h +unsigned-integer-overflow:protobuf # gRPC intentional overflows in timer calculations unsigned-integer-overflow:grpc @@ -102,47 +102,103 @@ undefined:nudb # Snappy compression library intentional overflows unsigned-integer-overflow:snappy.cc -# Abseil intentional overflows -unsigned-integer-overflow:absl/strings/numbers.cc -unsigned-integer-overflow:absl/strings/internal/cord_rep_flat.h -unsigned-integer-overflow:absl/base/internal/low_level_alloc.cc -unsigned-integer-overflow:absl/hash/internal/hash.h -unsigned-integer-overflow:absl/container/internal/raw_hash_set.h +# Abseil intentional overflows in hashing, RNG and time arithmetic. +# Matched at library scope (like boost above): the wraparound is by design +# across many absl files (hash mixing, raw_hash_set probing, duration math, +# int128, uniform_int_distribution), so listing individual files just churns. +unsigned-integer-overflow:absl # Standard library intentional overflows unsigned-integer-overflow:basic_string.h +unsigned-integer-overflow:bits/align.h +unsigned-integer-overflow:bits/basic_string.tcc unsigned-integer-overflow:bits/chrono.h unsigned-integer-overflow:bits/random.h unsigned-integer-overflow:bits/random.tcc unsigned-integer-overflow:bits/stl_algobase.h +unsigned-integer-overflow:bits/string_view.tcc unsigned-integer-overflow:bits/uniform_int_dist.h unsigned-integer-overflow:string_view unsigned-integer-overflow:__random/seed_seq.h unsigned-integer-overflow:__charconv/traits.h unsigned-integer-overflow:__chrono/duration.h +# libstdc++ (std::__bit_ceil etc.) negates an unsigned width; is a +# distinct header from the bits/ directory so it needs its own entry. +unsigned-integer-overflow:include/c++/*/bit # ============================================================================= # Rippled code suppressions # ============================================================================= -# Signed integer negation (-value) in amount types. -# INT64_MIN cannot occur in practice due to domain invariants (mantissa ranges -# are well within int64_t bounds), but UBSan flags the pattern as potential -# signed overflow. Narrowed to operator- to avoid suppressing unrelated -# overflows anywhere in a stack trace containing these type names. -signed-integer-overflow:operator-*IOUAmount* -signed-integer-overflow:operator-*XRPAmount* -signed-integer-overflow:operator-*MPTAmount* -signed-integer-overflow:operator-*STAmount* +# These suppressions are keyed by SOURCE FILE, not function name. This UBSan +# build runs without symbol information, so the runtime only knows the +# file:line of each report, never the enclosing function — function-name +# patterns silently never match. Each entry below is therefore scoped to the +# file whose arithmetic is intentional; the comment names the specific +# construct. -# STAmount::operator+ signed addition — operands are bounded by total supply -# (~10^17 for XRP, ~10^18 for MPT) so overflow cannot occur in practice. -signed-integer-overflow:operator+*STAmount* +# STAmount amount-type arithmetic. Unary negation of the mantissa in xrp()/ +# iou()/mpt()/canonicalize() and getInt64Value, plus bounded +/- on amounts: +# INT64_MIN cannot occur because canonicalize() keeps the mantissa well within +# int64_t, and operands are bounded by total supply (~10^17 XRP, ~10^18 MPT). +signed-integer-overflow:protocol/STAmount.cpp -# STAmount::getRate uses unsigned shift and addition -unsigned-integer-overflow:*STAmount*getRate* -# STAmount::serialize uses unsigned bitwise operations -unsigned-integer-overflow:*STAmount*serialize* +# nft::cipheredTaxon uses intentional uint32 wraparound (LCG permutation); +# the helper lives in the generated protocol header nft.h. +unsigned-integer-overflow:protocol/nft.h -# nft::cipheredTaxon uses intentional uint32 wraparound (LCG permutation) -unsigned-integer-overflow:cipheredTaxon +# STPathElement::getHash multiplies/adds accumulators (non-secure, speed-first). +unsigned-integer-overflow:protocol/STPathSet.cpp + +# beast XorShiftEngine PRNG and murmurhash3 mixing wrap by design. +unsigned-integer-overflow:beast/xor_shift_engine.h + +# Number::normalizeToRange multiplies the mantissa by powers of ten; the result +# is intentionally allowed to wrap while searching for the in-range value. +unsigned-integer-overflow:basics/Number.h + +# Counter / sequence arithmetic with intentional unsigned wraparound, each +# guarded by an explicit overflow or domain check at the call site: +# base_uint operator++/-- wrap by definition; +# ApplyView::insertPage ++page is asserted to wrap to 0 (page exhaustion); +# confineOwnerCount documents "overflow is well defined on unsigned"; +# NFTokenMint checks tokenSeq + 1u == 0u; AmendmentTable does (seq - 1) / 256. +unsigned-integer-overflow:basics/base_uint.h +unsigned-integer-overflow:ledger/ApplyView.cpp +unsigned-integer-overflow:ledger/helpers/AccountRootHelpers.cpp +unsigned-integer-overflow:tx/transactors/nft/NFTokenMint.cpp +unsigned-integer-overflow:app/misc/detail/AmendmentTable.cpp + +# Sentinel / bounded subtractions that wrap by design (loop counters, reverse +# iteration, "not found" sentinels, balance math bounded by issuance invariants, +# base58/base64 codec index math, hash-router and role bit math). +unsigned-integer-overflow:shamap/SHAMap.cpp +unsigned-integer-overflow:protocol/Permissions.cpp +unsigned-integer-overflow:protocol/tokens.cpp +unsigned-integer-overflow:basics/base64.cpp +unsigned-integer-overflow:json/json_value.cpp +unsigned-integer-overflow:app/misc/NetworkOPs.cpp +unsigned-integer-overflow:rpc/detail/Role.cpp +unsigned-integer-overflow:tx/transactors/oracle/OracleSet.cpp +unsigned-integer-overflow:ledger/helpers/MPTokenHelpers.cpp +unsigned-integer-overflow:crypto/RFC1751.cpp +unsigned-integer-overflow:tx/paths/detail/StrandFlow.h +unsigned-integer-overflow:protocol/STObject.h + +# GetAggregatePrice negates an unsigned trim count to step a reverse iterator; +# trimCount is bounded by the price set size. +unsigned-integer-overflow:rpc/handlers/orderbook/GetAggregatePrice.cpp + +# Test-only intentional overflow/underflow in fixture and unit-test arithmetic. +unsigned-integer-overflow:tests/libxrpl/basics/RangeSet.cpp +unsigned-integer-overflow:test/app/Batch_test.cpp +unsigned-integer-overflow:test/app/Invariants_test.cpp +unsigned-integer-overflow:test/app/Loan_test.cpp +unsigned-integer-overflow:test/app/NFToken_test.cpp +unsigned-integer-overflow:test/app/OfferMPT_test.cpp +unsigned-integer-overflow:test/app/Offer_test.cpp +unsigned-integer-overflow:test/app/Path_test.cpp +unsigned-integer-overflow:test/jtx/impl/acctdelete.cpp +unsigned-integer-overflow:test/ledger/SkipList_test.cpp +unsigned-integer-overflow:test/rpc/Subscribe_test.cpp +signed-integer-overflow:test/basics/XRPAmount_test.cpp diff --git a/src/xrpld/rpc/handlers/ledger/Ledger.cpp b/src/xrpld/rpc/handlers/ledger/Ledger.cpp index 5938c8c9c5..23a97a5026 100644 --- a/src/xrpld/rpc/handlers/ledger/Ledger.cpp +++ b/src/xrpld/rpc/handlers/ledger/Ledger.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include namespace xrpl { @@ -349,13 +350,15 @@ doLedgerGrpc(RPC::GRPCContext& context) auto end = std::chrono::system_clock::now(); auto duration = std::chrono::duration_cast(end - begin).count() * 1.0; + // Guard the per-item rates: an empty ledger has zero objects and/or zero + // transactions, and dividing by zero is undefined for these doubles. + auto const numObjects = response.ledger_objects().objects_size(); + auto const numTxns = response.transactions_list().transactions_size(); + std::string const msPerObj = numObjects > 0 ? std::to_string(duration / numObjects) : "n/a"; + std::string const msPerTxn = numTxns > 0 ? std::to_string(duration / numTxns) : "n/a"; JLOG(context.j.warn()) << __func__ << " - Extract time = " << duration - << " - num objects = " << response.ledger_objects().objects_size() - << " - num txns = " << response.transactions_list().transactions_size() - << " - ms per obj " - << duration / response.ledger_objects().objects_size() - << " - ms per txn " - << duration / response.transactions_list().transactions_size(); + << " - num objects = " << numObjects << " - num txns = " << numTxns + << " - ms per obj " << msPerObj << " - ms per txn " << msPerTxn; return {response, status}; } From 875bcc530e2c778b3176e4193b0c3efbafbc462a Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Mon, 15 Jun 2026 16:47:11 -0400 Subject: [PATCH 095/135] Correct a couple of variable names --- src/test/jtx/impl/envconfig.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index ad8746facb..47db568ead 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -65,9 +65,9 @@ namespace jtx { std::unique_ptr online_delete(std::unique_ptr cfg, std::uint32_t deleteInterval) { - cfg->ledgerHistory = kDeleteInterval; + cfg->ledgerHistory = deleteInterval; auto& section = cfg->section(Sections::kNodeDatabase); - section.set(Keys::kOnlineDelete, std::to_string(kDeleteInterval)); + section.set(Keys::kOnlineDelete, std::to_string(deleteInterval)); return cfg; } From 9650fe8a6ecb8344d134e183ac74c15ac58dcc44 Mon Sep 17 00:00:00 2001 From: Jingchen Date: Tue, 26 May 2026 21:04:01 +0100 Subject: [PATCH 096/135] refactor: Use explicit types to help compiler --- .clang-tidy | 1 + src/xrpld/overlay/detail/PeerImp.cpp | 2 +- src/xrpld/overlay/detail/Tuning.h | 6 ++++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index b23d7ccbff..b0da673b13 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -153,6 +153,7 @@ Checks: "-*, readability-use-std-min-max " # --- +# bugprone-narrowing-conversions, # this will break a lot of code but we should enable it in the future because it can eliminate a lot of bugs # readability-inconsistent-declaration-parameter-name, # in this codebase this check will break a lot of arg names # readability-static-accessed-through-instance, # this check is probably unnecessary. it makes the code less readable # --- diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 325f8ba038..324e8e6b87 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -2032,7 +2032,7 @@ PeerImp::checkTracking(std::uint32_t validationSeq) void PeerImp::checkTracking(std::uint32_t seq1, std::uint32_t seq2) { - int const diff = std::max(seq1, seq2) - std::min(seq1, seq2); + std::uint32_t const diff = std::max(seq1, seq2) - std::min(seq1, seq2); if (diff < Tuning::kConvergedLedgerLimit) { diff --git a/src/xrpld/overlay/detail/Tuning.h b/src/xrpld/overlay/detail/Tuning.h index a0f57ec3d7..97ce3f5188 100644 --- a/src/xrpld/overlay/detail/Tuning.h +++ b/src/xrpld/overlay/detail/Tuning.h @@ -1,14 +1,16 @@ #pragma once +#include +#include namespace xrpl::Tuning { /** How many ledgers off a server can be and we will still consider it converged */ -static constexpr auto kConvergedLedgerLimit = 24; +static constexpr std::uint32_t kConvergedLedgerLimit = 24; /** How many ledgers off a server has to be before we consider it diverged */ -static constexpr auto kDivergedLedgerLimit = 128; +static constexpr std::uint32_t kDivergedLedgerLimit = 128; /** The soft cap on the number of ledger entries in a single reply. */ static constexpr auto kSoftMaxReplyNodes = 8192; From 2728e11809b3a62fc5e17af95a8f300364691c67 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 27 May 2026 01:12:30 +0100 Subject: [PATCH 097/135] fix: Set request size limits and differential pricing for get-object-by-hash calls --- src/test/overlay/TMGetObjectByHash_test.cpp | 18 +- src/xrpld/overlay/detail/PeerImp.cpp | 224 +++++++++++++++----- src/xrpld/overlay/detail/PeerImp.h | 67 ++++++ src/xrpld/overlay/detail/Tuning.h | 90 ++++++++ 4 files changed, 346 insertions(+), 53 deletions(-) diff --git a/src/test/overlay/TMGetObjectByHash_test.cpp b/src/test/overlay/TMGetObjectByHash_test.cpp index 961e1b7eb4..e579989181 100644 --- a/src/test/overlay/TMGetObjectByHash_test.cpp +++ b/src/test/overlay/TMGetObjectByHash_test.cpp @@ -100,6 +100,17 @@ class TMGetObjectByHash_test : public beast::unit_test::Suite return lastSentMessage_; } + // Synchronous test access to the JobQueue-dispatched processor. + // The production path runs this on JtLedgerReq; tests need a + // synchronous entry point to inspect the reply via send(). + // PeerImp::processGetObjectByHash is `protected` so the derived + // test subclass can call it directly. + void + runProcessGetObjectByHash(std::shared_ptr const& m) + { + processGetObjectByHash(m); + } + static void resetId() { @@ -179,6 +190,10 @@ class TMGetObjectByHash_test : public beast::unit_test::Suite /** * Test that reply is limited to hardMaxReplyNodes when more objects * are requested than the limit allows. + * + * `onMessage(TMGetObjectByHash)` dispatches the generic-query path + * to the JobQueue, so tests invoke the synchronous processor + * directly via `runProcessGetObjectByHash`. */ void testReplyLimit(size_t const numObjects, int const expectedReplySize) @@ -191,8 +206,7 @@ class TMGetObjectByHash_test : public beast::unit_test::Suite auto peer = createPeer(env); auto request = createRequest(numObjects, env); - // Call the onMessage handler - peer->onMessage(request); + peer->runProcessGetObjectByHash(request); // Verify that a reply was sent auto sentMessage = peer->getLastSentMessage(); diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 324e8e6b87..822ef05304 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -81,6 +82,7 @@ #include #include +#include #include #include #include @@ -393,11 +395,21 @@ void PeerImp::charge(Resource::Charge const& fee, std::string const& context) { if ((usage_.charge(fee, context) == Resource::Disposition::Drop) && - usage_.disconnect(pJournal_) && strand_.running_in_this_thread()) + usage_.disconnect(pJournal_)) { - // Sever the connection - overlay_.incPeerDisconnectCharges(); - fail("charge: Resources"); + // Idempotent: only the first worker to observe Drop counts the + // metric and posts fail(). Without the guard, several queued + // workers can all see Drop before fail() lands on the strand, + // overcounting peerDisconnectsCharges_ and posting duplicate + // shutdowns. fail(std::string const&) self-posts to strand_ + // when invoked off-strand. + bool expected = false; + if (chargeDisconnectFired_.compare_exchange_strong( + expected, true, std::memory_order_acq_rel)) + { + overlay_.incPeerDisconnectCharges(); + fail("charge: Resources"); + } } } @@ -2473,63 +2485,63 @@ PeerImp::onMessage(std::shared_ptr const& m) return; } - protocol::TMGetObjectByHash reply; - - reply.set_query(false); - - reply.set_type(packet.type()); - if (packet.has_ledgerhash()) { if (!stringIsUInt256Sized(packet.ledgerhash())) { - fee_.update(Resource::kFeeMalformedRequest, "ledger hash"); + JLOG(pJournal_.debug()) << "GetObj: malformed ledgerhash from peer " << id_; + fee_.update(Resource::kFeeMalformedRequest, "get object ledger hash"); return; } - - reply.set_ledgerhash(packet.ledgerhash()); } - - fee_.update(Resource::kFeeModerateBurdenPeer, " received a get object by hash request"); - - // This is a very minimal implementation - for (int i = 0; i < packet.objects_size(); ++i) + // Reject oversized requests before touching the NodeStore. + // The legitimate upper bound (InboundLedger::getNeededHashes()) + // is 8 hashes; anything beyond kHardMaxReplyNodes is non-conforming. + if (packet.objects_size() > Tuning::kHardMaxReplyNodes) { - auto const& obj = packet.objects(i); - if (obj.has_hash() && stringIsUInt256Sized(obj.hash())) - { - uint256 const hash = uint256::fromRaw(obj.hash()); - // VFALCO TODO Move this someplace more sensible so we dont - // need to inject the NodeStore interfaces. - std::uint32_t const seq{obj.has_ledgerseq() ? obj.ledgerseq() : 0}; - auto nodeObject{app_.getNodeStore().fetchNodeObject(hash, seq)}; - if (nodeObject) - { - protocol::TMIndexedObject& newObj = *reply.add_objects(); - newObj.set_hash(hash.begin(), hash.size()); - newObj.set_data(&nodeObject->getData().front(), nodeObject->getData().size()); - - if (obj.has_nodeid()) - newObj.set_index(obj.nodeid()); - if (obj.has_ledgerseq()) - newObj.set_ledgerseq(obj.ledgerseq()); - - // Check if by adding this object, reply has reached its - // limit - if (reply.objects_size() >= Tuning::kHardMaxReplyNodes) - { - fee_.update( - Resource::kFeeModerateBurdenPeer, - "Reply limit reached. Truncating reply."); - break; - } - } - } + JLOG(pJournal_.warn()) + << "GetObj: oversized request from peer " << id_ << " (" << packet.objects_size() + << " > " << Tuning::kHardMaxReplyNodes << ")"; + fee_.update(Resource::kFeeInvalidData, "oversized get object request"); + return; } - JLOG(pJournal_.trace()) << "GetObj: " << reply.objects_size() << " of " - << packet.objects_size(); - send(std::make_shared(reply, protocol::mtGET_OBJECTS)); + // Dispatch heavy synchronous NodeStore lookups off the peer's + // I/O strand and onto the bounded job queue, mirroring the pattern + // used by processLedgerRequest. + std::weak_ptr const weak = shared_from_this(); + bool const queued = app_.getJobQueue().addJob(JtLedgerReq, "RcvGetObjByHash", [weak, m]() { + auto peer = weak.lock(); + if (!peer) + return; + try + { + peer->processGetObjectByHash(m); + } + catch (std::exception const& e) + { + // Surface backend failures (NodeStore I/O, allocation) + // back through the resource model so a misbehaving peer + // is still accountable rather than silently dropped. + JLOG(peer->pJournal_.warn()) << "GetObj: handler threw: " << e.what(); + peer->charge(Resource::kFeeRequestNoReply, "get object handler exception"); + } + }); + if (!queued) + { + // The JobQueue is no longer accepting new work (typically + // because it is shutting down / has been joined). + JLOG(pJournal_.warn()) << "GetObj: job queue refused request from peer " << id_; + return; + } + + // Admission-time charge: a peer that floods enqueues would + // otherwise be billed only the trivial onMessageEnd fee per + // message until the JobQueue catches up, re-creating an + // uncharged DoS window. Charge the base burden up-front (after + // a successful enqueue); the per-lookup differential is added + // in the worker. + fee_.update(Resource::kFeeModerateBurdenPeer, "received a get object by hash request"); } else { @@ -2585,6 +2597,69 @@ PeerImp::onMessage(std::shared_ptr const& m) } } +void +PeerImp::processGetObjectByHash(std::shared_ptr const& m) +{ + protocol::TMGetObjectByHash const& packet = *m; + + protocol::TMGetObjectByHash reply; + reply.set_query(false); + reply.set_type(packet.type()); + + if (packet.has_ledgerhash()) + { + reply.set_ledgerhash(packet.ledgerhash()); + } + + // Defense in depth: caller (onMessage) already validates cheap + // structural properties of the request before dispatching here: + // - objects_size() <= kHardMaxReplyNodes (oversize gate) + // - if has_ledgerhash() then ledgerhash is uint256-sized + // The iteration cap below mirrors the oversize gate so this method + // remains safe if invoked directly by tests or future callers, and + // a peer cannot drive unbounded NodeStore lookups by sending + // non-existent hashes. + int const requested = packet.objects_size(); + int const iterLimit = std::min(requested, Tuning::kHardMaxReplyNodes); + + for (int i = 0; i < iterLimit; ++i) + { + auto const& obj = packet.objects(i); + if (!obj.has_hash() || !stringIsUInt256Sized(obj.hash())) + continue; + + uint256 const hash = uint256::fromRaw(obj.hash()); + // VFALCO TODO Move this someplace more sensible so we don't + // need to inject the NodeStore interfaces. + std::uint32_t const seq{obj.has_ledgerseq() ? obj.ledgerseq() : 0}; + auto const nodeObject = app_.getNodeStore().fetchNodeObject(hash, seq); + if (!nodeObject) + continue; + + protocol::TMIndexedObject& newObj = *reply.add_objects(); + newObj.set_hash(hash.begin(), hash.size()); + auto const& data = nodeObject->getData(); + newObj.set_data(data.data(), data.size()); + if (obj.has_nodeid()) + newObj.set_index(obj.nodeid()); + if (obj.has_ledgerseq()) + newObj.set_ledgerseq(obj.ledgerseq()); + } + + // Apply work-proportional charge. `charge()` posts the disconnect + // step (if any) back to strand_, so it is safe to call from this + // JobQueue worker thread. + charge( + // We pass `requested` directly here, instead of actual lookups done. Which could be + // std::min(packet.objects_size(), static_cast(Tuning::kHardMaxReplyNodes)); + // Because we want to charge as per the request size, to discourage large requests. + computeGetObjectByHashFee(requested, reply.objects_size()), + "processed get object by hash request"); + + JLOG(pJournal_.trace()) << "GetObj: " << reply.objects_size() << " of " << requested; + send(std::make_shared(reply, protocol::mtGET_OBJECTS)); +} + void PeerImp::onMessage(std::shared_ptr const& m) { @@ -3412,6 +3487,53 @@ PeerImp::processLedgerRequest(std::shared_ptr const& m) send(std::make_shared(ledgerData, protocol::mtLEDGER_DATA)); } +// Differential pricing helper. Returns only the *dynamic* component +// of the per-message charge — the base `kFeeModerateBurdenPeer` is +// applied at admission time in `onMessage(TMGetObjectByHash)` so a +// high traffic client pays for the message regardless of when (or +// whether) the worker runs. +// +// Dynamic charge model: +// +// billable = max(0, requested - kFreeObjectsPerRequest) +// missed = max(0, requested - found) +// billableMisses = min(missed, billable) // misses billed first +// billableHits = billable - billableMisses +// sizeBand = (requested > kBandMediumMax) ? kCostBandLarge +// : (requested > kBandSmallMax) ? kCostBandMedium +// : kCostBandSmall +// dynamic = billableHits * kCostPerLookupHit +// + billableMisses * kCostPerLookupMiss +// + sizeBand +// +// Misses are billed first against the billable budget because a node store +// seek dominates a cache hit and because invalid hashes are ~100% miss by construction. +Resource::Charge +PeerImp::computeGetObjectByHashFee(int const requested, int const found) +{ + int const billable = std::max(0, requested - static_cast(Tuning::kFreeObjectsPerRequest)); + // Clamp `missed` so a future caller passing found > requested cannot + // produce a negative value that flips the hits/misses split. + int const missed = std::max(0, requested - found); + int const billableMisses = std::min(missed, billable); + int const billableHits = billable - billableMisses; + + int sizeBand = Tuning::kCostBandSmall; + if (requested > Tuning::kBandMediumMax) + { + sizeBand = Tuning::kCostBandLarge; + } + else if (requested > Tuning::kBandSmallMax) + { + sizeBand = Tuning::kCostBandMedium; + } + + int const dynamic = (billableHits * Tuning::kCostPerLookupHit) + + (billableMisses * Tuning::kCostPerLookupMiss) + sizeBand; + + return Resource::Charge(dynamic, "GetObject differential"); +} + int PeerImp::getScore(bool haveItem) const { diff --git a/src/xrpld/overlay/detail/PeerImp.h b/src/xrpld/overlay/detail/PeerImp.h index f5d87371be..26d7e0a832 100644 --- a/src/xrpld/overlay/detail/PeerImp.h +++ b/src/xrpld/overlay/detail/PeerImp.h @@ -147,6 +147,12 @@ private: protocol::TMStatusChange lastStatus_; Resource::Consumer usage_; ChargeWithContext fee_; + + // One-shot guard so concurrent JobQueue workers cannot double-count + // the per-connection peer-disconnect-by-charge metric (and cannot + // post duplicate fail() calls) when several queued requests cross + // kDropThreshold before the first fail() lands on the strand. + std::atomic chargeDisconnectFired_{false}; std::shared_ptr const slot_; boost::beast::multi_buffer readBuffer_; http_request_type request_; @@ -624,6 +630,67 @@ private: void processLedgerRequest(std::shared_ptr const& m); + +protected: + // Kept `protected` so test subclasses (see + // TMGetObjectByHash_test) can drive the + // synchronous processor and the differential-pricing helper without + // routing through the JobQueue or going through `friend` plumbing. + // Production callers reach these members only via + // `onMessage(TMGetObjectByHash)` → JobQueue → `processGetObjectByHash`. + + /** Process a generic-query TMGetObjectByHash message. + + Dispatched from `onMessage(TMGetObjectByHash)` to the JobQueue + (`JtLedgerReq`) so synchronous NodeStore lookups do not block the + peer's I/O strand. Caps iteration at `Tuning::kHardMaxReplyNodes` + regardless of hit/miss outcome and applies differential pricing + via `computeGetObjectByHashFee()` after the fetch loop completes. + + @param m The protocol message containing requested object hashes. + */ + void + processGetObjectByHash(std::shared_ptr const& m); + + /** Compute the per-message resource charge for a TMGetObjectByHash + request based on how much work was actually performed. + + The charge has three components on top of the base + `Resource::kFeeModerateBurdenPeer`: + - per-hit lookup cost (cheap; usually served from cache) + - per-miss lookup cost (expensive node store seeks) + - request-size band surcharge (escalates abusive batch sizes) + + The first `Tuning::kFreeObjectsPerRequest` objects are free so + that legitimate `InboundLedger::getNeededHashes()` traffic + (at most 8 objects) is unaffected. + + @param requested Number of objects requested by the message. This + value is used for request-size pricing and may + exceed `Tuning::kHardMaxReplyNodes` when this + helper is called directly, even though processing + caps the iterations to `Tuning::kHardMaxReplyNodes`. + @param found Number of objects successfully returned in the + reply. + @return A `Resource::Charge` whose cost reflects the work performed. + */ + static Resource::Charge + computeGetObjectByHashFee(int const requested, int const found); + + /** Read-only accessor for the accumulated peer-message charge. + + Exposed at `protected` scope so test subclasses can verify the + oversized-request rejection path (Layer 1) without invoking the + full JobQueue handler. Production callers should never read this back — + the value is consumed by `charge()`/`disconnect()` internally. + + @return The current `Resource::Charge` accumulated on `fee_`. + */ + Resource::Charge + currentFeeCharge() const + { + return fee_.fee; + } }; //------------------------------------------------------------------------------ diff --git a/src/xrpld/overlay/detail/Tuning.h b/src/xrpld/overlay/detail/Tuning.h index 97ce3f5188..20a60d470e 100644 --- a/src/xrpld/overlay/detail/Tuning.h +++ b/src/xrpld/overlay/detail/Tuning.h @@ -1,4 +1,6 @@ #pragma once +#include + #include #include @@ -39,4 +41,92 @@ static constexpr auto kMaxQueryDepth = 3; /** Size of buffer used to read from the socket. */ constexpr std::size_t kReadBufferBytes = 16384; +/** TMGetObjectByHash differential pricing. + + Honest peers ask for at most 8 hashes per call (the header, or up to + 4 state + 4 tx hashes from `InboundLedger::getNeededHashes()`). The + free tier covers them at zero cost. Beyond that, each lookup is billed: + 'misses' cost much more than 'hits' because a miss does a node store seek + while a hit is usually served from cache. On top of that, a size-band + surcharge kicks in for larger requests so an attacker who crams a + single message with thousands of hashes blows past + `Resource::kDropThreshold` and gets disconnected. + + The numbers below are picked to keep three things true given + `kDropThreshold = 25000`: + + - Honest traffic (<= 8 objects per request) is free. + - A single all-miss request at `kHardMaxReplyNodes` (12288) costs + more than the drop threshold, so an attacker gets dropped in one + message. + - A peer spamming 1024-object hit-only requests gets dropped in + ~19 messages — fast enough to be useful, slow enough that an + honest peer momentarily sending oversized requests has time to + back off. */ + +/** How many objects a request can ask for before per-lookup billing + begins? + Twice the honest peak (8) so a peer that occasionally retries a hash + never trips pricing. Same value as `SHAMapInnerNode::kBranchFactor`; + that's a coincidence, not a requirement. */ +static constexpr auto kFreeObjectsPerRequest = 16; + +/** Cost of one cache-hit lookup. The unit; everything else is a + multiple of this. */ +static constexpr auto kCostPerLookupHit = 1; + +/** Cost of one node-store miss, in units of `kCostPerLookupHit`. + + A miss does a node store disk seek; a hit usually comes from cache. + The 8x ratio is an order-of-magnitude guess at the latency gap on + SSD-backed nodes, not a measured number. The math only requires this + to be at least 2 — any smaller and a full-miss request at the hard + cap wouldn't trip the drop threshold. 8 leaves headroom: if + `kDropThreshold` goes up or `kHardMaxReplyNodes` comes down, the + drop-on-attack property still holds without a code change. */ +static constexpr auto kCostPerLookupMiss = 8; + +/** Size-band surcharges. Whichever band a request's size falls into, + its surcharge is added once on top of the per-lookup cost. + + The job of the surcharge is to make crossing a band edge feel like + a step, not a slope. With these values, the cost roughly doubles or triples at each cliff: + + n=64: costs 48 => n=65 costs 149 (~3x jump) + n=1024: costs 1108 => n=1025 costs 2009 (~2x jump) + + The 10x step between medium and large mirrors the ~16x step + between the band edges (64 -> 1024) so the cliff feels comparable + at both scales. + */ +static constexpr auto kCostBandSmall = 0; +static constexpr auto kCostBandMedium = 100; +static constexpr auto kCostBandLarge = 1000; + +/** How many hashes per type an honest peer asks for at a time. + + Matches the `4` passed to `neededStateHashes(4)` and + `neededTxHashes(4)` in `InboundLedger::getNeededHashes()`. Kept here + instead of imported from the ledger module so overlay stays + self-contained; if that `4` ever changes, update this in lockstep or + the band thresholds below will start charging honest peers. */ +static constexpr auto kLegitHashesPerType = 4; + +/** Cutoffs that decide which size band a request falls into. + + A SHAMap inner node has 16 children; an honest peer asks for 4 + hashes per type. So: + + kBandSmallMax = 4 * 16 = 64 // one inner node's worth + kBandMediumMax = 4 * 16^2 = 1024 // a depth-2 subtree's worth + + A request up to 64 objects is small (no surcharge); up to 1024 is + medium; anything larger is large. The bounds are inclusive: a + request of exactly 64 is small, 65 is medium. Anything past 1024 is + well beyond what the honest sync path produces, so it's billed at + the large rate to drive attack-shaped traffic over the drop + threshold quickly. */ +static constexpr auto kBandSmallMax = kLegitHashesPerType * SHAMapInnerNode::kBranchFactor; +static constexpr auto kBandMediumMax = kBandSmallMax * SHAMapInnerNode::kBranchFactor; + } // namespace xrpl::Tuning From e29dc474b3d3eb10bb9ff3407cc378c71800124b Mon Sep 17 00:00:00 2001 From: Sergey Kuznetsov Date: Wed, 27 May 2026 14:42:48 +0100 Subject: [PATCH 098/135] refactor: Improve payment channel closing and returned error codes --- .../ledger/helpers/PaymentChannelHelpers.h | 36 +++++++++++++++++++ .../ledger/helpers/PaymentChannelHelpers.cpp | 30 ++++++++++++++++ .../payment_channel/PaymentChannelClaim.cpp | 28 +++++++++------ .../payment_channel/PaymentChannelFund.cpp | 34 +++++++++++------- src/test/app/PayChan_test.cpp | 5 ++- 5 files changed, 109 insertions(+), 24 deletions(-) diff --git a/include/xrpl/ledger/helpers/PaymentChannelHelpers.h b/include/xrpl/ledger/helpers/PaymentChannelHelpers.h index 24838f1331..4528431028 100644 --- a/include/xrpl/ledger/helpers/PaymentChannelHelpers.h +++ b/include/xrpl/ledger/helpers/PaymentChannelHelpers.h @@ -5,8 +5,21 @@ #include #include +#include +#include +#include + namespace xrpl { +/** Close a payment channel and return its remaining funds to the channel owner. + * + * @param slep The SLE for the PayChannel object to close. + * @param view The apply view in which ledger state modifications are made. + * @param key The ledger key identifying the PayChannel entry. + * @param j Journal used for fatal-level diagnostic messages. + * @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal + * fails; tefINTERNAL if the source account SLE cannot be found. + */ TER closeChannel( std::shared_ptr const& slep, @@ -14,4 +27,27 @@ closeChannel( uint256 const& key, beast::Journal j); +/** Add two uint32_t values with saturation at UINT32_MAX. + * + * @param rules The current ledger rules used to check amendment status. + * @param lhs Left-hand operand. + * @param rhs Right-hand operand. + * @return @p lhs + @p rhs, saturated at UINT32_MAX when the amendment + * is active. + */ +uint32_t +saturatingAdd(Rules const& rules, uint32_t const lhs, uint32_t const rhs); + +/** Determine whether a payment channel time field represents an expired time. + * + * @param view The apply view providing the parent close time and rules. + * @param timeField The optional expiry timestamp (seconds since the XRP + * Ledger epoch). If empty, the function returns false. + * @return @c true if @p timeField is set and the indicated time is + * in the past relative to the view's parent close time; + * @c false otherwise. + */ +bool +isChannelExpired(ApplyView const& view, std::optional timeField); + } // namespace xrpl diff --git a/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp b/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp index 31c206d85b..ba5ce50989 100644 --- a/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp +++ b/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp @@ -5,14 +5,20 @@ #include #include #include +#include #include #include +#include #include #include #include #include +#include +#include +#include #include +#include namespace xrpl { @@ -65,4 +71,28 @@ closeChannel( return tesSUCCESS; } +uint32_t +saturatingAdd(Rules const& rules, uint32_t const lhs, uint32_t const rhs) +{ + if (rules.enabled(fixCleanup3_2_0)) + { + static constexpr auto kUint32Max = + static_cast(std::numeric_limits::max()); + uint64_t const saturatedResult = std::min(uint64_t{lhs} + rhs, kUint32Max); + return static_cast(saturatedResult); + } + + return lhs + rhs; +} + +bool +isChannelExpired(ApplyView const& view, std::optional timeField) +{ + if (!timeField) + return false; + if (view.rules().enabled(fixCleanup3_2_0)) + return after(view.header().parentCloseTime, *timeField); + return view.header().parentCloseTime.time_since_epoch().count() >= *timeField; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp index cc99b8f62d..6a84d44746 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp @@ -43,6 +43,9 @@ PaymentChannelClaim::getFlagsMask(PreflightContext const&) NotTEC PaymentChannelClaim::preflight(PreflightContext const& ctx) { + if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfChannel] == beast::kZero) + return temMALFORMED; + auto const bal = ctx.tx[~sfBalance]; if (bal && (!isXRP(*bal) || *bal <= beast::kZero)) return temBAD_AMOUNT; @@ -117,12 +120,10 @@ PaymentChannelClaim::doApply() AccountID const txAccount = ctx_.tx[sfAccount]; auto const curExpiration = (*slep)[~sfExpiration]; + if (isChannelExpired(ctx_.view(), (*slep)[~sfCancelAfter]) || + isChannelExpired(ctx_.view(), curExpiration)) { - auto const cancelAfter = (*slep)[~sfCancelAfter]; - auto const closeTime = ctx_.view().header().parentCloseTime.time_since_epoch().count(); - if ((cancelAfter && closeTime >= *cancelAfter) || - (curExpiration && closeTime >= *curExpiration)) - return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View")); + return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View")); } if (txAccount != src && txAccount != dst) @@ -135,13 +136,19 @@ PaymentChannelClaim::doApply() auto const reqBalance = ctx_.tx[sfBalance].xrp(); if (txAccount == dst && !ctx_.tx[~sfSignature]) - return temBAD_SIGNATURE; + { + return ctx_.view().rules().enabled(fixCleanup3_2_0) ? TER{tecNO_PERMISSION} + : TER{temBAD_SIGNATURE}; + } if (ctx_.tx[~sfSignature]) { PublicKey const pk((*slep)[sfPublicKey]); if (ctx_.tx[sfPublicKey] != pk) - return temBAD_SIGNER; + { + return ctx_.view().rules().enabled(fixCleanup3_2_0) ? TER{tecNO_PERMISSION} + : TER{temBAD_SIGNER}; + } } if (reqBalance > chanFunds) @@ -185,9 +192,10 @@ PaymentChannelClaim::doApply() if (dst == txAccount || (*slep)[sfBalance] == (*slep)[sfAmount]) return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View")); - auto const settleExpiration = - ctx_.view().header().parentCloseTime.time_since_epoch().count() + - (*slep)[sfSettleDelay]; + auto const settleExpiration = saturatingAdd( + ctx_.view().rules(), + ctx_.view().header().parentCloseTime.time_since_epoch().count(), + (*slep)[sfSettleDelay]); if (!curExpiration || *curExpiration > settleExpiration) { diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp index 41906aa3da..dcd2797dd1 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,9 @@ PaymentChannelFund::makeTxConsequences(PreflightContext const& ctx) NotTEC PaymentChannelFund::preflight(PreflightContext const& ctx) { + if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfChannel] == beast::kZero) + return temMALFORMED; + if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::kZero)) return temBAD_AMOUNT; @@ -47,13 +51,12 @@ PaymentChannelFund::doApply() AccountID const src = (*slep)[sfAccount]; auto const txAccount = ctx_.tx[sfAccount]; - auto const expiration = (*slep)[~sfExpiration]; + auto const curExpiration = (*slep)[~sfExpiration]; + if (isChannelExpired(ctx_.view(), (*slep)[~sfCancelAfter]) || + isChannelExpired(ctx_.view(), curExpiration)) { - auto const cancelAfter = (*slep)[~sfCancelAfter]; - auto const closeTime = ctx_.view().header().parentCloseTime.time_since_epoch().count(); - if ((cancelAfter && closeTime >= *cancelAfter) || (expiration && closeTime >= *expiration)) - return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View")); + return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View")); } if (src != txAccount) @@ -62,16 +65,21 @@ PaymentChannelFund::doApply() return tecNO_PERMISSION; } - if (auto extend = ctx_.tx[~sfExpiration]) + if (auto newExpiration = ctx_.tx[~sfExpiration]) { - auto minExpiration = ctx_.view().header().parentCloseTime.time_since_epoch().count() + - (*slep)[sfSettleDelay]; - if (expiration && *expiration < minExpiration) - minExpiration = *expiration; + auto minExpiration = saturatingAdd( + ctx_.view().rules(), + ctx_.view().header().parentCloseTime.time_since_epoch().count(), + (*slep)[sfSettleDelay]); + if (curExpiration && *curExpiration < minExpiration) + minExpiration = *curExpiration; - if (*extend < minExpiration) - return temBAD_EXPIRATION; - (*slep)[~sfExpiration] = *extend; + if (*newExpiration < minExpiration) + { + return ctx_.view().rules().enabled(fixCleanup3_2_0) ? TER{tecNO_PERMISSION} + : TER{temBAD_EXPIRATION}; + } + (*slep)[~sfExpiration] = *newExpiration; ctx_.view().update(slep); } diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index b81afa830e..1e465aad91 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -1992,7 +1992,10 @@ public: run() override { using namespace test::jtx; - FeatureBitset const all{testableAmendments()}; + // fixCleanup3_2_0 changes payment-channel error codes (tem* -> tec*) + // and channel-closing semantics. This suite asserts the + // pre-amendment behavior, so run it with the amendment disabled. + FeatureBitset const all{testableAmendments() - fixCleanup3_2_0}; testWithFeats(all); testDepositAuthCreds(); testMetaAndOwnership(all - fixIncludeKeyletFields); From f98c251011e606e13a562598f2ee974d0a35b624 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 27 May 2026 16:18:18 +0100 Subject: [PATCH 099/135] refactor: Improve tracking of book (un)subscriptions --- .../scripts/levelization/results/ordering.txt | 2 - include/xrpl/ledger/BookListeners.h | 49 ----- include/xrpl/ledger/OrderBookDB.h | 50 ++--- include/xrpl/server/InfoSub.h | 84 ++++++++- include/xrpl/server/NetworkOPs.h | 13 ++ src/libxrpl/ledger/BookListeners.cpp | 55 ------ src/libxrpl/server/InfoSub.cpp | 87 +++++++-- src/xrpld/app/ledger/OrderBookDBImpl.cpp | 82 ++------ src/xrpld/app/ledger/OrderBookDBImpl.h | 19 -- src/xrpld/app/misc/NetworkOPs.cpp | 176 ++++++++++++++++-- .../rpc/handlers/subscribe/Unsubscribe.cpp | 14 +- 11 files changed, 384 insertions(+), 247 deletions(-) delete mode 100644 include/xrpl/ledger/BookListeners.h delete mode 100644 src/libxrpl/ledger/BookListeners.cpp diff --git a/.github/scripts/levelization/results/ordering.txt b/.github/scripts/levelization/results/ordering.txt index c2000d1768..7a5be869a5 100644 --- a/.github/scripts/levelization/results/ordering.txt +++ b/.github/scripts/levelization/results/ordering.txt @@ -12,7 +12,6 @@ libxrpl.ledger > xrpl.json libxrpl.ledger > xrpl.ledger libxrpl.ledger > xrpl.nodestore libxrpl.ledger > xrpl.protocol -libxrpl.ledger > xrpl.server libxrpl.ledger > xrpl.shamap libxrpl.net > xrpl.basics libxrpl.net > xrpl.net @@ -206,7 +205,6 @@ xrpl.core > xrpl.protocol xrpl.json > xrpl.basics xrpl.ledger > xrpl.basics xrpl.ledger > xrpl.protocol -xrpl.ledger > xrpl.server xrpl.ledger > xrpl.shamap xrpl.net > xrpl.basics xrpl.nodestore > xrpl.basics diff --git a/include/xrpl/ledger/BookListeners.h b/include/xrpl/ledger/BookListeners.h deleted file mode 100644 index 3b96aca680..0000000000 --- a/include/xrpl/ledger/BookListeners.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include -#include - -#include -#include - -namespace xrpl { - -/** Listen to public/subscribe messages from a book. */ -class BookListeners -{ -public: - using pointer = std::shared_ptr; - - BookListeners() = default; - - /** Add a new subscription for this book - */ - void - addSubscriber(InfoSub::ref sub); - - /** Stop publishing to a subscriber - */ - void - removeSubscriber(std::uint64_t sub); - - /** Publish a transaction to subscribers - - Publish a transaction to clients subscribed to changes on this book. - Uses havePublished to prevent sending duplicate transactions to clients - that have subscribed to multiple books. - - @param jvObj JSON transaction data to publish - @param havePublished InfoSub sequence numbers that have already - published this transaction. - - */ - void - publish(MultiApiJson const& jvObj, hash_set& havePublished); - -private: - std::recursive_mutex lock_; - - hash_map listeners_; -}; - -} // namespace xrpl diff --git a/include/xrpl/ledger/OrderBookDB.h b/include/xrpl/ledger/OrderBookDB.h index a0aee58e2a..a44183900c 100644 --- a/include/xrpl/ledger/OrderBookDB.h +++ b/include/xrpl/ledger/OrderBookDB.h @@ -1,11 +1,11 @@ #pragma once +#include +#include #include -#include #include #include #include -#include #include #include @@ -77,34 +77,24 @@ public: */ virtual bool isBookToXRP(Asset const& asset, std::optional const& domain = std::nullopt) = 0; - - /** - * Process a transaction for order book tracking. - * @param ledger The ledger the transaction was applied to - * @param alTx The transaction to process - * @param jvObj The JSON object of the transaction - */ - virtual void - processTxn( - std::shared_ptr const& ledger, - AcceptedLedgerTx const& alTx, - MultiApiJson const& jvObj) = 0; - - /** - * Get the book listeners for a book. - * @param book The book to get the listeners for - * @return The book listeners for the book - */ - virtual BookListeners::pointer - getBookListeners(Book const&) = 0; - - /** - * Create a new book listeners for a book. - * @param book The book to create the listeners for - * @return The new book listeners for the book - */ - virtual BookListeners::pointer - makeBookListeners(Book const&) = 0; }; +/** Extract the set of books affected by a transaction. + * + * Walks the transaction's metadata nodes and collects every order book + * whose offers were created, modified, or deleted. Used by NetworkOPs to + * fan transaction notifications out to book subscribers. + * + * @param alTx The accepted ledger transaction to inspect. + * @param j Journal used to log per-node parsing failures. Inspecting an + * offer node can throw if a required field is missing; in that + * case the bad node is skipped and a warn-level message is + * emitted via @p j. Other affected books in the same transaction + * are still returned. + * @return The set of books whose offers were created, modified, or + * deleted. May be empty for non-offer transactions. + */ +hash_set +affectedBooks(AcceptedLedgerTx const& alTx, beast::Journal const& j); + } // namespace xrpl diff --git a/include/xrpl/server/InfoSub.h b/include/xrpl/server/InfoSub.h index e93676a938..f316885fd6 100644 --- a/include/xrpl/server/InfoSub.h +++ b/include/xrpl/server/InfoSub.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -26,6 +27,19 @@ public: }; /** Manages a client's subscription to data feeds. + * + * An InfoSub holds a non-owning reference to its `Source` (typically the + * process-wide `NetworkOPsImp`). The destructor reaches back into the + * `Source` to remove this subscriber from every server-side subscription + * map. + * + * @note Lifetime contract: every `InfoSub` instance MUST be destroyed + * before the backing `Source`. NetworkOPsImp shutdown drops all + * subscriber strong refs before its own teardown to satisfy this. + * @note Thread-safety: per-instance state is guarded by `lock_`. The + * destructor reads tracking sets without taking `lock_` because + * the strong-pointer ref-count is zero at destruction time, so + * no other thread can be calling the public mutators. */ class InfoSub : public CountedObject { @@ -117,8 +131,43 @@ public: virtual bool subBook(ref ispListener, Book const&) = 0; + + /** + * Remove a book subscription for a live subscriber. + * + * Clears the book from the subscriber's own tracking set + * (InfoSub::bookSubscriptions_) and then removes the server-side + * entry from subBook_. Call this from RPC unsubscribe handlers. + * + * @param ispListener The subscriber requesting removal. + * @param book The order book to unsubscribe from. + * @return true if the entry was present and removed, false if the + * subscriber was not subscribed to @p book. + * + * @note Thread-safety: acquires subLock_ internally. + * @note Do NOT call from ~InfoSub(). Use unsubBookInternal instead + * to avoid a redundant write-back to bookSubscriptions_ on a + * partially-destroyed object. + */ virtual bool - unsubBook(std::uint64_t uListener, Book const&) = 0; + unsubBook(ref ispListener, Book const&) = 0; + + /** + * Remove a book subscription during InfoSub teardown. + * + * Removes only the server-side entry from subBook_. Does NOT touch + * InfoSub::bookSubscriptions_ because the InfoSub is being destroyed. + * Called by ~InfoSub() for each book in bookSubscriptions_. + * + * @param uListener The sequence number of the subscriber being torn down. + * @param book The order book entry to remove. + * @return true if the entry was present and removed, false otherwise + * (e.g., already removed by a concurrent RPC unsubscribe). + * + * @note Thread-safety: acquires subLock_ internally. + */ + virtual bool + unsubBookInternal(std::uint64_t uListener, Book const&) = 0; virtual bool subTransactions(ref ispListener) = 0; @@ -158,6 +207,13 @@ public: addRpcSub(std::string const& strUrl, ref rspEntry) = 0; virtual bool tryRemoveRpcSub(std::string const& strUrl) = 0; + + /** Journal used by InfoSub for diagnostics that occur after the + * owning subsystem (e.g. application-level Logs) is the only + * surviving sink — primarily destructor-time cleanup failures. + */ + [[nodiscard]] virtual beast::Journal const& + journal() const = 0; }; public: @@ -184,6 +240,31 @@ public: void deleteSubAccountInfo(AccountID const& account, bool rt); + /** Record that this subscriber is following @p book. + * + * Called by NetworkOPsImp::subBook so that ~InfoSub() can issue a + * matching unsubBook for every book this subscriber is tracking, + * keeping per-subscriber state symmetric with the server-side map. + * + * @param book The order book this subscriber has just subscribed to. + * @note Idempotent: re-inserting an already-tracked book is a no-op. + * @note Thread-safe: takes InfoSub::lock_. + */ + void + insertBookSubscription(Book const& book); + + /** Stop tracking @p book for this subscriber. + * + * Called by the unsubscribe RPC handler so that the book is not + * re-unsubscribed by ~InfoSub(). Pairs with insertBookSubscription. + * + * @param book The order book to forget. + * @note No-op if @p book was not previously inserted. + * @note Thread-safe: takes InfoSub::lock_. + */ + void + deleteBookSubscription(Book const& book); + // return false if already subscribed to this account bool insertSubAccountHistory(AccountID const& account); @@ -217,6 +298,7 @@ private: std::shared_ptr request_; std::uint64_t seq_; hash_set accountHistorySubscriptions_; + hash_set bookSubscriptions_; unsigned int apiVersion_ = 0; static int diff --git a/include/xrpl/server/NetworkOPs.h b/include/xrpl/server/NetworkOPs.h index e2aa17566e..785d808935 100644 --- a/include/xrpl/server/NetworkOPs.h +++ b/include/xrpl/server/NetworkOPs.h @@ -249,6 +249,19 @@ public: virtual void stateAccounting(json::Value& obj) = 0; + + /** Total number of (book, subscriber) entries currently tracked. + * + * Counts every weak_ptr stored across every book in subBook_, NOT the + * number of distinct subscribers and NOT the number of distinct + * books: a single subscriber following N books contributes N entries. + * + * @note Diagnostic accessor; intended for tests and operator visibility + * into per-book subscription state. The returned value is a + * snapshot under the subscription lock. + */ + virtual std::size_t + getBookSubscribersCount() = 0; }; } // namespace xrpl diff --git a/src/libxrpl/ledger/BookListeners.cpp b/src/libxrpl/ledger/BookListeners.cpp deleted file mode 100644 index d78da4c73e..0000000000 --- a/src/libxrpl/ledger/BookListeners.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include - -#include -#include -#include -#include - -#include -#include - -namespace xrpl { - -void -BookListeners::addSubscriber(InfoSub::ref sub) -{ - std::scoped_lock const sl(lock_); - listeners_[sub->getSeq()] = sub; -} - -void -BookListeners::removeSubscriber(std::uint64_t seq) -{ - std::scoped_lock const sl(lock_); - listeners_.erase(seq); -} - -void -BookListeners::publish(MultiApiJson const& jvObj, hash_set& havePublished) -{ - std::scoped_lock const sl(lock_); - auto it = listeners_.cbegin(); - - while (it != listeners_.cend()) - { - InfoSub::pointer p = it->second.lock(); - - if (p) - { - // Only publish jvObj if this is the first occurrence - if (havePublished.emplace(p->getSeq()).second) - { - jvObj.visit( - p->getApiVersion(), // - [&](json::Value const& jv) { p->send(jv, true); }); - } - ++it; - } - else - { - it = listeners_.erase(it); - } - } -} - -} // namespace xrpl diff --git a/src/libxrpl/server/InfoSub.cpp b/src/libxrpl/server/InfoSub.cpp index 87b48296a1..353c295856 100644 --- a/src/libxrpl/server/InfoSub.cpp +++ b/src/libxrpl/server/InfoSub.cpp @@ -1,15 +1,47 @@ #include +#include +#include #include #include +#include #include #include +#include #include #include namespace xrpl { +namespace { + +// Wraps a Source teardown call so that an exception from one cleanup +// step does not prevent the subsequent steps from running. Source methods +// acquire a lock and can throw std::system_error; a throw out of ~InfoSub +// during stack unwinding would terminate the process. Failures are +// reported through the Source's Journal so they reach the configured log +// sinks; JLOG itself cannot throw, so the noexcept guarantee holds. +template +void +safeUnsub(std::uint64_t seq, F&& f, beast::Journal j) noexcept +{ + try + { + f(); + } + catch (std::exception const& e) + { + JLOG(j.warn()) << "~InfoSub[seq=" << seq << "]: cleanup step failed: " << e.what(); + } + catch (...) + { + JLOG(j.warn()) << "~InfoSub[seq=" << seq << "]: cleanup step failed: unknown exception"; + } +} + +} // namespace + // This is the primary interface into the "client" portion of the program. // Code that wants to do normal operations on the network such as // creating and monitoring accounts, creating transactions, and so on @@ -32,25 +64,44 @@ InfoSub::InfoSub(Source& source, Consumer consumer) InfoSub::~InfoSub() { - source_.unsubTransactions(seq_); - source_.unsubRTTransactions(seq_); - source_.unsubLedger(seq_); - source_.unsubManifests(seq_); - source_.unsubServer(seq_); - source_.unsubValidations(seq_); - source_.unsubPeerStatus(seq_); - source_.unsubConsensus(seq_); + // Each Source teardown call below acquires a server-side lock and + // can throw. Wrap each independent call so partial failure does not + // skip the remaining teardown steps. + + auto const& j = source_.journal(); + + safeUnsub(seq_, [&] { source_.unsubTransactions(seq_); }, j); + safeUnsub(seq_, [&] { source_.unsubRTTransactions(seq_); }, j); + safeUnsub(seq_, [&] { source_.unsubLedger(seq_); }, j); + safeUnsub(seq_, [&] { source_.unsubManifests(seq_); }, j); + safeUnsub(seq_, [&] { source_.unsubServer(seq_); }, j); + safeUnsub(seq_, [&] { source_.unsubValidations(seq_); }, j); + safeUnsub(seq_, [&] { source_.unsubPeerStatus(seq_); }, j); + safeUnsub(seq_, [&] { source_.unsubConsensus(seq_); }, j); // Use the internal unsubscribe so that it won't call // back to us and modify its own parameter if (!realTimeSubscriptions_.empty()) - source_.unsubAccountInternal(seq_, realTimeSubscriptions_, true); + { + safeUnsub( + seq_, [&] { source_.unsubAccountInternal(seq_, realTimeSubscriptions_, true); }, j); + } if (!normalSubscriptions_.empty()) - source_.unsubAccountInternal(seq_, normalSubscriptions_, false); + { + safeUnsub( + seq_, [&] { source_.unsubAccountInternal(seq_, normalSubscriptions_, false); }, j); + } for (auto const& account : accountHistorySubscriptions_) - source_.unsubAccountHistoryInternal(seq_, account, false); + { + safeUnsub(seq_, [&] { source_.unsubAccountHistoryInternal(seq_, account, false); }, j); + } + + for (auto const& book : bookSubscriptions_) + { + safeUnsub(seq_, [&] { source_.unsubBookInternal(seq_, book); }, j); + } } Resource::Consumer& @@ -114,6 +165,20 @@ InfoSub::deleteSubAccountHistory(AccountID const& account) accountHistorySubscriptions_.erase(account); } +void +InfoSub::insertBookSubscription(Book const& book) +{ + std::scoped_lock const sl(lock_); + bookSubscriptions_.insert(book); +} + +void +InfoSub::deleteBookSubscription(Book const& book) +{ + std::scoped_lock const sl(lock_); + bookSubscriptions_.erase(book); +} + void InfoSub::clearRequest() { diff --git a/src/xrpld/app/ledger/OrderBookDBImpl.cpp b/src/xrpld/app/ledger/OrderBookDBImpl.cpp index 658ec4ea7a..1e474ef949 100644 --- a/src/xrpld/app/ledger/OrderBookDBImpl.cpp +++ b/src/xrpld/app/ledger/OrderBookDBImpl.cpp @@ -9,19 +9,17 @@ #include #include #include -#include #include +#include #include #include #include #include -#include #include #include #include #include -#include #include #include #include @@ -307,55 +305,10 @@ OrderBookDBImpl::isBookToXRP(Asset const& asset, std::optional const& do return xrpBooks_.contains(asset); } -BookListeners::pointer -OrderBookDBImpl::makeBookListeners(Book const& book) +hash_set +affectedBooks(AcceptedLedgerTx const& alTx, beast::Journal const& j) { - std::scoped_lock const sl(lock_); - auto ret = getBookListeners(book); - - if (!ret) - { - ret = std::make_shared(); - - listeners_[book] = ret; - XRPL_ASSERT( - getBookListeners(book) == ret, - "xrpl::OrderBookDB::makeBookListeners : result roundtrip " - "lookup"); - } - - return ret; -} - -BookListeners::pointer -OrderBookDBImpl::getBookListeners(Book const& book) -{ - BookListeners::pointer ret; - std::scoped_lock const sl(lock_); - - auto it0 = listeners_.find(book); - if (it0 != listeners_.end()) - ret = it0->second; - - return ret; -} - -// Based on the meta, send the meta to the streams that are listening. -// We need to determine which streams a given meta effects. -void -OrderBookDBImpl::processTxn( - std::shared_ptr const& ledger, - AcceptedLedgerTx const& alTx, - MultiApiJson const& jvObj) -{ - std::scoped_lock const sl(lock_); - - // For this particular transaction, maintain the set of unique - // subscriptions that have already published it. This prevents sending - // the transaction multiple times if it touches multiple ltOFFER - // entries for the same book, or if it touches multiple books and a - // single client has subscribed to those books. - hash_set havePublished; + hash_set result; for (auto const& node : alTx.getMeta().getNodes()) { @@ -363,40 +316,41 @@ OrderBookDBImpl::processTxn( { if (node.getFieldU16(sfLedgerEntryType) == ltOFFER) { - auto process = [&, this](SField const& field) { + auto extract = [&](SField const& field) { if (auto data = dynamic_cast(node.peekAtPField(field)); data && data->isFieldPresent(sfTakerPays) && data->isFieldPresent(sfTakerGets)) { - auto listeners = getBookListeners( - {data->getFieldAmount(sfTakerGets).asset(), - data->getFieldAmount(sfTakerPays).asset(), - (*data)[~sfDomainID]}); - if (listeners) - listeners->publish(jvObj, havePublished); + result.emplace( + data->getFieldAmount(sfTakerGets).asset(), + data->getFieldAmount(sfTakerPays).asset(), + (*data)[~sfDomainID]); } }; - // We need a field that contains the TakerGets and TakerPays - // parameters. if (node.getFName() == sfModifiedNode) { - process(sfPreviousFields); + extract(sfPreviousFields); } else if (node.getFName() == sfCreatedNode) { - process(sfNewFields); + extract(sfNewFields); } else if (node.getFName() == sfDeletedNode) { - process(sfFinalFields); + extract(sfFinalFields); } } } catch (std::exception const& ex) { - JLOG(j_.info()) << "processTxn: field not found (" << ex.what() << ")"; + // The bad node is skipped; other affected books in the same + // transaction are still returned. Logged at warn so a malformed + // offer node is visible to operators. + JLOG(j.warn()) << "affectedBooks: skipping malformed node (" << ex.what() << ")"; } } + + return result; } } // namespace xrpl diff --git a/src/xrpld/app/ledger/OrderBookDBImpl.h b/src/xrpld/app/ledger/OrderBookDBImpl.h index a50f512441..a68f63c043 100644 --- a/src/xrpld/app/ledger/OrderBookDBImpl.h +++ b/src/xrpld/app/ledger/OrderBookDBImpl.h @@ -1,10 +1,7 @@ #pragma once #include -#include -#include #include -#include #include #include @@ -54,18 +51,6 @@ public: void update(std::shared_ptr const& ledger); - // see if this txn effects any orderbook - void - processTxn( - std::shared_ptr const& ledger, - AcceptedLedgerTx const& alTx, - MultiApiJson const& jvObj) override; - - BookListeners::pointer - getBookListeners(Book const&) override; - BookListeners::pointer - makeBookListeners(Book const&) override; - private: std::reference_wrapper registry_; int const pathSearchMax_; @@ -84,10 +69,6 @@ private: std::recursive_mutex lock_; - using BookToListenersMap = hash_map; - - BookToListenersMap listeners_; - std::atomic seq_; beast::Journal const j_; diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 12c79b821c..e59840befc 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -527,6 +527,8 @@ public: updateLocalTx(ReadView const& view) override; std::size_t getLocalTxCount() override; + std::size_t + getBookSubscribersCount() override; // // Monitoring: publisher side. @@ -586,7 +588,9 @@ public: bool subBook(InfoSub::ref ispListener, Book const&) override; bool - unsubBook(std::uint64_t uListener, Book const&) override; + unsubBook(InfoSub::ref ispListener, Book const&) override; + bool + unsubBookInternal(std::uint64_t uListener, Book const&) override; bool subManifests(InfoSub::ref ispListener) override; @@ -629,6 +633,12 @@ public: bool tryRemoveRpcSub(std::string const& strUrl) override; + beast::Journal const& + journal() const override + { + return journal_; + } + void stop() override { @@ -705,6 +715,32 @@ private: AcceptedLedgerTx const& transaction, bool last); + /** + * Fan transaction notifications out to all book subscribers. + * + * Extracts the set of order books affected by @p transaction, then + * delivers @p jvObj to every live subscriber of those books. + * + * Uses a two-pass design to keep subLock_ hold time short: + * 1. Under subLock_, collect strong InfoSub pointers for all live + * subscribers and prune any expired weak_ptrs encountered. + * 2. Release subLock_, then call send() on each collected pointer. + * + * @param transaction The accepted ledger transaction to inspect. + * @param jvObj JSON representation of the transaction to deliver. + * + * @note Thread-safety: acquires subLock_ for the collection pass only. + * send() is intentionally called outside the lock to avoid blocking + * all other sub/unsub/publish paths while I/O is in progress. + * @note Contention: subLock_ is shared with all other subscription types. + * On high-throughput nodes processing multi-hop payments that touch + * many offer nodes, this pass holds subLock_ longer than the old + * per-book BookListeners locks did. This is an accepted trade-off + * for lock-domain simplicity. + */ + void + pubBookTransaction(AcceptedLedgerTx const& transaction, MultiApiJson const& jvObj); + void pubProposedAccountTransaction( std::shared_ptr const& ledger, @@ -802,8 +838,19 @@ private: LedgerMaster& ledgerMaster_; + /** Maps each order book to its current set of subscribers. + * Outer key: the Book (currency pair + optional domain). + * Inner key: InfoSub::seq (unique per connection). + * Inner value: weak_ptr so that a dropped connection does not prevent + * the InfoSub from being destroyed; expired entries are pruned lazily + * by pubBookTransaction and eagerly by unsubBookInternal (~InfoSub path). + * Guarded by subLock_. + */ + using SubBookMapType = hash_map; + SubInfoMapType subAccount_; SubInfoMapType subRTAccount_; + SubBookMapType subBook_; ///< Guarded by subLock_. subRpcMapType rpcSubMap_; @@ -3191,6 +3238,16 @@ NetworkOPsImp::getLocalTxCount() return localTX_->size(); } +std::size_t +NetworkOPsImp::getBookSubscribersCount() +{ + std::scoped_lock const sl(subLock_); + std::size_t total = 0; + for (auto const& [_, subs] : subBook_) + total += subs.size(); + return total; +} + // This routine should only be used to publish accepted or validated // transactions. MultiApiJson @@ -3352,11 +3409,89 @@ NetworkOPsImp::pubValidatedTransaction( } if (transaction.getResult() == tesSUCCESS) - registry_.get().getOrderBookDB().processTxn(ledger, transaction, jvObj); + pubBookTransaction(transaction, jvObj); pubAccountTransaction(ledger, transaction, last); } +void +NetworkOPsImp::pubBookTransaction(AcceptedLedgerTx const& alTx, MultiApiJson const& jvObj) +{ + auto const books = affectedBooks(alTx, journal_); + if (books.empty()) + return; + + // Two-pass design: + // + // 1. Under subLock_, walk subBook_, collect a strong pointer for each + // unique listener (and prune any expired weak_ptrs we encounter). + // 2. Release subLock_, then send to each collected listener. + // + // Reasoning: + // * send() can be slow / blocking, so holding subLock_ across it would + // stall every other sub/unsub/pub path on this server (see the matching + // TODO above pubServer at line ~2275). + // * A strong pointer destructed while subLock_ is held risks running + // ~InfoSub() in-line, which re-enters unsubBook() and mutates the very + // subBook_/SubMapType being iterated -> dangling iterator UB. + // + // Releasing subLock_ before any InfoSub::pointer can decay solves both. + // ~InfoSub() reacquires subLock_ via unsubBook() on its own and serializes + // safely with concurrent traffic. + + std::vector listeners; + hash_set seen; + + // Sized for the common case where every affected book has at most + // one subscriber. Multi-subscriber books trigger reallocation, but + // that is rare and the upper-bound estimate (sum of per-book sizes) + // would itself require walking subBook_ twice. + listeners.reserve(books.size()); + seen.reserve(books.size()); + + { + std::scoped_lock const sl(subLock_); + + for (auto const& book : books) + { + auto it = subBook_.find(book); + if (it == subBook_.end()) + continue; + + for (auto sit = it->second.begin(); sit != it->second.end();) + { + if (auto p = sit->second.lock()) + { + // Defensive: subBook_ entries are normally cleared by + // ~InfoSub() -> unsubBook(), so we rarely see expired + // weak_ptrs here. The else branch covers the narrow race + // where the last strong ref is dropped between insertion + // and our lock() call. + if (seen.emplace(p->getSeq()).second) + listeners.emplace_back(std::move(p)); + ++sit; + } + else + { + JLOG(journal_.debug()) + << "pubBookTransaction: pruning expired weak_ptr for seq=" << sit->first; + sit = it->second.erase(sit); + } + } + + if (it->second.empty()) + subBook_.erase(it); + } + } + + for (auto const& p : listeners) + { + jvObj.visit(p->getApiVersion(), [&](json::Value const& jv) { p->send(jv, true); }); + } + // listeners destructs here, outside subLock_; ~InfoSub (if any fires) + // will reacquire subLock_ via unsubBook with no iterator hazard. +} + void NetworkOPsImp::pubAccountTransaction( std::shared_ptr const& ledger, @@ -4010,26 +4145,39 @@ NetworkOPsImp::unsubAccountHistoryInternal( bool NetworkOPsImp::subBook(InfoSub::ref isrListener, Book const& book) { - if (auto listeners = registry_.get().getOrderBookDB().makeBookListeners(book)) + // Server-side insert first, then InfoSub bookkeeping. If the InfoSub-side + // insert throws, the orphan in subBook_ is cleared by the expired-weak_ptr + // prune in pubBookTransaction. With the reverse ordering, ~InfoSub would + // call unsubBookInternal for a key that was never inserted server-side. { - listeners->addSubscriber(isrListener); - } - else - { - // LCOV_EXCL_START - UNREACHABLE("xrpl::NetworkOPsImp::subBook : null book listeners"); - // LCOV_EXCL_STOP + std::scoped_lock const sl(subLock_); + subBook_[book].try_emplace(isrListener->getSeq(), isrListener); } + isrListener->insertBookSubscription(book); return true; } bool -NetworkOPsImp::unsubBook(std::uint64_t uSeq, Book const& book) +NetworkOPsImp::unsubBook(InfoSub::ref isrListener, Book const& book) { - if (auto listeners = registry_.get().getOrderBookDB().getBookListeners(book)) - listeners->removeSubscriber(uSeq); + // Mirrors unsubAccount: clear the per-subscriber tracking set first so + // ~InfoSub does not re-issue an unsubBookInternal for a book the caller + // already removed, then erase the server-side entry. + isrListener->deleteBookSubscription(book); + return unsubBookInternal(isrListener->getSeq(), book); +} - return true; +bool +NetworkOPsImp::unsubBookInternal(std::uint64_t uSeq, Book const& book) +{ + std::scoped_lock const sl(subLock_); + auto it = subBook_.find(book); + if (it == subBook_.end()) + return false; + bool const erased = it->second.erase(uSeq) != 0u; + if (it->second.empty()) + subBook_.erase(it); + return erased; } std::uint32_t diff --git a/src/xrpld/rpc/handlers/subscribe/Unsubscribe.cpp b/src/xrpld/rpc/handlers/subscribe/Unsubscribe.cpp index 36dae615b3..af42af2a55 100644 --- a/src/xrpld/rpc/handlers/subscribe/Unsubscribe.cpp +++ b/src/xrpld/rpc/handlers/subscribe/Unsubscribe.cpp @@ -186,13 +186,23 @@ doUnsubscribe(RPC::JsonContext& context) book.domain = domain; } - context.netOps.unsubBook(ispSub->getSeq(), book); + if (!context.netOps.unsubBook(ispSub, book)) + { + JLOG(context.j.debug()) + << "doUnsubscribe: book not subscribed (no-op for seq=" << ispSub->getSeq() + << ")"; + } // both_sides is deprecated. if ((jv.isMember(jss::both) && jv[jss::both].asBool()) || (jv.isMember(jss::both_sides) && jv[jss::both_sides].asBool())) { - context.netOps.unsubBook(ispSub->getSeq(), reversed(book)); + if (!context.netOps.unsubBook(ispSub, reversed(book))) + { + JLOG(context.j.debug()) + << "doUnsubscribe: reversed book not subscribed (no-op for seq=" + << ispSub->getSeq() << ")"; + } } } } From 82ee5b7556456cf9f9b78bed91d24e1b72eeea50 Mon Sep 17 00:00:00 2001 From: Bart Date: Mon, 1 Jun 2026 16:04:45 -0400 Subject: [PATCH 100/135] refactor: Handle int and uint API versions separately --- include/xrpl/protocol/ApiVersion.h | 33 ++++++++++++++++++------------ 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/include/xrpl/protocol/ApiVersion.h b/include/xrpl/protocol/ApiVersion.h index 345049b377..10b7571641 100644 --- a/include/xrpl/protocol/ApiVersion.h +++ b/include/xrpl/protocol/ApiVersion.h @@ -102,25 +102,32 @@ getAPIVersionNumber(json::Value const& jv, bool betaEnabled) json::Value const maxVersion( betaEnabled ? RPC::kApiBetaVersion : RPC::kApiMaximumSupportedVersion); - if (jv.isObject()) + if (!jv.isObject() || !jv.isMember(jss::api_version)) + return RPC::kApiVersionIfUnspecified; + + try { - if (jv.isMember(jss::api_version)) + auto const& rawVersion = jv[jss::api_version]; + switch (rawVersion.type()) { - auto const specifiedVersion = jv[jss::api_version]; - if (!specifiedVersion.isInt() && !specifiedVersion.isUInt()) - { - return RPC::kApiInvalidVersion; + case json::ValueType::Int: + if (rawVersion.asInt() < 0) + return RPC::kApiInvalidVersion; + [[fallthrough]]; + case json::ValueType::UInt: { + auto const apiVersion = rawVersion.asUInt(); + if (apiVersion < kMinVersion || apiVersion > maxVersion) + return RPC::kApiInvalidVersion; + return apiVersion; } - auto const specifiedVersionInt = specifiedVersion.asInt(); - if (specifiedVersionInt < kMinVersion || specifiedVersionInt > maxVersion) - { + default: return RPC::kApiInvalidVersion; - } - return specifiedVersionInt; } } - - return RPC::kApiVersionIfUnspecified; + catch (...) + { + return RPC::kApiInvalidVersion; + } } } // namespace RPC From 5a25c9188bd5a6ed6aafbe5e8c3be25d53da5ad3 Mon Sep 17 00:00:00 2001 From: Bart Date: Mon, 1 Jun 2026 16:53:43 -0400 Subject: [PATCH 101/135] release: Bump version to 3.2.0-rc4 --- 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 7de1862dfc..e81be00920 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -23,7 +23,7 @@ namespace { //------------------------------------------------------------------------------ // clang-format off // NOLINTNEXTLINE(readability-identifier-naming) -char const* const versionString = "3.2.0-rc3" +char const* const versionString = "3.2.0-rc4" // clang-format on ; From 47b06ecd17c3b9f2852ebd15987a85401965117e Mon Sep 17 00:00:00 2001 From: Sergey Kuznetsov Date: Wed, 3 Jun 2026 15:32:43 +0100 Subject: [PATCH 102/135] refactor: Use rocksdb includes only when it is available --- include/xrpl/basics/rocksdb.h | 29 ------------------- src/libxrpl/nodestore/ManagerImp.cpp | 4 +++ .../nodestore/backend/RocksDBFactory.cpp | 26 ++++++++--------- src/test/nodestore/import_test.cpp | 11 +++++-- 4 files changed, 24 insertions(+), 46 deletions(-) delete mode 100644 include/xrpl/basics/rocksdb.h diff --git a/include/xrpl/basics/rocksdb.h b/include/xrpl/basics/rocksdb.h deleted file mode 100644 index 3d468b0f1b..0000000000 --- a/include/xrpl/basics/rocksdb.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#if XRPL_ROCKSDB_AVAILABLE -// #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#endif diff --git a/src/libxrpl/nodestore/ManagerImp.cpp b/src/libxrpl/nodestore/ManagerImp.cpp index 5cefbfd357..1d87ff9199 100644 --- a/src/libxrpl/nodestore/ManagerImp.cpp +++ b/src/libxrpl/nodestore/ManagerImp.cpp @@ -44,8 +44,10 @@ ManagerImp::missingBackend() // the Factory classes is an undefined behaviour. void registerNuDBFactory(Manager& manager); +#if XRPL_ROCKSDB_AVAILABLE void registerRocksDBFactory(Manager& manager); +#endif void registerNullFactory(Manager& manager); void @@ -54,7 +56,9 @@ registerMemoryFactory(Manager& manager); ManagerImp::ManagerImp() { registerNuDBFactory(*this); +#if XRPL_ROCKSDB_AVAILABLE registerRocksDBFactory(*this); +#endif registerNullFactory(*this); registerMemoryFactory(*this); } diff --git a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp index d2c193888c..e7767cd4ac 100644 --- a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp +++ b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp @@ -1,12 +1,22 @@ +#if XRPL_ROCKSDB_AVAILABLE #include +#include #include #include +#include +#include +#include #include #include #include +#include +#include #include #include #include +#include +#include +#include #include #include @@ -24,26 +34,14 @@ #include #include +#include #include #include #include +#include #include #include -#if XRPL_ROCKSDB_AVAILABLE -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - namespace xrpl::NodeStore { class RocksDBEnv : public rocksdb::EnvWrapper diff --git a/src/test/nodestore/import_test.cpp b/src/test/nodestore/import_test.cpp index a80b5ccc93..d8c4a96713 100644 --- a/src/test/nodestore/import_test.cpp +++ b/src/test/nodestore/import_test.cpp @@ -21,11 +21,16 @@ #include #include #include + +#if XRPL_ROCKSDB_AVAILABLE + #include #include #include #include +#endif + #include #include #include @@ -297,17 +302,17 @@ public: auto const args = parseArgs(arg()); bool usage = args.empty(); - if (!usage && args.find("from") == args.end()) + if (!usage && !args.contains("from")) { log << "Missing parameter: from"; usage = true; } - if (!usage && args.find("to") == args.end()) + if (!usage && !args.contains("to")) { log << "Missing parameter: to"; usage = true; } - if (!usage && args.find("buffer") == args.end()) + if (!usage && !args.contains("buffer")) { log << "Missing parameter: buffer"; usage = true; From 8e3eabc398f1400dec1a9c4c63d9b2dabc0ad78d Mon Sep 17 00:00:00 2001 From: Michael Legleux Date: Wed, 3 Jun 2026 11:59:18 -0700 Subject: [PATCH 103/135] refactor: Remove auto-update script and update RPM version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Update RPM version scheme; remove auto-update script; service hardening - **RPM version scheme**: pre-releases now use `~` in the `Version` field instead of the `0..` `Release`-field hack. Matches Debian's `~` convention, so RPM and DEB version strings are symmetric. Requires rpm ≥ 4.10 (RHEL 9 ships 4.17). Before/after for a pre-release build: ``` # before xrpld-3.2.0-0.1.rc3+202606011647.d4cb68d5.el9.x86_64.rpm # after (symmetric with DEB) xrpld-3.2.0~rc2+202606010139.7679a310-1.el9.x86_64.rpm xrpld_3.2.0~rc2+202606010139.7679a310-1_amd64.deb ``` - **Auto-update removed**: `update-xrpld`, `update-xrpld.service`, and `update-xrpld.timer` deleted. The `50-xrpld.preset` `disable` line for the timer is dropped too. - **Service hardening** (two new `[Service]` directives in `xrpld.service`): - `CapabilityBoundingSet=CAP_NET_BIND_SERVICE` — drops every Linux capability except `CAP_NET_BIND_SERVICE`, capping the privilege ceiling to least-privilege while still letting operators bind ports <1024 (e.g. WS/HTTPS on 443). - `SystemCallArchitectures=native` — restricts the service to the native syscall ABI, blocking alternate-ABI (32-bit/x32) syscalls used to evade seccomp filtering. - [ ] Build RPM from a pre-release version (e.g. `3.2.0-b1`) and confirm `rpm -qi` shows `Version: 3.2.0~b1`, `Release: 1` - [ ] Confirm `3.2.0~b1` sorts before `3.2.0` via `rpmvercmp` - [ ] Install package and confirm no `update-xrpld*` units appear in `systemctl list-unit-files` - [ ] Confirm `systemctl show xrpld` reflects the new `CapabilityBoundingSet` and `SystemCallArchitectures` * fix: Track tmpfiles-created directories in RPM %files as %ghost --- package/README.md | 1 - package/build_pkg.sh | 33 +++--- package/debian/rules | 2 - package/debian/xrpld.docs | 1 - package/rpm/xrpld.spec | 19 +--- package/shared/50-xrpld.preset | 2 - package/shared/update-xrpld | 152 ---------------------------- package/shared/update-xrpld.service | 16 --- package/shared/update-xrpld.timer | 10 -- package/shared/xrpld.service | 2 + 10 files changed, 19 insertions(+), 219 deletions(-) delete mode 100755 package/shared/update-xrpld delete mode 100644 package/shared/update-xrpld.service delete mode 100644 package/shared/update-xrpld.timer diff --git a/package/README.md b/package/README.md index 867ca273b4..d440f70fb8 100644 --- a/package/README.md +++ b/package/README.md @@ -15,7 +15,6 @@ package/ xrpld.sysusers sysusers.d config (used by both RPM and DEB) xrpld.tmpfiles tmpfiles.d config (used by both RPM and DEB) xrpld.logrotate logrotate config (installed to /etc/logrotate.d/xrpld) - update-xrpld auto-update script (installed to /usr/libexec/xrpld/, run by update-xrpld.timer) ``` ## Prerequisites diff --git a/package/build_pkg.sh b/package/build_pkg.sh index f2c2c63c12..e2ec8fee3d 100755 --- a/package/build_pkg.sh +++ b/package/build_pkg.sh @@ -114,10 +114,11 @@ VER_BASE="${VERSION%%-*}" VER_SUFFIX="${VERSION#*-}" [[ "${VER_SUFFIX}" == "${VERSION}" ]] && VER_SUFFIX="" -# Reject multi-segment suffixes (e.g. "beta-1", "rc1-15-gabc123"). The RPM -# Release field forbids '-', and the convention here is single-token suffixes -# like b1 or rc2. Fail early with a clear message rather than letting either -# rpmbuild blow up or silently mangling dashes into dots. +# Reject multi-segment suffixes (e.g. "beta-1", "rc1-15-gabc123"). Neither an +# RPM Version nor a Debian upstream version may contain '-' (it's the NVR / +# version-revision separator), and the convention here is single-token +# suffixes like b1 or rc2. Fail early with a clear message rather than letting +# the package tooling blow up or silently mangle dashes. if [[ "${VER_SUFFIX}" == *-* ]]; then echo "build_pkg.sh: multi-segment pre-release in VERSION='${VERSION}' (suffix '${VER_SUFFIX}')." >&2 echo "Use single-token suffixes like 3.2.0-b1 or 3.2.0-rc2." >&2 @@ -142,9 +143,6 @@ stage_common() { cp "${SHARED}/xrpld.sysusers" "${dest}/xrpld.sysusers" cp "${SHARED}/xrpld.tmpfiles" "${dest}/xrpld.tmpfiles" cp "${SHARED}/xrpld.logrotate" "${dest}/xrpld.logrotate" - cp "${SHARED}/update-xrpld" "${dest}/update-xrpld" - cp "${SHARED}/update-xrpld.service" "${dest}/update-xrpld.service" - cp "${SHARED}/update-xrpld.timer" "${dest}/update-xrpld.timer" cp "${SHARED}/50-xrpld.preset" "${dest}/50-xrpld.preset" } @@ -156,20 +154,18 @@ build_rpm() { cp "${SRC_DIR}/package/rpm/xrpld.spec" "${topdir}/SPECS/xrpld.spec" stage_common "${topdir}/SOURCES" - # RPM Version can't contain '-'. A pre-release goes in Release with a - # leading "0." so 3.2.0-b1 sorts before the final 3.2.0-. - # The order is "0.." (e.g. 0.1.b6) — the Fedora/EPEL - # convention. Reversing to "0.." (e.g. 0.b6.1) breaks - # rpmvercmp against the former because numeric segments outrank alphabetic - # ones, so "0.1.b5" would sort newer than "0.b6.1". - local rpm_release="${PKG_RELEASE}" - [[ -n "${VER_SUFFIX}" ]] && rpm_release="0.${PKG_RELEASE}.${VER_SUFFIX}" + # Pre-releases use the modern rpm '~' convention (rpm >= 4.10): the suffix + # goes in Version (e.g. 3.2.0~b1), which rpmvercmp sorts *before* the final + # 3.2.0 — identical semantics to Debian's '~'. Release is just the package + # release number. This replaces the older "0.." Release + # hack and keeps the RPM and DEB version strings symmetric. + local rpm_version="${VER_BASE}${VER_SUFFIX:+~${VER_SUFFIX}}" set -x rpmbuild -bb \ --define "_topdir ${topdir}" \ - --define "xrpld_version ${VER_BASE}" \ - --define "xrpld_release ${rpm_release}" \ + --define "xrpld_version ${rpm_version}" \ + --define "xrpld_release ${PKG_RELEASE}" \ "${topdir}/SPECS/xrpld.spec" } @@ -181,13 +177,10 @@ build_deb() { stage_common "${staging}" cp -r "${DEBIAN_DIR}" "${staging}/debian" - # Debhelper auto-discovers these only from debian/. cp "${staging}/xrpld.service" "${staging}/debian/xrpld.service" cp "${staging}/xrpld.sysusers" "${staging}/debian/xrpld.sysusers" cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles" cp "${staging}/xrpld.logrotate" "${staging}/debian/xrpld.logrotate" - cp "${staging}/update-xrpld.service" "${staging}/debian/xrpld.update-xrpld.service" - cp "${staging}/update-xrpld.timer" "${staging}/debian/xrpld.update-xrpld.timer" # Debian '~' marks a pre-release; 3.2.0~b1 sorts before 3.2.0. local deb_full_version="${VER_BASE}${VER_SUFFIX:+~${VER_SUFFIX}}-${PKG_RELEASE}" diff --git a/package/debian/rules b/package/debian/rules index cd94da7e5b..16574bca3f 100644 --- a/package/debian/rules +++ b/package/debian/rules @@ -10,7 +10,6 @@ override_dh_auto_configure override_dh_auto_build override_dh_auto_test: override_dh_installsystemd: dh_installsystemd --no-stop-on-upgrade xrpld.service - dh_installsystemd --name=update-xrpld --no-start update-xrpld.service update-xrpld.timer execute_before_dh_installtmpfiles: dh_installsysusers @@ -21,7 +20,6 @@ override_dh_install: install -D -m 0755 xrpld debian/xrpld/usr/bin/xrpld install -D -m 0644 xrpld.cfg debian/xrpld/etc/xrpld/xrpld.cfg install -D -m 0644 validators.txt debian/xrpld/etc/xrpld/validators.txt - install -D -m 0755 update-xrpld debian/xrpld/usr/libexec/xrpld/update-xrpld override_dh_dwz: @: diff --git a/package/debian/xrpld.docs b/package/debian/xrpld.docs index 1217b6db43..b43bf86b50 100644 --- a/package/debian/xrpld.docs +++ b/package/debian/xrpld.docs @@ -1,2 +1 @@ README.md -LICENSE.md diff --git a/package/rpm/xrpld.spec b/package/rpm/xrpld.spec index 4933c724f7..5595fd0d8d 100644 --- a/package/rpm/xrpld.spec +++ b/package/rpm/xrpld.spec @@ -35,8 +35,6 @@ install -Dm0644 %{_sourcedir}/validators.txt %{buildroot}%{_sysconfdir}/%{ # systemd units, sysusers, tmpfiles, preset install -Dm0644 %{_sourcedir}/xrpld.service %{buildroot}%{_unitdir}/xrpld.service -install -Dm0644 %{_sourcedir}/update-xrpld.service %{buildroot}%{_unitdir}/update-xrpld.service -install -Dm0644 %{_sourcedir}/update-xrpld.timer %{buildroot}%{_unitdir}/update-xrpld.timer install -Dm0644 %{_sourcedir}/xrpld.sysusers %{buildroot}%{_sysusersdir}/xrpld.conf install -Dm0644 %{_sourcedir}/xrpld.tmpfiles %{buildroot}%{_tmpfilesdir}/xrpld.conf install -Dm0644 %{_sourcedir}/50-xrpld.preset %{buildroot}%{_presetdir}/50-xrpld.preset @@ -44,9 +42,6 @@ install -Dm0644 %{_sourcedir}/50-xrpld.preset %{buildroot}%{_presetdir}/50- # Logrotate config install -Dm0644 %{_sourcedir}/xrpld.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/%{name} -# Update helper -install -Dm0755 %{_sourcedir}/update-xrpld %{buildroot}%{_libexecdir}/%{name}/update-xrpld - # Docs install -Dm0644 %{_sourcedir}/LICENSE.md %{buildroot}%{_docdir}/%{name}/LICENSE.md install -Dm0644 %{_sourcedir}/README.md %{buildroot}%{_docdir}/%{name}/README.md @@ -61,10 +56,10 @@ ln -s %{_bindir}/%{name} %{buildroot}/usr/local/bin/rippled %post systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || : -%systemd_post xrpld.service update-xrpld.timer +%systemd_post xrpld.service %preun -%systemd_preun xrpld.service update-xrpld.timer +%systemd_preun xrpld.service %postun %systemd_postun_with_restart xrpld.service @@ -74,7 +69,6 @@ systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || : %doc %{_docdir}/%{name}/README.md %dir %{_sysconfdir}/%{name} -%dir %{_libexecdir}/%{name} %{_bindir}/%{name} @@ -82,18 +76,13 @@ systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || : %config(noreplace) %{_sysconfdir}/%{name}/validators.txt %config(noreplace) %{_sysconfdir}/logrotate.d/%{name} -%{_libexecdir}/%{name}/update-xrpld %{_unitdir}/xrpld.service -%{_unitdir}/update-xrpld.service -%{_unitdir}/update-xrpld.timer %{_presetdir}/50-xrpld.preset %{_sysusersdir}/xrpld.conf %{_tmpfilesdir}/xrpld.conf - -%ghost %dir /var/lib/%{name} -%ghost %dir /var/log/%{name} - +%ghost %dir /var/lib/xrpld +%ghost %dir /var/log/xrpld # Legacy compatibility for pre-FHS package layouts. # TODO: remove after rippled fully deprecated. diff --git a/package/shared/50-xrpld.preset b/package/shared/50-xrpld.preset index 6264e00131..bfbcd56577 100644 --- a/package/shared/50-xrpld.preset +++ b/package/shared/50-xrpld.preset @@ -1,4 +1,2 @@ # /usr/lib/systemd/system-preset/50-xrpld.preset enable xrpld.service -# Don't enable automatic updates -disable update-xrpld.timer diff --git a/package/shared/update-xrpld b/package/shared/update-xrpld deleted file mode 100755 index 4bd4db2538..0000000000 --- a/package/shared/update-xrpld +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Optional: also write logs to a legacy file in addition to journald. -# By default, this script logs to systemd/journald, viewable via: -# journalctl -t update-xrpld -# -# Uncomment the line below if you need a flat file for compatibility with -# external tooling, manual inspection, or environments where journald logs -# are not persisted or easily accessible. -# -# Note: This duplicates all output (stdout/stderr) to both journald and the file. -# It is generally not needed on modern systems and may cause log file growth -# if left enabled long-term. -# -# Requires /var/log/xrpld/ to exist and be writable by the service (root). -# -# exec > >(tee -a /var/log/xrpld/update.log) 2>&1 - -PATH=/usr/sbin:/usr/bin:/sbin:/bin - -PKG_NAME=${PKG_NAME:-xrpld} - -log() { - # If running under systemd/journald, let it handle timestamps. - if [[ -n "${JOURNAL_STREAM:-}" ]]; then - printf '%s\n' "$*" - else - printf '%s %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*" - fi -} - -require_root() { - if [[ ${EUID:-$(id -u)} -ne 0 ]]; then - log "RESULT: failed reason=not-root" - exit 1 - fi -} - -get_installed_version() { - if command -v dpkg-query >/dev/null 2>&1; then - dpkg-query -W -f='${Version}' "$PKG_NAME" 2>/dev/null || printf 'unknown' - elif command -v rpm >/dev/null 2>&1; then - rpm -q --qf '%{VERSION}-%{RELEASE}' "$PKG_NAME" 2>/dev/null || printf 'unknown' - else - printf 'unknown' - fi -} - -trap 'log "RESULT: failed reason=script-error exit_code=$?"' ERR - -apt_can_update() { - apt-get update -qq - apt-get -s --only-upgrade install "$PKG_NAME" 2>/dev/null | grep -q "^Inst ${PKG_NAME}\b" -} - -apt_apply_update() { - DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \ - -o Dpkg::Options::="--force-confdef" \ - -o Dpkg::Options::="--force-confold" \ - "$PKG_NAME" -} - -get_rpm_pm() { - if command -v dnf >/dev/null 2>&1; then - printf 'dnf\n' - elif command -v yum >/dev/null 2>&1; then - printf 'yum\n' - else - return 1 - fi -} - -rpm_refresh_metadata() { - local pm=$1 - if [[ "$pm" == "dnf" ]]; then - dnf makecache --refresh -q >/dev/null - else - yum clean expire-cache -q >/dev/null - fi -} - -rpm_can_update() { - local pm=$1 - - rpm_refresh_metadata "$pm" - local rc=0 - set +e - "$pm" check-update -q "$PKG_NAME" >/dev/null 2>&1 - rc=$? - set -e - - if [[ $rc -eq 100 ]]; then - return 0 - elif [[ $rc -eq 0 ]]; then - return 1 - else - log "$pm check-update failed with exit code ${rc}." - exit 1 - fi -} - -rpm_apply_update() { - local pm=$1 - "$pm" update -y "$PKG_NAME" -} - -restart_service() { - # Preserve the operator's prior service state: if xrpld was intentionally - # stopped before the update, don't bring it back up just because the - # auto-update timer fired. - if systemctl is-active --quiet "${PKG_NAME}.service"; then - systemctl restart "${PKG_NAME}.service" - log "${PKG_NAME} service restarted successfully." - else - log "${PKG_NAME} service was not running; skipping restart to preserve prior state." - fi -} - -main() { - require_root - if command -v apt-get >/dev/null 2>&1; then - log "Checking for ${PKG_NAME} updates via apt" - if apt_can_update; then - log "Update available; installing." - apt_apply_update - restart_service - log "RESULT: updated ${PKG_NAME}=$(get_installed_version)" - else - log "RESULT: no-update ${PKG_NAME}=$(get_installed_version)" - fi - return - fi - - local rpm_pm="" - if rpm_pm="$(get_rpm_pm)"; then - log "Checking for ${PKG_NAME} updates via ${rpm_pm}" - if rpm_can_update "$rpm_pm"; then - log "Update available; installing" - rpm_apply_update "$rpm_pm" - restart_service - log "RESULT: updated ${PKG_NAME}=$(get_installed_version)" - else - log "RESULT: no-update ${PKG_NAME}=$(get_installed_version)" - fi - return - fi - log "RESULT: failed reason=no-package-manager" - exit 1 -} - -main "$@" diff --git a/package/shared/update-xrpld.service b/package/shared/update-xrpld.service deleted file mode 100644 index a964ca5482..0000000000 --- a/package/shared/update-xrpld.service +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=Check for and install xrpld package updates -Documentation=man:systemd.service(5) -Wants=network-online.target -After=network-online.target -ConditionPathExists=/usr/libexec/xrpld/update-xrpld -ConditionPathExists=/usr/bin/xrpld - -[Service] -Type=oneshot -ExecStart=/usr/bin/flock -n /run/lock/xrpld-update.lock /usr/libexec/xrpld/update-xrpld -StandardOutput=journal -StandardError=journal -SyslogIdentifier=update-xrpld -TimeoutStartSec=30min -PrivateTmp=true diff --git a/package/shared/update-xrpld.timer b/package/shared/update-xrpld.timer deleted file mode 100644 index 21dabf1400..0000000000 --- a/package/shared/update-xrpld.timer +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=Daily xrpld update check - -[Timer] -OnCalendar=*-*-* 00:00:00 -RandomizedDelaySec=24h -Persistent=true - -[Install] -WantedBy=timers.target diff --git a/package/shared/xrpld.service b/package/shared/xrpld.service index 72b6cc9938..0dd4e3a791 100644 --- a/package/shared/xrpld.service +++ b/package/shared/xrpld.service @@ -17,6 +17,8 @@ PrivateTmp=true User=xrpld Group=xrpld LimitNOFILE=65536 +CapabilityBoundingSet=CAP_NET_BIND_SERVICE +SystemCallArchitectures=native [Install] WantedBy=multi-user.target From e833e8884d01bdf5f1088215a4709c5b3d3b4aae Mon Sep 17 00:00:00 2001 From: Valentin Balaschenko <13349202+vlntb@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:29:09 +0100 Subject: [PATCH 104/135] refactor: Revert "Explicitly trim the heap after cache sweeps (#6022)" --- src/xrpld/app/main/Application.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index 508dfc8590..15522b9806 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -43,7 +43,6 @@ #include #include #include -#include #include #include #include @@ -1088,8 +1087,6 @@ public: << "; size after: " << cachedSLEs_.size(); } - mallocTrim("doSweep", journal_); - // Set timer to do another sweep later. setSweepTimer(); } From fded06652ad3d85977e36af903548425e8ff8094 Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Wed, 3 Jun 2026 15:57:34 -0400 Subject: [PATCH 105/135] fix: Add zero NFT Offer ID check for NFTokenCancelOffer --- .../tx/transactors/nft/NFTokenCancelOffer.cpp | 11 +++++++++-- src/test/app/NFToken_test.cpp | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp index 924dc49269..e00cd53685 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -23,8 +24,14 @@ namespace xrpl { NotTEC NFTokenCancelOffer::preflight(PreflightContext const& ctx) { - if (auto const& ids = ctx.tx[sfNFTokenOffers]; - ids.empty() || (ids.size() > kMaxTokenOfferCancelCount)) + auto const& offerIds = ctx.tx[sfNFTokenOffers]; + + if (offerIds.empty() || (offerIds.size() > kMaxTokenOfferCancelCount)) + return temMALFORMED; + + // Zero offer IDs cannot be passed as ledger entry keys. + if (ctx.rules.enabled(fixCleanup3_2_0) && + std::ranges::any_of(offerIds, [](uint256 const& id) { return id.isZero(); })) return temMALFORMED; // In order to prevent unnecessarily overlarge transactions, we diff --git a/src/test/app/NFToken_test.cpp b/src/test/app/NFToken_test.cpp index ebd470ec92..269bc72c53 100644 --- a/src/test/app/NFToken_test.cpp +++ b/src/test/app/NFToken_test.cpp @@ -892,6 +892,25 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite BEAST_EXPECT(ownerCount(env, buyer) == 1); } + // Only test this with fixCleanup3_2_0 enabled. Without the fix, + // an assert-enabled build can crash when Ledger::read() receives + // a zero-key offer ID. + if (features[fixCleanup3_2_0]) + { + // Zero is not a valid offer ID. + env(token::cancelOffer(buyer, {uint256{}}), Ter(temMALFORMED)); + env.close(); + BEAST_EXPECT(ownerCount(env, buyer) == 1); + + // List of offer IDs containing zero is invalid. + // craftedIndex is not a valid offer index but it is not zero. + auto const craftedIndex = keylet::nftoffer(gw, env.seq(gw)).key; + env(token::cancelOffer(buyer, {buyerOfferIndex, uint256{}, craftedIndex}), + Ter(temMALFORMED)); + env.close(); + BEAST_EXPECT(ownerCount(env, buyer) == 1); + } + // List of tokens to delete is too long. { std::vector const offers(kMaxTokenOfferCancelCount + 1, buyerOfferIndex); From 61dae6f79249a829b9204ba323250324c36ea273 Mon Sep 17 00:00:00 2001 From: Bart Date: Wed, 3 Jun 2026 16:18:08 -0400 Subject: [PATCH 106/135] release: Bump version to 3.2.0-rc5 --- 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 e81be00920..ab83236bb5 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -23,7 +23,7 @@ namespace { //------------------------------------------------------------------------------ // clang-format off // NOLINTNEXTLINE(readability-identifier-naming) -char const* const versionString = "3.2.0-rc4" +char const* const versionString = "3.2.0-rc5" // clang-format on ; From 96d0563ea644ba5bb28e08e37dc7752d05a204fb Mon Sep 17 00:00:00 2001 From: Michael Legleux Date: Thu, 4 Jun 2026 16:23:33 -0700 Subject: [PATCH 107/135] fix: Adjust xrpld systemd service --- package/shared/xrpld.service | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/package/shared/xrpld.service b/package/shared/xrpld.service index 0dd4e3a791..7f3496acbb 100644 --- a/package/shared/xrpld.service +++ b/package/shared/xrpld.service @@ -2,14 +2,15 @@ Description=XRP Ledger Daemon After=network-online.target Wants=network-online.target -StartLimitIntervalSec=300 +StartLimitIntervalSec=5min StartLimitBurst=5 [Service] Type=simple ExecStart=/usr/bin/xrpld --net --silent --conf /etc/xrpld/xrpld.cfg -Restart=always +Restart=on-failure RestartSec=5s +TimeoutStopSec=5min NoNewPrivileges=true ProtectSystem=full ProtectHome=true @@ -17,8 +18,11 @@ PrivateTmp=true User=xrpld Group=xrpld LimitNOFILE=65536 -CapabilityBoundingSet=CAP_NET_BIND_SERVICE SystemCallArchitectures=native +# Uncomment both lines to allow xrpld to bind to privileged ports (<1024) +#CapabilityBoundingSet=CAP_NET_BIND_SERVICE +#AmbientCapabilities=CAP_NET_BIND_SERVICE + [Install] WantedBy=multi-user.target From e5785c4fcbf45eb9fb80c87a2b19831badd791c1 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Fri, 5 Jun 2026 15:57:23 -0400 Subject: [PATCH 108/135] fix: Fix Number comparison operator --- include/xrpl/basics/Number.h | 35 ++++++----- src/test/basics/Number_test.cpp | 104 ++++++++++++++++++++++++++++++-- 2 files changed, 121 insertions(+), 18 deletions(-) diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index 93bef82a8c..cee0c45355 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -408,33 +408,40 @@ public: } friend constexpr bool - operator<(Number const& x, Number const& y) noexcept + operator<(Number const& l, Number const& r) noexcept { + bool const lneg = l.negative_; + bool const rneg = r.negative_; + // If the two amounts have different signs (zero is treated as positive) // then the comparison is true iff the left is negative. - bool const lneg = x.negative_; - bool const rneg = y.negative_; - if (lneg != rneg) return lneg; - // Both have same sign and the left is zero: the right must be - // greater than 0. - if (x.mantissa_ == 0) - return y.mantissa_ > 0; + // Both have same sign and the left is zero: both must be non-negative. + // If the right is greater than 0, then it is larger, so the comparison is true. + if (l.mantissa_ == 0) + return r.mantissa_ > 0; - // Both have same sign, the right is zero and the left is non-zero. - if (y.mantissa_ == 0) + // Both have same sign, the right is zero and the left is non-zero, so the left must be + // positive, and thus is larger, so the comparison is false. + if (r.mantissa_ == 0) return false; // Both have the same sign, compare by exponents: - if (x.exponent_ > y.exponent_) + if (l.exponent_ > r.exponent_) return lneg; - if (x.exponent_ < y.exponent_) + if (l.exponent_ < r.exponent_) return !lneg; - // If equal exponents, compare mantissas - return x.mantissa_ < y.mantissa_; + // If equal signs and exponents, compare mantissas. + if (lneg) + { + // If negative, the operator is reversed. + return l.mantissa_ > r.mantissa_; + } + + return l.mantissa_ < r.mantissa_; } /** Return the sign of the amount */ diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index 81019970ad..f4bd1c9d66 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,8 @@ #include #include #include +#include +#include namespace xrpl { @@ -1386,10 +1389,103 @@ public: testRelationals() { testcase << "test_relationals " << to_string(Number::getMantissaScale()); - BEAST_EXPECT(!(Number{100} < Number{10})); - BEAST_EXPECT(Number{100} > Number{10}); - BEAST_EXPECT(Number{100} >= Number{10}); - BEAST_EXPECT(!(Number{100} <= Number{10})); + + { + auto test = [this](auto const& nums) { + BEAST_EXPECT(std::ranges::is_sorted(nums)); + + for (auto iter1 = nums.begin(); iter1 != nums.end(); ++iter1) + { + auto iter2 = iter1; + for (++iter2; iter2 != nums.end(); ++iter2) + { + Number const& smaller = *iter1; + Number const& larger = *iter2; + std::stringstream ss; + ss << smaller << " < " << larger; + auto const str = ss.str(); + + // The ==/!= operators use a completely different code path than <, etc. + // This helps detect a breakage in one but not the other. It also helps + // verify that the values are being ordered correctly. + BEAST_EXPECTS(smaller != larger, str + " (!=)"); + BEAST_EXPECTS(!(smaller == larger), str + " (==)"); + + // true results using operator< and derived operators + BEAST_EXPECTS(smaller < larger, str + " (<)"); + BEAST_EXPECTS(larger > smaller, str + " (>)"); + BEAST_EXPECTS(larger >= smaller, str + " (>=)"); + BEAST_EXPECTS(smaller <= larger, str + " (<=)"); + + // false results using operator< and derived operators + BEAST_EXPECTS(!(larger < smaller), str + " (! <)"); + BEAST_EXPECTS(!(smaller > larger), str + " (! >)"); + BEAST_EXPECTS(!(smaller >= larger), str + " (! >=)"); + BEAST_EXPECTS(!(larger <= smaller), str + " (! <=)"); + } + } + }; + + auto const intNums = [this]() { + // Inequality test cases are built from a list of sorted integers + auto const values = + std::to_array({-100, -50, -20, -10, -1, 0, 1, 10, 20, 50, 100}); + // Check this list is sorted before converting it to Numbers. + // That way if any of the other tests fail, we know it's because of code and not the + // source data. + BEAST_EXPECT(std::ranges::is_sorted(values)); + + std::vector result; + result.reserve(values.size()); + for (auto const v : values) + result.emplace_back(v); + return result; + }(); + + auto const otherNums = std::to_array({ + Number{-5, 100}, + Number{-1, 100}, + Number{-7, -10}, + Number{-2, -10}, + Number{0}, + Number{2, -10}, + Number{7, -10}, + Number{1, 100}, + Number{5, 100}, + }); + + test(intNums); + test(otherNums); + } + + { + // Equality test cases are . Number will be compared against itself + using Case = std::pair; + auto const c = std::to_array({ + {700, __LINE__}, + {50, __LINE__}, + {1, __LINE__}, + {0, __LINE__}, + {-1, __LINE__}, + {-30, __LINE__}, + {-600, __LINE__}, + }); + for (auto const& [n, line] : c) + { + auto const str = to_string(n); + + // NOLINTBEGIN(misc-redundant-expression) Explicitly testing operators with + // equivalent values + expect(n == n, str + " ==", __FILE__, line); + expect(!(n != n), str + " !=", __FILE__, line); + + expect(!(n < n), str + " < ", __FILE__, line); + expect(!(n > n), str + " >", __FILE__, line); + expect(n >= n, str + " >=", __FILE__, line); + expect(n <= n, str + " <=", __FILE__, line); + // NOLINTEND(misc-redundant-expression) + } + } } void From 781ef175c9e0826f12da0e8d9557eeb68c5c516a Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Fri, 5 Jun 2026 22:23:41 +0200 Subject: [PATCH 109/135] perf: Dispatch "hasInvalidAmount()" on type tag instead of dynamic_cast --- src/libxrpl/protocol/STAmount.cpp | 32 ++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index 1ba9cd042f..ddb2be29cf 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -1250,16 +1250,34 @@ hasInvalidAmount(STBase const& field, int depth, beast::Journal j) return true; } - if (auto const amount = dynamic_cast(&field)) - return !isLegalMPT(*amount) || !isLegalNet(*amount); + // Dispatch on the serialized type tag rather than RTTI: this is on the invariant-checking path + // and a dynamic_cast chain over every field of every modified entry is measurably expensive. + // The object-like tags below all denote STObject subclasses (STLedgerEntry, STTx), so the + // downcast is sound; nested fields are only ever plain STI_OBJECT / STI_ARRAY containers. + // safeDowncast keeps a dynamic_cast validity assert in debug builds while compiling to + // static_cast in release. + switch (field.getSType()) + { + case STI_AMOUNT: { + auto const& amount = safeDowncast(field); + return !isLegalMPT(amount) || !isLegalNet(amount); + } - if (auto const object = dynamic_cast(&field)) - return hasInvalidAmount(*object, depth + 1, j); + case STI_OBJECT: + case STI_LEDGERENTRY: + case STI_TRANSACTION: + return hasInvalidAmount(safeDowncast(field), depth + 1, j); - if (auto const array = dynamic_cast(&field)) - return hasInvalidAmount(*array, depth + 1, j); + case STI_ARRAY: + return hasInvalidAmount(safeDowncast(field), depth + 1, j); - return false; + default: { + XRPL_ASSERT( + dynamic_cast(&field) == nullptr, + "xrpl::hasInvalidAmount : valid object type"); + return false; + } + } } bool From ed5f13481a444380e7821d98d2ce73d316a77744 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Fri, 5 Jun 2026 22:49:49 +0200 Subject: [PATCH 110/135] fix: Disable transaction invariants --- src/libxrpl/tx/Transactor.cpp | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index 28fa059902..1aa7567c6c 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -1173,21 +1173,17 @@ Transactor::checkTransactionInvariants(TER result, XRPAmount fee) [[nodiscard]] TER Transactor::checkInvariants(TER result, XRPAmount fee) { - // Transaction invariants first (more specific). These check post-conditions of the specific - // transaction. If these fail, the transaction's core logic is wrong. - auto const txResult = checkTransactionInvariants(result, fee); - - // Protocol invariants second (broader). These check properties that must hold regardless of - // transaction type. - auto const protoResult = ctx_.checkInvariants(result, fee); - - // Fail if either check failed. tef (fatal) takes priority over tec. - if (protoResult == tefINVARIANT_FAILED) - return tefINVARIANT_FAILED; - if (txResult == tecINVARIANT_FAILED || protoResult == tecINVARIANT_FAILED) - return tecINVARIANT_FAILED; - - return result; + /* + * DISABLED for 3.2.0 — Must be re-introduced for 3.3.0 + * + * Transaction invariants are disabled due to a performance regression: + * the two-pass design (transaction-specific invariants + protocol invariants) + * iterates over modified ledger entries twice per transaction. + * + * Until resolved, only protocol invariants are checked (delegated to ctx_). + * This is safe because all transaction invariants in 3.2.0 are no-ops. + */ + return ctx_.checkInvariants(result, fee); } //------------------------------------------------------------------------------ ApplyResult From 0ac8e6cf1ebf14d9ff1e6df700ce603fae507163 Mon Sep 17 00:00:00 2001 From: Bart Date: Fri, 5 Jun 2026 17:30:12 -0400 Subject: [PATCH 111/135] release: Bump version to 3.2.0-rc6 --- 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 ab83236bb5..b704f50813 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -23,7 +23,7 @@ namespace { //------------------------------------------------------------------------------ // clang-format off // NOLINTNEXTLINE(readability-identifier-naming) -char const* const versionString = "3.2.0-rc5" +char const* const versionString = "3.2.0-rc6" // clang-format on ; From 6b63f0ff614e090c8a782d63591e4b035c7715ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 05:37:50 -0400 Subject: [PATCH 112/135] ci: [DEPENDABOT] bump codecov/codecov-action from 6.0.1 to 7.0.0 (#7426) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/reusable-build-test-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 31457bb892..4163e17779 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -324,7 +324,7 @@ jobs: - name: Upload coverage report if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }} - uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: disable_search: true disable_telem: true From 3c43f4614f87965298773279ff5b85d4c56c637b Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 15 Jun 2026 22:19:38 +0100 Subject: [PATCH 113/135] release: Bump version to 3.2.0 --- 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 b704f50813..c488bb20de 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -23,7 +23,7 @@ namespace { //------------------------------------------------------------------------------ // clang-format off // NOLINTNEXTLINE(readability-identifier-naming) -char const* const versionString = "3.2.0-rc6" +char const* const versionString = "3.2.0" // clang-format on ; From f5b4ac358f3bb9590d48d78c199fdb3278c8c3e0 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Mon, 15 Jun 2026 20:13:40 -0400 Subject: [PATCH 114/135] AI review feedback - Remove extraneous function declaration. - Add a timeout to SHAMapStore_test::testLedgerGaps so it won't hang in case of regression. - Add an optional timeout parameter to SHAMapStore::rendezvous, and return a success flag. - Use the timeout in SHAMapStore_test::testLedgerGaps to show lack of progress in the Store without an arbitrary sleep delay. - Refactor LedgerMaster::missingFromCompleteLedgerRange to use RangeSet functions instead of a naive iteration. - If lastGoodValidatedLedger_ is default (0) in healthWait(), then act as if no ledgers are missing. --- src/test/app/SHAMapStore_test.cpp | 13 +++++++++---- src/xrpld/app/ledger/detail/LedgerMaster.cpp | 19 +++++-------------- src/xrpld/app/misc/SHAMapStore.h | 5 +++-- src/xrpld/app/misc/SHAMapStoreImp.cpp | 19 +++++++++++++------ src/xrpld/app/misc/SHAMapStoreImp.h | 6 ++---- 5 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/test/app/SHAMapStore_test.cpp b/src/test/app/SHAMapStore_test.cpp index 5eae657dcf..3138628829 100644 --- a/src/test/app/SHAMapStore_test.cpp +++ b/src/test/app/SHAMapStore_test.cpp @@ -664,9 +664,13 @@ public: // current ledger from LedgerMaster. std::this_thread::sleep_for(100ms); LedgerIndex const deleteSeq = maxSeq; - while (!lm.haveLedger(deleteSeq)) { - std::this_thread::sleep_for(100ms); + std::size_t iterations = 30; + while (!lm.haveLedger(deleteSeq) && --iterations > 0) + { + std::this_thread::sleep_for(100ms); + } + BEAST_EXPECTS(iterations > 25, to_string(iterations)); } lm.clearLedger(deleteSeq); @@ -716,7 +720,6 @@ public: for (int l = 0; l < 5; ++l) { env.close(); - // DO NOT CALL rendezvous()! You'll end up with a deadlock. ++maxSeq; // Nothing has changed BEAST_EXPECTS( @@ -728,7 +731,9 @@ public: "Complete Ledgers", expectedRange(minSeq, deleteSeq, maxSeq), lm.getCompleteLedgers())); - std::this_thread::sleep_for(1s); + // The Store is "stuck" in healthWait() and won't finish the run() loop until + // it's backfilled + BEAST_EXPECT(!store.rendezvous(100ms)); } // Put the missing ledger back in LedgerMaster diff --git a/src/xrpld/app/ledger/detail/LedgerMaster.cpp b/src/xrpld/app/ledger/detail/LedgerMaster.cpp index 9da428fc37..456f55a182 100644 --- a/src/xrpld/app/ledger/detail/LedgerMaster.cpp +++ b/src/xrpld/app/ledger/detail/LedgerMaster.cpp @@ -1577,23 +1577,14 @@ LedgerMaster::getCompleteLedgers() const std::size_t LedgerMaster::missingFromCompleteLedgerRange(LedgerIndex first, LedgerIndex last) const { - // Make a copy of the range to avoid holding the lock - auto const range = [&] { + RangeSet const target{range(first, last)}; + + auto const missing = [&target, this] { std::scoped_lock const sl(completeLock_); - return completeLedgers_; + return target - completeLedgers_; }(); - std::size_t missing = 0; - - for (LedgerIndex idx = first; idx <= last; ++idx) - { - if (!boost::icl::contains(range, idx)) - { - ++missing; - } - } - - return missing; + return boost::icl::size(missing); } std::optional diff --git a/src/xrpld/app/misc/SHAMapStore.h b/src/xrpld/app/misc/SHAMapStore.h index 3f43bc8fa4..30736186c5 100644 --- a/src/xrpld/app/misc/SHAMapStore.h +++ b/src/xrpld/app/misc/SHAMapStore.h @@ -5,6 +5,7 @@ #include #include +#include #include namespace xrpl { @@ -27,8 +28,8 @@ public: virtual void start() = 0; - virtual void - rendezvous() const = 0; + virtual bool + rendezvous(std::optional const& timeout = {}) const = 0; virtual void stop() = 0; diff --git a/src/xrpld/app/misc/SHAMapStoreImp.cpp b/src/xrpld/app/misc/SHAMapStoreImp.cpp index 277d2bb49d..43ef446125 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.cpp +++ b/src/xrpld/app/misc/SHAMapStoreImp.cpp @@ -233,14 +233,20 @@ SHAMapStoreImp::onLedgerClosed(std::shared_ptr const& ledger) cond_.notify_one(); } -void -SHAMapStoreImp::rendezvous() const +bool +SHAMapStoreImp::rendezvous(std::optional const& timeout) const { if (!working_) - return; + return true; + + auto notWorking = [&] { return !working_; }; std::unique_lock lock(mutex_); - rendezvous_.wait(lock, [&] { return !working_; }); + if (timeout) + return rendezvous_.wait_for(lock, *timeout, notWorking); + else + rendezvous_.wait(lock, notWorking); + return true; } int @@ -641,8 +647,9 @@ SHAMapStoreImp::healthWait() OperatingMode mode = netOPs_->getOperatingMode(); std::unique_lock lock(mutex_); - auto numMissing = - ledgerMaster_->missingFromCompleteLedgerRange(lastGoodValidatedLedger_, index); + auto numMissing = lastGoodValidatedLedger_ == 0 + ? 0 + : ledgerMaster_->missingFromCompleteLedgerRange(lastGoodValidatedLedger_, index); while (!stop_ && (mode != OperatingMode::FULL || age > ageThreshold_ || numMissing > 0)) { // this value shouldn't change, so grab it while we have the diff --git a/src/xrpld/app/misc/SHAMapStoreImp.h b/src/xrpld/app/misc/SHAMapStoreImp.h index 185821394b..adcd605c23 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.h +++ b/src/xrpld/app/misc/SHAMapStoreImp.h @@ -150,8 +150,8 @@ public: void onLedgerClosed(std::shared_ptr const& ledger) override; - void - rendezvous() const override; + bool + rendezvous(std::optional const& timeout = {}) const override; int fdRequired() const override; @@ -213,8 +213,6 @@ private: enum class HealthResult { Stopping, KeepGoing }; [[nodiscard]] HealthResult healthWait(); - bool - hasCompleteRange(LedgerIndex first, LedgerIndex last); public: void From fad34e852d0d11d76649d104545db1cf12843d03 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Mon, 15 Jun 2026 21:28:23 -0400 Subject: [PATCH 115/135] More AI review feedback: - Move a log message into the same block as the thing it's logging. - Change default recovery time from 1s to 2s. This may need some more tuning. - Rename the online_delete test config helper function to onlineDelete. - Copy some class values while under lock, even though they can't change, as defense in depth. - Use RAII scope unlock in healthWait(). --- cfg/xrpld-example.cfg | 2 +- src/test/app/LedgerMaster_test.cpp | 2 +- src/test/app/SHAMapStore_test.cpp | 3 +-- src/test/jtx/envconfig.h | 2 +- src/test/jtx/impl/envconfig.cpp | 2 +- src/xrpld/app/misc/SHAMapStoreImp.cpp | 22 +++++++++------------- src/xrpld/app/misc/SHAMapStoreImp.h | 2 +- 7 files changed, 15 insertions(+), 20 deletions(-) diff --git a/cfg/xrpld-example.cfg b/cfg/xrpld-example.cfg index 00a9122f75..370195f3d0 100644 --- a/cfg/xrpld-example.cfg +++ b/cfg/xrpld-example.cfg @@ -1063,7 +1063,7 @@ # recent ledgers are available. If not, then continue # sleeping for this number of seconds and # checking until healthy. -# Default is 1. +# Default is 2. # # Notes: # The 'node_db' entry configures the primary, persistent storage. diff --git a/src/test/app/LedgerMaster_test.cpp b/src/test/app/LedgerMaster_test.cpp index adc6344aaa..da91206966 100644 --- a/src/test/app/LedgerMaster_test.cpp +++ b/src/test/app/LedgerMaster_test.cpp @@ -129,7 +129,7 @@ class LedgerMaster_test : public beast::unit_test::Suite auto const deleteInterval = 8; Env env{*this, envconfig([](auto cfg) { - return online_delete(std::move(cfg), deleteInterval); + return onlineDelete(std::move(cfg), deleteInterval); })}; auto const alice = Account("alice"); diff --git a/src/test/app/SHAMapStore_test.cpp b/src/test/app/SHAMapStore_test.cpp index 3138628829..a814068aef 100644 --- a/src/test/app/SHAMapStore_test.cpp +++ b/src/test/app/SHAMapStore_test.cpp @@ -47,8 +47,7 @@ class SHAMapStore_test : public beast::unit_test::Suite static auto onlineDelete(std::unique_ptr cfg) { - using namespace jtx; - return online_delete(std::move(cfg), kDeleteInterval); + return jtx::onlineDelete(std::move(cfg), kDeleteInterval); } static auto diff --git a/src/test/jtx/envconfig.h b/src/test/jtx/envconfig.h index d0e5abdf6f..f1ee066b12 100644 --- a/src/test/jtx/envconfig.h +++ b/src/test/jtx/envconfig.h @@ -60,7 +60,7 @@ envconfig(F&& modfunc, Args&&... args) /// /// @return unique_ptr to Config instance std::unique_ptr -online_delete(std::unique_ptr cfg, std::uint32_t deleteInterval = 8); +onlineDelete(std::unique_ptr cfg, std::uint32_t deleteInterval = 8); /// @brief adjust config so no admin ports are enabled /// diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index 47db568ead..14690058ec 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -63,7 +63,7 @@ setupConfigForUnitTests(Config& cfg) namespace jtx { std::unique_ptr -online_delete(std::unique_ptr cfg, std::uint32_t deleteInterval) +onlineDelete(std::unique_ptr cfg, std::uint32_t deleteInterval) { cfg->ledgerHistory = deleteInterval; auto& section = cfg->section(Sections::kNodeDatabase); diff --git a/src/xrpld/app/misc/SHAMapStoreImp.cpp b/src/xrpld/app/misc/SHAMapStoreImp.cpp index 43ef446125..1b3daa22cd 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.cpp +++ b/src/xrpld/app/misc/SHAMapStoreImp.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -317,9 +318,8 @@ SHAMapStoreImp::run() bool const readyToRotate = validatedSeq >= lastRotated + deleteInterval_ && canDelete_ >= lastRotated - 1 && healthWait() == HealthResult::KeepGoing; - JLOG(journal_.debug()) << "run: Setting lastGoodValidatedLedger_ to " << validatedSeq; - { + JLOG(journal_.debug()) << "run: Setting lastGoodValidatedLedger_ to " << validatedSeq; // Note that this is set after the healthWait() check, so that we // don't start the rotation until the validated ledger is fully // processed. It is not guaranteed to be done at this point. It also @@ -647,37 +647,33 @@ SHAMapStoreImp::healthWait() OperatingMode mode = netOPs_->getOperatingMode(); std::unique_lock lock(mutex_); + auto const waitTime = recoveryWaitTime_; + auto const ageThreshold = ageThreshold_; auto numMissing = lastGoodValidatedLedger_ == 0 ? 0 : ledgerMaster_->missingFromCompleteLedgerRange(lastGoodValidatedLedger_, index); - while (!stop_ && (mode != OperatingMode::FULL || age > ageThreshold_ || numMissing > 0)) + while (!stop_ && (mode != OperatingMode::FULL || age > ageThreshold || numMissing > 0)) { // this value shouldn't change, so grab it while we have the // lock auto const lowerBound = lastGoodValidatedLedger_; - lock.unlock(); + ScopeUnlock unlock(lock); auto const stream = - mode != OperatingMode::FULL || age > ageThreshold_ ? journal_.warn() : journal_.info(); - JLOG(stream) << "Waiting " << recoveryWaitTime_.count() - << "s for node to stabilize. state: " + mode != OperatingMode::FULL || age > ageThreshold ? journal_.warn() : journal_.info(); + JLOG(stream) << "Waiting " << waitTime.count() << "s for node to stabilize. state: " << app_.getOPs().strOperatingMode(mode, false) << ". age " << age.count() << "s. Missing ledgers: " << numMissing << ". Expect: " << lowerBound << "-" << index << ". Complete ledgers: " << ledgerMaster_->getCompleteLedgers(); - std::this_thread::sleep_for(recoveryWaitTime_); + std::this_thread::sleep_for(waitTime); index = ledgerMaster_->getValidLedgerIndex(); age = ledgerMaster_->getValidatedLedgerAge(); mode = netOPs_->getOperatingMode(); numMissing = lowerBound == 0 ? 0 : ledgerMaster_->missingFromCompleteLedgerRange(lowerBound, index); - - lock.lock(); } - JLOG(journal_.debug()) << "healthWait: Setting lastGoodValidatedLedger_ to " << index; - lastGoodValidatedLedger_ = index; - return stop_ ? HealthResult::Stopping : HealthResult::KeepGoing; } diff --git a/src/xrpld/app/misc/SHAMapStoreImp.h b/src/xrpld/app/misc/SHAMapStoreImp.h index adcd605c23..58fd1a7132 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.h +++ b/src/xrpld/app/misc/SHAMapStoreImp.h @@ -94,7 +94,7 @@ private: /// available during an online_delete healthWait() call, sleep /// the thread for this time, and continue checking until recovery. /// See also: "recovery_wait_seconds" in xrpld-example.cfg - std::chrono::seconds recoveryWaitTime_{1}; + std::chrono::seconds recoveryWaitTime_{2}; // these do not exist upon SHAMapStore creation, but do exist // as of run() or before From 0364e4dc4197cf8be2ac84ea8acdc438b72ad17e Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 16 Jun 2026 14:24:12 +0100 Subject: [PATCH 116/135] docs: Rewrite build environment docs (#7533) Co-authored-by: Ed Hennis --- .github/scripts/strategy-matrix/linux.json | 6 +- .github/workflows/build-nix-images.yml | 8 + .github/workflows/publish-docs.yml | 2 +- .../workflows/reusable-build-test-config.yml | 5 + .github/workflows/reusable-clang-tidy.yml | 2 +- .github/workflows/reusable-upload-recipe.yml | 2 +- BUILD.md | 391 ++++-------------- CONTRIBUTING.md | 16 +- bin/check-tools.sh | 158 +++++++ {nix/docker => bin}/install-sanitizer-libs.sh | 0 cspell.config.yaml | 1 + docs/build/advanced_conan.md | 193 +++++++++ docs/build/conan.md | 2 +- docs/build/environment.md | 162 +++----- docs/build/nix.md | 45 +- docs/build/nix_troubleshooting.md | 61 +++ nix/docker/Dockerfile | 4 +- nix/docker/README.md | 90 ++++ nix/docker/check-tools.sh | 39 -- 19 files changed, 710 insertions(+), 477 deletions(-) create mode 100755 bin/check-tools.sh rename {nix/docker => bin}/install-sanitizer-libs.sh (100%) create mode 100644 docs/build/advanced_conan.md create mode 100644 docs/build/nix_troubleshooting.md create mode 100644 nix/docker/README.md delete mode 100755 nix/docker/check-tools.sh diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index 4f45216cda..a9b85b766a 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -1,5 +1,5 @@ { - "image_tag": "sha-63ffdc3", + "image_tag": "sha-fe4c8ae", "configs": { "ubuntu": [ { @@ -68,7 +68,7 @@ "compiler": ["gcc"], "build_type": ["Release"], "arch": ["amd64"], - "image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-63ffdc3" + "image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-577d745" } ], @@ -77,7 +77,7 @@ "compiler": ["gcc"], "build_type": ["Release"], "arch": ["amd64"], - "image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-63ffdc3" + "image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-577d745" } ] } diff --git a/.github/workflows/build-nix-images.yml b/.github/workflows/build-nix-images.yml index 24f069902d..3af6a3b1d4 100644 --- a/.github/workflows/build-nix-images.yml +++ b/.github/workflows/build-nix-images.yml @@ -9,12 +9,20 @@ on: - "flake.nix" - "flake.lock" - "nix/**" + - "!nix/docker/README.md" + - "!nix/devshell.nix" + - "bin/check-tools.sh" + - "bin/install-sanitizer-libs.sh" pull_request: paths: - ".github/workflows/build-nix-images.yml" - "flake.nix" - "flake.lock" - "nix/**" + - "!nix/docker/README.md" + - "!nix/devshell.nix" + - "bin/check-tools.sh" + - "bin/install-sanitizer-libs.sh" workflow_dispatch: concurrency: diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index bcf5968384..0de5347aab 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -41,7 +41,7 @@ env: jobs: build: runs-on: ubuntu-latest - container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-63ffdc3 + container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fe4c8ae steps: - name: Checkout repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 28d317e4dd..95e6b0cbe2 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -121,6 +121,11 @@ jobs: if: ${{ inputs.ccache_enabled && runner.debug == '1' }} run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >>"${GITHUB_ENV}" + - name: Check tools + env: + CHECK_TOOLS_SKIP_CLONE: "1" + run: ./bin/check-tools.sh + - name: Print build environment uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574 diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index 9f10711b6f..34fa860e12 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -36,7 +36,7 @@ jobs: needs: [determine-files] if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }} runs-on: ["self-hosted", "Linux", "X64", "heavy"] - container: "ghcr.io/xrplf/xrpld/nix-debian:sha-63ffdc3" + container: "ghcr.io/xrplf/xrpld/nix-debian:sha-fe4c8ae" permissions: contents: read issues: write diff --git a/.github/workflows/reusable-upload-recipe.yml b/.github/workflows/reusable-upload-recipe.yml index 1c90fb0e72..ba7a0943d9 100644 --- a/.github/workflows/reusable-upload-recipe.yml +++ b/.github/workflows/reusable-upload-recipe.yml @@ -40,7 +40,7 @@ defaults: jobs: upload: runs-on: ubuntu-latest - container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-63ffdc3 + container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fe4c8ae steps: - name: Checkout repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 diff --git a/BUILD.md b/BUILD.md index 662ba0d33d..2ac24f2c5d 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,26 +1,57 @@ -| :warning: **WARNING** :warning: | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md). | +| :warning: **WARNING** :warning: | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md).

These instructions also assume a basic familiarity with Conan and CMake. If you are unfamiliar with Conan, you can read our [crash course](./docs/build/conan.md) or the official [Getting Started][conan-getting-started] walkthrough. | -> These instructions also assume a basic familiarity with Conan and CMake. -> If you are unfamiliar with Conan, you can read our -> [crash course](./docs/build/conan.md) or the official [Getting Started][3] -> walkthrough. +## Minimum Requirements -## Branches +See [System Requirements](https://xrpl.org/system-requirements.html). -For a stable release, choose the `master` branch or one of the [tagged -releases](https://github.com/XRPLF/rippled/releases). +Building xrpld generally requires Git, Python, Conan, CMake, and a C++ +compiler. + +- [Python](https://www.python.org/downloads/) +- [Conan](https://conan.io/downloads.html) +- [CMake](https://cmake.org/download/) + +You can verify that the required tools are installed and runnable with: ```bash -git checkout master +./bin/check-tools.sh ``` -For the latest release candidate, choose the `release` branch. +`xrpld` is written in the C++23 dialect. The [tested compiler versions][cpp23-support] are: -```bash -git checkout release -``` +| Compiler | Version | +| ----------- | --------------- | +| GCC | 15.2 | +| Clang | 22 | +| Apple Clang | 17 | +| MSVC | 19.44[^windows] | + +## Operating Systems + +Please see the [environment setup guide](./docs/build/environment.md) for detailed instructions for all platforms. + +### Linux + +The Ubuntu Linux distribution has received the highest level of quality +assurance, testing, and support. We also support Red Hat and use Debian +internally. +Our Linux CI tooling is distro-independent and uses a Nix-based environment, so it should be possible to build on other Linux distributions as well, although we have not tested them. + +### macOS + +Many `xrpld` engineers use macOS for development. + +### Windows + +Windows is used by some engineers for development only. + +[^windows]: Windows is not recommended for production use. + +## Steps + +### Branches For the latest set of untested features, or to contribute, choose the `develop` branch. @@ -29,55 +60,15 @@ branch. git checkout develop ``` -## Minimum Requirements +For a release candidate, choose the relevant release branch, e.g. +`release/3.2.x`. -See [System Requirements](https://xrpl.org/system-requirements.html). +```bash +git checkout release/3.2.x +``` -Building xrpld generally requires git, Python, Conan, CMake, and a C++ -compiler. Some guidance on setting up such a [C++ development environment can be -found here](./docs/build/environment.md). - -- [Python 3.11](https://www.python.org/downloads/), or higher -- [Conan 2.17](https://conan.io/downloads.html)[^1], or higher -- [CMake 3.22](https://cmake.org/download/), or higher - -[^1]: - It is possible to build with Conan 1.60+, but the instructions are - significantly different, which is why we are not recommending it. - -`xrpld` is written in the C++23 dialect and includes the `` header. -The [tested compiler versions][2] are: - -| Compiler | Version | -| ----------- | --------- | -| GCC | 15 | -| Clang | 22 | -| Apple Clang | 17 | -| MSVC | 19.44[^3] | - -### Linux - -The Ubuntu Linux distribution has received the highest level of quality -assurance, testing, and support. We also support Red Hat and use Debian -internally. - -Here are [sample instructions for setting up a C++ development environment on -Linux](./docs/build/environment.md#linux). - -### Mac - -Many xrpld engineers use macOS for development. - -Here are [sample instructions for setting up a C++ development environment on -macOS](./docs/build/environment.md#macos). - -### Windows - -Windows is used by some engineers for development only. - -[^3]: Windows is not recommended for production use. - -## Steps +For a stable release, choose one of the [tagged +releases](https://github.com/XRPLF/rippled/releases). ### Set Up Conan @@ -86,18 +77,11 @@ Conan, CMake, and a C++ compiler, you may need to set up your Conan profile. These instructions assume a basic familiarity with Conan and CMake. If you are unfamiliar with Conan, then please read [this crash course](./docs/build/conan.md) or the official -[Getting Started][3] walkthrough. +[Getting Started][conan-getting-started] walkthrough. -#### Conan lockfile +#### Profiles -To achieve reproducible dependencies, we use a [Conan lockfile](https://docs.conan.io/2/tutorial/versioning/lockfiles.html), -which has to be updated every time dependencies change. - -Please see the [instructions on how to regenerate the lockfile](conan/lockfile/README.md). - -#### Default profile - -We recommend that you import the provided `conan/profiles/default` profile: +We recommend that you install our Conan profiles: ```bash conan config install conan/profiles/ -tf $(conan config home)/profiles/ @@ -109,222 +93,15 @@ You can check your Conan profile by running: conan profile show ``` -#### Custom profile +If the default profile is not suitable for your environment, you can create a custom profile and pass it to Conan. +More information on customizing Conan can be found in the [Advanced Conan configuration](./docs/build/advanced_conan.md). -If the default profile does not work for you and you do not yet have a Conan -profile, you can create one by running: +#### Add xrplf remote + +Run the following command to add the `xrplf` remote, which hosts some of our dependencies: ```bash -conan profile detect -``` - -You may need to make changes to the profile to suit your environment. You can -refer to the provided `conan/profiles/default` profile for inspiration, and you -may also need to apply the required [tweaks](#conan-profile-tweaks) to this -default profile. - -### Patched recipes - -Occasionally, we need patched recipes or recipes not present in Conan Center. -We maintain a fork of the Conan Center Index -[here](https://github.com/XRPLF/conan-center-index/) containing the modified and newly added recipes. - -To ensure our patched recipes are used, you must add our Conan remote at a -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 -``` - -Alternatively, you can pull our recipes from the repository and export them locally: - -```bash -# Define which recipes to export. -recipes=('abseil' 'ed25519' 'mpt-crypto' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi') - -# Selectively check out the recipes from our CCI fork. -cd external -mkdir -p conan-center-index -cd conan-center-index -git init -git remote add origin git@github.com:XRPLF/conan-center-index.git -git sparse-checkout init -for recipe in "${recipes[@]}"; do - echo "Checking out recipe '${recipe}'..." - git sparse-checkout add recipes/${recipe} -done -git fetch origin master -git checkout master - -./export_all.sh -cd ../../ -``` - -In the case we switch to a newer version of a dependency that still requires a -patch or add a new dependency, it will be necessary for you to pull in the changes and re-export the -updated dependencies with the newer version. However, if we switch to a newer -version that no longer requires a patch, no action is required on your part, as -the new recipe will be automatically pulled from the official Conan Center. - -> [!NOTE] -> You might need to add `--lockfile=""` to your `conan install` command -> to avoid automatic use of the existing `conan.lock` file when you run -> `conan export` manually on your machine -> -> This is not recommended though, as you might end up using different revisions of recipes. - -### Conan profile tweaks - -#### Missing compiler version - -If you see an error similar to the following after running `conan profile show`: - -```text -ERROR: Invalid setting '17' is not a valid 'settings.compiler.version' value. -Possible values are ['5.0', '5.1', '6.0', '6.1', '7.0', '7.3', '8.0', '8.1', -'9.0', '9.1', '10.0', '11.0', '12.0', '13', '13.0', '13.1', '14', '14.0', '15', -'15.0', '16', '16.0'] -Read "http://docs.conan.io/2/knowledge/faq.html#error-invalid-setting" -``` - -you need to add your compiler to the list of compiler versions in -`$(conan config home)/settings_user.yml`, by adding the required version number(s) -to the `version` array specific for your compiler. For example: - -```yaml -compiler: - apple-clang: - version: ["17.0"] -``` - -#### Multiple compilers - -If you have multiple compilers installed, make sure to select the one to use in -your default Conan configuration **before** running `conan profile detect`, by -setting the `CC` and `CXX` environment variables. - -For example, if you are running MacOS and have [homebrew -LLVM@18](https://formulae.brew.sh/formula/llvm@18), and want to use it as a -compiler in the new Conan profile: - -```bash -export CC=$(brew --prefix llvm@18)/bin/clang -export CXX=$(brew --prefix llvm@18)/bin/clang++ -conan profile detect -``` - -You should also explicitly set the path to the compiler in the profile file, -which helps to avoid errors when `CC` and/or `CXX` are set and disagree with the -selected Conan profile. For example: - -```text -[conf] -tools.build:compiler_executables={'c':'/usr/bin/gcc','cpp':'/usr/bin/g++'} -``` - -#### Multiple profiles - -You can manage multiple Conan profiles in the directory -`$(conan config home)/profiles`, for example renaming `default` to a different -name and then creating a new `default` profile for a different compiler. - -#### Select language - -The default profile created by Conan will typically select different C++ dialect -than C++23 used by this project. You should set `23` in the profile line -starting with `compiler.cppstd=`. For example: - -```bash -sed -i.bak -e 's|^compiler\.cppstd=.*$|compiler.cppstd=23|' $(conan config home)/profiles/default -``` - -#### Select standard library in Linux - -**Linux** developers will commonly have a default Conan [profile][] that -compiles with GCC and links with libstdc++. If you are linking with libstdc++ -(see profile setting `compiler.libcxx`), then you will need to choose the -`libstdc++11` ABI: - -```bash -sed -i.bak -e 's|^compiler\.libcxx=.*$|compiler.libcxx=libstdc++11|' $(conan config home)/profiles/default -``` - -#### Select architecture and runtime in Windows - -**Windows** developers may need to use the x64 native build tools. An easy way -to do that is to run the shortcut "x64 Native Tools Command Prompt" for the -version of Visual Studio that you have installed. - -Windows developers must also build `xrpld` and its dependencies for the x64 -architecture: - -```bash -sed -i.bak -e 's|^arch=.*$|arch=x86_64|' $(conan config home)/profiles/default -``` - -**Windows** developers also must select static runtime: - -```bash -sed -i.bak -e 's|^compiler\.runtime=.*$|compiler.runtime=static|' $(conan config home)/profiles/default -``` - -#### Clang workaround for grpc - -If your compiler is clang, version 19 or later, or apple-clang, version 17 or -later, you may encounter a compilation error while building the `grpc` -dependency: - -```text -In file included from .../lib/promise/try_seq.h:26: -.../lib/promise/detail/basic_seq.h:499:38: error: a template argument list is expected after a name prefixed by the template keyword [-Wmissing-template-arg-list-after-template-kw] - 499 | Traits::template CallSeqFactory(f_, *cur_, std::move(arg))); - | ^ -``` - -The workaround for this error is to add two lines to profile: - -```text -[conf] -tools.build:cxxflags=['-Wno-missing-template-arg-list-after-template-kw'] -``` - -#### Workaround for gcc 12 - -If your compiler is gcc, version 12, and you have enabled `werr` option, you may -encounter a compilation error such as: - -```text -/usr/include/c++/12/bits/char_traits.h:435:56: error: 'void* __builtin_memcpy(void*, const void*, long unsigned int)' accessing 9223372036854775810 or more bytes at offsets [2, 9223372036854775807] and 1 may overlap up to 9223372036854775813 bytes at offset -3 [-Werror=restrict] - 435 | return static_cast(__builtin_memcpy(__s1, __s2, __n)); - | ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~ -cc1plus: all warnings being treated as errors -``` - -The workaround for this error is to add two lines to your profile: - -```text -[conf] -tools.build:cxxflags=['-Wno-restrict'] -``` - -#### Workaround for clang 16 - -If your compiler is clang, version 16, you may encounter compilation error such -as: - -```text -In file included from .../boost/beast/websocket/stream.hpp:2857: -.../boost/beast/websocket/impl/read.hpp:695:17: error: call to 'async_teardown' is ambiguous - async_teardown(impl.role, impl.stream(), - ^~~~~~~~~~~~~~ -``` - -The workaround for this error is to add two lines to your profile: - -```text -[conf] -tools.build:cxxflags=['-DBOOST_ASIO_DISABLE_CONCEPTS'] +conan remote add --index 0 --force xrplf https://conan.ripplex.io ``` ### Set Up Ccache @@ -333,14 +110,7 @@ To speed up repeated compilations, we recommend that you install [ccache](https://ccache.dev), a tool that wraps your compiler so that it can cache build objects locally. -#### Linux - -You can install it using the package manager, e.g. `sudo apt install ccache` -(Ubuntu) or `sudo dnf install ccache` (RHEL). - -#### macOS - -You can install it using Homebrew, i.e. `brew install ccache`. +On Linux and macOS, `ccache` is included in the [Nix development shell](./docs/build/nix.md). #### Windows @@ -549,7 +319,7 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details. | Option | Default Value | Description | | ---------- | ------------- | -------------------------------------------------------------- | -| `assert` | OFF | Enable assertions. | +| `assert` | OFF | Force enabling assertions. | | `coverage` | OFF | Prepare the coverage report. | | `tests` | OFF | Build tests. | | `unity` | OFF | Configure a unity build. | @@ -557,7 +327,7 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details. | `werr` | OFF | Treat compilation warnings as errors | | `wextra` | OFF | Enable additional compilation warnings | -[Unity builds][5] may be faster for the first build (at the cost of much more +[Unity builds][unity-build] may be faster for the first build (at the cost of much more memory) since they concatenate sources into fewer translation units. Non-unity builds may be faster for incremental builds, and can be helpful for detecting `#include` omissions. @@ -583,14 +353,14 @@ After any updates or changes to dependencies, you may need to do the following: conan remove '*' ``` -3. Re-run [conan export](#patched-recipes) if needed. -4. [Regenerate lockfile](#conan-lockfile). +3. Re-run [conan export](./docs/build/advanced_conan.md#patched-recipes) if needed. +4. [Regenerate lockfile](./docs/build/advanced_conan.md#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). +please [add `xrplf` remote](#add-xrplf-remote) or re-run `conan export` for [patched recipes](./docs/build/advanced_conan.md#patched-recipes). ### `protobuf/port_def.inc` file not found @@ -610,28 +380,9 @@ For example, if you want to build Debug: 1. For conan install, pass `--settings build_type=Debug` 2. For cmake, pass `-DCMAKE_BUILD_TYPE=Debug` -## Add a Dependency - -If you want to experiment with a new package, follow these steps: - -1. Search for the package on [Conan Center](https://conan.io/center/). -2. Modify [`conanfile.py`](./conanfile.py): - - Add a version of the package to the `requires` property. - - Change any default options for the package by adding them to the - `default_options` property (with syntax `'$package:$option': $value`). -3. Modify [`CMakeLists.txt`](./CMakeLists.txt): - - Add a call to `find_package($package REQUIRED)`. - - Link a library from the package to the target `xrpl_libs` - (search for the existing call to `target_link_libraries(xrpl_libs INTERFACE ...)`). -4. Start coding! Don't forget to include whatever headers you need from the package. - -[1]: https://github.com/conan-io/conan-center-index/issues/13168 -[2]: https://en.cppreference.com/w/cpp/compiler_support/20 -[3]: https://docs.conan.io/en/latest/getting_started.html -[5]: https://en.wikipedia.org/wiki/Unity_build -[6]: https://github.com/boostorg/beast/issues/2648 -[7]: https://github.com/boostorg/beast/issues/2661 +[cpp23-support]: https://en.cppreference.com/w/cpp/compiler_support/23 +[conan-getting-started]: https://docs.conan.io/en/latest/getting_started.html +[unity-build]: https://en.wikipedia.org/wiki/Unity_build [gcovr]: https://gcovr.com/en/stable/getting-started.html [python-pip]: https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/ [build_type]: https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html -[profile]: https://docs.conan.io/en/latest/reference/profiles.html diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 25dd7ac059..fc93223925 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,9 +14,9 @@ The following branches exist in the main project repository: - `develop`: The latest set of unreleased features, and the most common starting point for contributions. -- `release`: The latest beta release or release candidate. -- `master`: The latest stable release. -- `gh-pages`: The documentation for this project, built by Doxygen. +- `release/*` (e.g. `release/3.2.x`): Release branches, one per release line, + holding the latest release candidate, or stable release for that line. + Stable releases are published as [tagged releases](https://github.com/XRPLF/rippled/releases). The tip of each branch must be signed. In order for GitHub to sign a squashed commit that it builds from your pull request, GitHub must know @@ -130,11 +130,9 @@ tl;dr ## Pull requests In general, pull requests use `develop` as the base branch. -The exceptions are -- Fixes and improvements to a release candidate use `release` as the - base. -- Hotfixes use `master` as the base. +The exceptions are fixes, improvements, and hotfixes for an existing release, +which use that release's branch (e.g. `release/3.2.x`) as the base. If your changes are not quite ready, but you want to make it easily available for preliminary examination or review, you can create a "Draft" pull request. @@ -216,7 +214,7 @@ coherent rather than a set of _thou shalt not_ commandments. ## Formatting -All code must conform to `clang-format` version 21, +All code must conform to `clang-format` version 22, according to the settings in [`.clang-format`](./.clang-format), unless the result would be unreasonably difficult to read or maintain. To demarcate lines that should be left as-is, surround them with comments like @@ -261,7 +259,7 @@ This ensures that configuration changes don't introduce new warnings across the ### Installing clang-tidy -See the [environment setup guide](./docs/build/environment.md#clang-tidy) for platform-specific installation instructions. +See the [environment setup guide](./docs/build/environment.md#clang-tidy) for how to get clang-tidy. ### Running clang-tidy locally diff --git a/bin/check-tools.sh b/bin/check-tools.sh new file mode 100755 index 0000000000..15b16b6fc8 --- /dev/null +++ b/bin/check-tools.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# +# check-tools.sh — verify the xrpld development tooling is present and runnable. +# +# Works on Linux, macOS, and Windows (Git Bash / MSYS). For every expected tool +# it runs a version probe, collecting anything that is missing or fails to run, +# and prints a summary at the end (exiting non-zero if anything is missing). +# +# The tool set is platform-aware: +# - Linux: the full Nix CI environment (see nix/packages.nix, nix/ci-env.nix), +# with GCC, Clang and the sanitizer/coverage tooling. This script is +# run during the Nix Docker image build (nix/docker/Dockerfile), so +# the Linux list is kept in sync with that environment. +# - macOS: the same tooling, minus GCC/g++/gcov/mold +# - Windows: the core build tools only (CMake, Conan, Git, Python). +# MSVC is expected to be provided separately and is not checked here. +# +# Some tools (clang-format, doxygen, gcovr, gh, git-cliff, gpg, pre-commit, +# run-clang-tidy) are present in our Linux CI images and in local development +# setups, but not in the macOS CI environment. They are checked everywhere +# except when running in CI on macOS. +# +# Environment variables: +# CI if set, skip the tools above when on macOS. +# CHECK_TOOLS_SKIP_CLONE if set, skip the git-over-HTTPS connectivity check. + +set -uo pipefail + +missing=() +checked=0 + +# check [probe-command...] +# Runs the probe (default: " --version") quietly. Records as +# missing if the command is not found or exits non-zero. +check() { + local name="$1" + shift + local -a probe=("$@") + if [ "${#probe[@]}" -eq 0 ]; then + probe=("${name}" --version) + fi + + echo "Checking ${name}..." + checked=$((checked + 1)) + if "${probe[@]}" | head -n 1; then + printf ' [ ok ] %s\n' "${name}" + else + printf ' [MISS] %s\n' "${name}" + missing+=("${name}") + fi +} + +case "$(uname -s)" in + Linux*) os=linux ;; + Darwin*) os=macos ;; + MINGW* | MSYS* | CYGWIN*) os=windows ;; + *) + echo "Unknown OS: $(uname -s)" >&2 + exit 1 + ;; +esac + +echo "Detected OS: ${os} ($(uname -s) $(uname -m))" +echo +echo "Core build tools:" +check cmake +check conan +check git +if [ "${os}" = "windows" ]; then + check python python --version +else + check python3 +fi + +# The full development toolchain. Available from Nix on Linux and macOS; on +# Windows these are typically not installed, so they are skipped. +if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then + echo + echo "Development tooling:" + check ccache + check clang + check clang++ + check ClangBuildAnalyzer + check curl + check file + check less + check make + check netstat which netstat + check ninja + check perl + check pkg-config + check vim + + # These tools are present in our Linux CI images and in local development + # setups, but not in the macOS CI environment. So check them everywhere + # except when running in CI on macOS. + if [ "${os}" = "linux" ] || [ -z "${CI:-}" ]; then + check clang-format + check doxygen + check gcovr + check gh + check git-cliff + check gpg + # pre-commit, or its alternative implementation prek + check pre-commit sh -c 'pre-commit --version || prek --version' + check run-clang-tidy run-clang-tidy --help + fi +fi + +# GCC is the default compiler on Linux. macOS uses the system Apple Clang +# instead, so GCC/g++/gcov are not expected there. +if [ "${os}" = "linux" ]; then + echo + echo "GCC toolchain:" + check gcc + check g++ + check gcov + + echo + echo "Mold:" + check mold +fi + +if [ "${os}" = "windows" ]; then + echo + echo "Note: on Windows the C++ compiler is MSVC, which is provided" + echo " separately (e.g. via Visual Studio) and is not checked here." +fi + +# A simple test to verify that git can clone a repository over HTTPS +# (i.e. the CA bundle is wired up). Clone to a temp dir and clean up. +if [ -n "${CHECK_TOOLS_SKIP_CLONE:-}" ]; then + echo + echo "Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set)." +else + echo + echo "Connectivity check:" + checked=$((checked + 1)) + tmp_clone="$(mktemp -d)" + if git clone --depth 1 https://github.com/XRPLF/actions.git "${tmp_clone}/actions" >/dev/null 2>&1; then + printf ' [ ok ] git clone over HTTPS\n' + else + printf ' [MISS] git clone over HTTPS\n' + missing+=("git-https-clone") + fi + rm -rf "${tmp_clone}" +fi + +echo +if [ "${#missing[@]}" -eq 0 ]; then + echo "All ${checked} checked tools are present and runnable." +else + echo "Missing or non-functional tools (${#missing[@]} of ${checked}):" >&2 + for tool in "${missing[@]}"; do + echo " - ${tool}" >&2 + done + exit 1 +fi diff --git a/nix/docker/install-sanitizer-libs.sh b/bin/install-sanitizer-libs.sh similarity index 100% rename from nix/docker/install-sanitizer-libs.sh rename to bin/install-sanitizer-libs.sh diff --git a/cspell.config.yaml b/cspell.config.yaml index 77f0e9df7a..0d38c4be7b 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -109,6 +109,7 @@ words: - enabled - enablerepo - endmacro + - envrc - exceptioned - EXPECT_STREQ - Falco diff --git a/docs/build/advanced_conan.md b/docs/build/advanced_conan.md new file mode 100644 index 0000000000..aae17e385a --- /dev/null +++ b/docs/build/advanced_conan.md @@ -0,0 +1,193 @@ +# Advanced Conan configuration + +This document provides advanced instructions for setting up and configuring Conan for `xrpld` development: custom profiles, the lockfile, patched recipes, and profile tweaks. + +## Custom profile + +If the default profile does not work for you and you do not yet have a Conan +profile, you can create one by running: + +```bash +conan profile detect +``` + +You may need to make changes to the profile to suit your environment. You can +refer to the provided `conan/profiles/default` profile for inspiration, and you +may also need to apply the required [tweaks](#conan-profile-tweaks) to this +default profile. + +## Conan lockfile + +To achieve reproducible dependencies, we use a [Conan lockfile](https://docs.conan.io/2/tutorial/versioning/lockfiles.html), +which has to be updated every time dependencies change. + +Please see the [instructions on how to regenerate the lockfile](../../conan/lockfile/README.md). + +## Patched recipes + +Occasionally, we need patched recipes or recipes not present in Conan Center. +We maintain a fork of the Conan Center Index +[here](https://github.com/XRPLF/conan-center-index/) containing the modified and newly added recipes. + +To ensure our patched recipes are used, you must add our Conan remote at a +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 --force xrplf https://conan.ripplex.io +``` + +Alternatively, you can pull our recipes from the repository and export them locally: + +```bash +# Define which recipes to export. +recipes=('abseil' 'ed25519' 'mpt-crypto' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi') + +# Selectively check out the recipes from our CCI fork. +cd external +mkdir -p conan-center-index +cd conan-center-index +git init +git remote add origin git@github.com:XRPLF/conan-center-index.git +git sparse-checkout init +for recipe in "${recipes[@]}"; do + echo "Checking out recipe '${recipe}'..." + git sparse-checkout add recipes/${recipe} +done +git fetch origin master +git checkout master + +./export_all.sh +cd ../../ +``` + +In the case we switch to a newer version of a dependency that still requires a +patch or add a new dependency, it will be necessary for you to pull in the changes and re-export the +updated dependencies with the newer version. However, if we switch to a newer +version that no longer requires a patch, no action is required on your part, as +the new recipe will be automatically pulled from the official Conan Center. + +> [!NOTE] +> You might need to add `--lockfile=""` to your `conan install` command +> to avoid automatic use of the existing `conan.lock` file when you run +> `conan export` manually on your machine +> +> This is not recommended though, as you might end up using different revisions of recipes. + +## Conan profile tweaks + +### Missing compiler version + +If you see an error similar to the following after running `conan profile show`: + +```text +ERROR: Invalid setting '17' is not a valid 'settings.compiler.version' value. +Possible values are ['5.0', '5.1', '6.0', '6.1', '7.0', '7.3', '8.0', '8.1', +'9.0', '9.1', '10.0', '11.0', '12.0', '13', '13.0', '13.1', '14', '14.0', '15', +'15.0', '16', '16.0'] +Read "http://docs.conan.io/2/knowledge/faq.html#error-invalid-setting" +``` + +you need to create `$(conan config home)/settings_user.yml` file if it doesn't exist and add the required version number(s) +to the `version` array specific for your compiler. For example: + +```yaml +compiler: + apple-clang: + version: ["17.0"] +``` + +### Multiple compilers + +If you have multiple compilers installed, make sure to select the one to use in +your default Conan configuration **before** running `conan profile detect`, by +setting the `CC` and `CXX` environment variables. + +For example, if you are running MacOS and have [homebrew +LLVM@18](https://formulae.brew.sh/formula/llvm@18), and want to use it as a +compiler in the new Conan profile: + +```bash +export CC=$(brew --prefix llvm@18)/bin/clang +export CXX=$(brew --prefix llvm@18)/bin/clang++ +conan profile detect +``` + +You should also explicitly set the path to the compiler in the profile file, +which helps to avoid errors when `CC` and/or `CXX` are set and disagree with the +selected Conan profile. For example: + +```text +[conf] +tools.build:compiler_executables={'c':'/usr/bin/gcc','cpp':'/usr/bin/g++'} +``` + +### Multiple profiles + +You can manage multiple Conan profiles in the directory +`$(conan config home)/profiles`, for example renaming `default` to a different +name and then creating a new `default` profile for a different compiler. + +### Select language + +The default profile created by Conan will typically select different C++ dialect +than C++23 used by this project. You should set `23` in the profile line +starting with `compiler.cppstd=`. For example: + +```bash +sed -i.bak -e 's|^compiler\.cppstd=.*$|compiler.cppstd=23|' $(conan config home)/profiles/default +``` + +### Select standard library in Linux + +**Linux** developers will commonly have a default Conan [profile][] that +compiles with GCC and links with libstdc++. If you are linking with libstdc++ +(see profile setting `compiler.libcxx`), then you will need to choose the +`libstdc++11` ABI: + +```bash +sed -i.bak -e 's|^compiler\.libcxx=.*$|compiler.libcxx=libstdc++11|' $(conan config home)/profiles/default +``` + +### Select architecture and runtime in Windows + +**Windows** developers may need to use the x64 native build tools. An easy way +to do that is to run the shortcut "x64 Native Tools Command Prompt" for the +version of Visual Studio that you have installed. + +Windows developers must also build `xrpld` and its dependencies for the x64 +architecture: + +```bash +sed -i.bak -e 's|^arch=.*$|arch=x86_64|' $(conan config home)/profiles/default +``` + +**Windows** developers also must select static runtime: + +```bash +sed -i.bak -e 's|^compiler\.runtime=.*$|compiler.runtime=static|' $(conan config home)/profiles/default +``` + +## Add a Dependency + +If you want to experiment with a new package, follow these steps: + +1. Search for the package on [Conan Center](https://conan.io/center/). +2. Modify [`conanfile.py`](../../conanfile.py): + - Add a version of the package to the `requires` property. + - Change any default options for the package by adding them to the + `default_options` property (with syntax `'$package:$option': $value`). +3. Regenerate the [Conan lockfile](../../conan/lockfile/README.md) so the new + dependency is captured: + + ```bash + ./conan/lockfile/regenerate.sh + ``` + +4. Modify [`CMakeLists.txt`](../../CMakeLists.txt): + - Add a call to `find_package($package REQUIRED)`. + - Link a library from the package to the target `xrpl_libs` + (search for the existing call to `target_link_libraries(xrpl_libs INTERFACE ...)`). +5. Start coding! Don't forget to include whatever headers you need from the package. + +[profile]: https://docs.conan.io/2/reference/config_files/profiles.html diff --git a/docs/build/conan.md b/docs/build/conan.md index 9dcd2c8f1c..22c25a0bf9 100644 --- a/docs/build/conan.md +++ b/docs/build/conan.md @@ -115,7 +115,7 @@ By default, Conan will use the profile named "default". [find_package]: https://cmake.org/cmake/help/latest/command/find_package.html [pcf]: https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#package-configuration-file [prefix_path]: https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html -[profile]: https://docs.conan.io/en/latest/reference/profiles.html +[profile]: https://docs.conan.io/2/reference/config_files/profiles.html [pvf]: https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#package-version-file [runtime]: https://cmake.org/cmake/help/latest/variable/CMAKE_MSVC_RUNTIME_LIBRARY.html [search]: https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure diff --git a/docs/build/environment.md b/docs/build/environment.md index fb1ebde8bc..2cca608567 100644 --- a/docs/build/environment.md +++ b/docs/build/environment.md @@ -1,69 +1,73 @@ Our [build instructions][BUILD.md] assume you have a C++ development environment complete with Git, Python, Conan, CMake, and a C++ compiler. -This document exists to help readers set one up on any of the Big Three -platforms: Linux, macOS, or Windows. - -As an alternative to system packages, the Nix development shell can be used to provide a development environment. See [using nix development shell](./nix.md) for more details. +This document explains how to set one up. [BUILD.md]: ../../BUILD.md -## Linux +## Tested compiler versions -Package ecosystems vary across Linux distributions, -so there is no one set of instructions that will work for every Linux user. -The instructions below are written for Debian 12 (Bookworm). +`xrpld` is built in the **C++23** dialect by default. +Make sure your toolchain is recent enough — the compiler versions currently tested in CI are: -``` -export GCC_RELEASE=12 -sudo apt update -sudo apt install --yes gcc-${GCC_RELEASE} g++-${GCC_RELEASE} python3-pip \ - python-is-python3 python3-venv python3-dev curl wget ca-certificates \ - git build-essential cmake ninja-build libc6-dev -sudo pip install --break-system-packages conan +| Compiler | Version | +| ----------- | ------- | +| GCC | 15.2 | +| Clang | 22 | +| Apple Clang | 17 | +| MSVC | 19.44 | -sudo update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-${GCC_RELEASE} 999 -sudo update-alternatives --install \ - /usr/bin/gcc gcc /usr/bin/gcc-${GCC_RELEASE} 100 \ - --slave /usr/bin/g++ g++ /usr/bin/g++-${GCC_RELEASE} \ - --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-${GCC_RELEASE} \ - --slave /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-${GCC_RELEASE} \ - --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-${GCC_RELEASE} \ - --slave /usr/bin/gcov gcov /usr/bin/gcov-${GCC_RELEASE} \ - --slave /usr/bin/gcov-tool gcov-tool /usr/bin/gcov-tool-${GCC_RELEASE} \ - --slave /usr/bin/gcov-dump gcov-dump /usr/bin/gcov-dump-${GCC_RELEASE} \ - --slave /usr/bin/lto-dump lto-dump /usr/bin/lto-dump-${GCC_RELEASE} -sudo update-alternatives --auto cc -sudo update-alternatives --auto gcc +LLVM tools (`clang-tidy` and `clang-format`) are also pinned to version 22. + +Older compilers may fail to build the latest `develop` code: the codebase now +relies on C++23 features and has been adjusted for `clang-tidy`. +If the latest code doesn't build for you, update your build toolchain first. + +## Linux and macOS + +The **recommended way** to get a development environment on Linux and macOS is +the Nix development shell. It provides the exact tooling used in CI — `git`, +`python`, `conan`, `cmake`, `clang-tidy`, `clang-format`, and everything else — +with a single command and without installing anything system-wide: + +```bash +nix --experimental-features 'nix-command flakes' develop ``` -If you use different Linux distribution, hope the instruction above can guide -you in the right direction. We try to maintain compatibility with all recent -compiler releases, so if you use a rolling distribution like e.g. Arch or CentOS -then there is a chance that everything will "just work". +On **Linux**, Nix also provides the compiler (GCC). On **macOS**, the shell uses +your **system-wide Apple Clang** as the compiler, so you still need to manage +its version (see below). -## macOS +See [Using the Nix development shell](./nix.md) for installation and usage +details, including how to select a different compiler. -Open a Terminal and enter the below command to bring up a dialog to install -the command line developer tools. -Once it is finished, this command should return a version greater than the -minimum required (see [BUILD.md][]). +> [!NOTE] +> Using Nix is not mandatory. Any custom environment (Homebrew packages or +> anything else) will continue to work, but then it is up to you to keep it in +> sync with the environment used in CI. Nix unifies the development environment +> for everyone and synchronizes updates, which is why we recommend it. -``` +### macOS: managing the Apple Clang version + +Because the Nix shell uses the system-wide Apple Clang on macOS, the compiler +version is whatever your installed Xcode (or Command Line Tools) provides. The +following command should return a version greater than or equal to the +[minimum required](#tested-compiler-versions): + +```bash clang --version ``` -### Install Xcode Specific Version (Optional) - -If you develop other applications using XCode you might be consistently updating to the newest version of Apple Clang. -This will likely cause issues building xrpld. You may want to install a specific version of Xcode: +If you develop other applications using Xcode, you might be consistently +updating to the newest version of Apple Clang, which will likely cause issues +building xrpld. You may want to install and pin a specific version of Xcode: 1. **Download Xcode** - Visit [Apple Developer Downloads](https://developer.apple.com/download/more/) - Sign in with your Apple Developer account - - Search for an Xcode version that includes **Apple Clang (Expected Version)** + - Search for an Xcode version that includes the expected Apple Clang version - Download the `.xip` file -2. **Install and Configure Xcode** +2. **Install and configure Xcode** ```bash # Extract the .xip file and rename for version management @@ -79,62 +83,28 @@ This will likely cause issues building xrpld. You may want to install a specific export DEVELOPER_DIR=/Applications/Xcode_16.2.app/Contents/Developer ``` -The command line developer tools should include Git too: +## Windows -``` -git --version -``` +Nix is not available on Windows, so the required tools have to be installed +manually: -Install [Homebrew][], -use it to install [pyenv][], -use it to install Python, -and use it to install Conan: +- [Visual Studio 2022](https://visualstudio.microsoft.com/) with the + **"Desktop development with C++"** workload — this provides MSVC and the + "x64 Native Tools Command Prompt". +- [Git for Windows](https://git-scm.com/download/win) +- [Python 3.11](https://www.python.org/downloads/), or higher +- [Conan 2.17](https://conan.io/downloads.html), or higher +- [CMake 3.22](https://cmake.org/download/), or higher -[Homebrew]: https://brew.sh/ -[pyenv]: https://github.com/pyenv/pyenv - -``` -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -brew update -brew install xz -brew install pyenv -pyenv install 3.11 -pyenv global 3.11 -eval "$(pyenv init -)" -pip install 'conan' -``` - -Install CMake with Homebrew too: - -``` -brew install cmake -``` +> [!NOTE] +> Windows is used for development only and is not recommended for production. ## Clang-tidy -Clang-tidy is required to run static analysis checks locally (see [CONTRIBUTING.md](../../CONTRIBUTING.md)). -It is not required to build the project. Currently this project uses clang-tidy version 21. +`clang-tidy` is required to run static analysis checks locally (see +[CONTRIBUTING.md](../../CONTRIBUTING.md)). It is not required to build the +project. This project currently uses `clang-tidy` version 22. -### Linux - -LLVM 21 is not available in the default Debian 12 (Bookworm) repositories. -Install it using the official LLVM apt installer: - -``` -wget https://apt.llvm.org/llvm.sh -chmod +x llvm.sh -sudo ./llvm.sh 21 -sudo apt install --yes clang-tidy-21 -``` - -Then use `run-clang-tidy-21` when running clang-tidy locally. - -### macOS - -Install LLVM 21 via Homebrew: - -``` -brew install llvm@21 -``` - -Then use `run-clang-tidy` from the LLVM 21 Homebrew prefix when running clang-tidy locally. +On Linux and macOS, the [Nix development shell](./nix.md) provides `clang-tidy` +22 out of the box — run it via `run-clang-tidy`. No separate installation is +needed. diff --git a/docs/build/nix.md b/docs/build/nix.md index 33bb3711d0..2ae483aefe 100644 --- a/docs/build/nix.md +++ b/docs/build/nix.md @@ -2,9 +2,12 @@ This guide explains how to use Nix to set up a reproducible development environment for xrpld. Using Nix eliminates the need to manually install utilities and ensures consistent tooling across different machines. +**The Nix development shell is the recommended way to develop xrpld.** It unifies the development environment for everyone and synchronizes updates: the same tooling and compiler versions are used both here and in CI. Any custom environment (Homebrew packages or anything else) will continue to work, but then it is up to you to keep it in sync with the environment used in CI. + ## Benefits of Using Nix - **Reproducible environment**: Everyone gets the same versions of tools and compilers +- **Matches CI**: The Linux CI runs in Docker images built from this exact Nix environment - **No system pollution**: Dependencies are isolated and don't affect your system packages - **Multiple compiler versions**: Easily switch between different GCC and Clang versions - **Quick setup**: Get started with a single command @@ -28,11 +31,22 @@ This will: - Download and set up all required development tools (CMake, Ninja, Conan, etc.) - Configure the appropriate compiler for your platform: - - **macOS**: Apple Clang (default system compiler) - - **Linux**: GCC 15 + - **Linux**: GCC 15.2 (provided by Nix) + - **macOS**: Apple Clang (your system compiler) The first time you run this command, it will take a few minutes to download and build the environment. Subsequent runs will be much faster. +### Platform notes + +- **Linux**: `nix develop` gives you a shell with all the tooling necessary to + develop xrpld and with GCC 15.2 (also provided by Nix). There are no caveats. +- **macOS**: `nix develop` gives you a full environment too. The compiler is + your system-wide Apple Clang, while every other tool — including Conan — is + provided by Nix. Conan has no binary in the Nix cache for macOS, so it is + built from source the first time you enter the shell, which makes the initial + setup slower (this is handled automatically; see + [`nix/devshell.nix`](../../nix/devshell.nix)). + > [!TIP] > To avoid typing `--experimental-features 'nix-command flakes'` every time, you can permanently enable flakes by creating `~/.config/nix/nix.conf`: > @@ -51,7 +65,7 @@ The first time you run this command, it will take a few minutes to download and A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#gcc15`. Use `nix flake show` to see all the available development shells. -Use `nix develop .#no_compiler` to use the compiler from your system. +Use `nix develop .#no-compiler` to use the compiler from your system. ### Example Usage @@ -68,12 +82,28 @@ nix develop ### Using a different shell -`nix develop` opens bash by default. If you want to use another shell this could be done by adding `-c` flag. For example: +`nix develop` opens bash by default. To use another shell, pass it with the `-c` flag — this works with any shell, e.g. `zsh` or `fish`: ```bash +# Use zsh nix develop -c zsh + +# Use fish +nix develop -c fish + +# Use your login shell +nix develop -c "$SHELL" ``` +> [!WARNING] +> Your shell's interactive startup files (e.g. `config.fish`, `.zshrc`) may prepend other directories — most commonly Homebrew — to `$PATH`, which can shadow the tools provided by the Nix shell. After entering, verify that tools resolve into the Nix store: +> +> ```bash +> command -v cmake # should print a /nix/store/... path +> ``` +> +> If it doesn't, either adjust your shell configuration so it doesn't override `$PATH`, or use [direnv](#automatic-activation-with-direnv) (below), which loads the environment _after_ your shell config and so takes precedence regardless of the shell you use. + ## Building xrpld with Nix Once inside the Nix development shell, follow the standard [build instructions](../../BUILD.md#steps). The Nix shell provides all necessary tools (CMake, Ninja, Conan, etc.). @@ -82,6 +112,8 @@ Once inside the Nix development shell, follow the standard [build instructions]( [direnv](https://direnv.net/) or [nix-direnv](https://github.com/nix-community/nix-direnv) can automatically activate the Nix development shell when you enter the repository directory. +This is also the most robust way to use the environment from **any shell** (bash, zsh, fish, …): direnv stays in your current shell and loads the environment _after_ your shell's startup files have run, so the Nix-provided tools take precedence over anything your shell configuration adds to `$PATH`. To use it, install direnv for your shell, then add an `.envrc` containing `use flake` at the repository root and run `direnv allow`. + ## Conan and Prebuilt Packages Please note that there is no guarantee that binaries from conan cache will work when using nix. If you encounter any errors, please use `--build '*'` to force conan to compile everything from source: @@ -93,3 +125,8 @@ conan install .. --output-folder . --build '*' --settings build_type=Release ## Updating `flake.lock` file To update `flake.lock` to the latest revision use `nix flake update` command. + +## Troubleshooting + +See [Troubleshooting Nix problems](./nix_troubleshooting.md) for common issues, +such as `nix develop` failing inside Git worktrees. diff --git a/docs/build/nix_troubleshooting.md b/docs/build/nix_troubleshooting.md new file mode 100644 index 0000000000..ae5cb8059a --- /dev/null +++ b/docs/build/nix_troubleshooting.md @@ -0,0 +1,61 @@ +# Troubleshooting Nix problems + +Common issues encountered when using the [Nix development shell](./nix.md), and +how to resolve them. + +## Git worktrees + +If `nix develop` fails with an error like: + +``` +error: + … while fetching the input 'git+file:///path/to/rippled' + + error: opening Git repository "/path/to/rippled": unsupported extension name extensions.relativeworktrees (libgit2 error code = 6) +``` + +then your Nix is linked against a libgit2 older than **1.9.4**. Git 2.48+ writes +the `extensions.relativeWorktrees` config entry when a worktree is created with +relative paths (`git worktree add --relative-paths`, or with +`worktree.useRelativePaths=true`), and older libgit2 versions refuse to open a +repository that uses it. Nix uses libgit2 to read the flake, so evaluation +fails. + +> [!IMPORTANT] +> This entry is written to the **shared** repository config, so once any +> relative worktree exists, `nix develop` fails in the main checkout too — not +> just inside the worktree. + +### Workarounds + +These work today, with any Nix version: + +- bypass libgit2 with a `path:` flakeref: `nix develop "path:$PWD"` + (note: this copies the working tree to the store and ignores `.gitignore`); or +- create worktrees with absolute paths (omit `--relative-paths`); or +- clear the extension if you don't need relative worktrees: + `git config --unset extensions.relativeWorktrees`. + +### Permanent fix + +The fix is in [libgit2 1.9.4](https://github.com/libgit2/libgit2/releases/tag/v1.9.4), +so the real solution is a Nix that links against libgit2 `1.9.4` or newer. Check +which version yours links against: + +```bash +nix-store -qR "$(readlink -f "$(command -v nix)")" | grep libgit2 +``` + +> [!WARNING] +> `nix upgrade-nix` does **not** help yet. It installs the build from the +> official [`nix-fallback-paths`](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/installer/tools/nix-fallback-paths.nix), +> which is still linked against libgit2 `1.9.2` — there is no new upstream Nix +> release with the fix. (On some systems that build is even the exact store path +> you already have, making the upgrade a no-op.) + +nixpkgs has already rebuilt Nix against the fixed libgit2 (e.g. `nix-2.34.7+1`), +so the cleanest path is to reinstall Nix using your usual installation method +once it picks up that rebuild, then re-run the `grep libgit2` check above to +confirm it reports `1.9.4` or newer. + +Until then, prefer the workarounds above. diff --git a/nix/docker/Dockerfile b/nix/docker/Dockerfile index e6df48e18c..6d8980f897 100644 --- a/nix/docker/Dockerfile +++ b/nix/docker/Dockerfile @@ -71,7 +71,7 @@ if [ ! -e "${target}" ]; then fi EOF -COPY nix/docker/check-tools.sh /tmp/check-tools.sh +COPY bin/check-tools.sh /tmp/check-tools.sh RUN /tmp/check-tools.sh # Sanity-check that the g++/clang++ are able to build binaries, including sanitizer-instrumented ones. @@ -93,7 +93,7 @@ RUN if echo "${BASE_IMAGE}" | grep -qiE 'nixos'; then \ SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"] # Sanity-check that the built binaries run correctly in the vanilla base image, with the necessary sanitizer runtime libraries installed. -COPY nix/docker/install-sanitizer-libs.sh /tmp/install-sanitizer-libs.sh +COPY bin/install-sanitizer-libs.sh /tmp/install-sanitizer-libs.sh COPY nix/docker/test_files/run-test-binaries.sh /tmp/run-test-binaries.sh COPY --from=final /tmp/bins /tmp/bins diff --git a/nix/docker/README.md b/nix/docker/README.md new file mode 100644 index 0000000000..085433b758 --- /dev/null +++ b/nix/docker/README.md @@ -0,0 +1,90 @@ +# Nix CI Docker images + +This directory builds the Docker images used by xrpld's Linux CI. Each image +bundles the **exact same toolchain that the Nix development shell provides** +(see [`docs/build/nix.md`](../../docs/build/nix.md)), so what runs in CI matches +what developers get locally from `nix develop`. + +The toolchain (CMake, Ninja, Conan, GCC, Clang, clang-tidy, the +sanitizer/coverage tools, …) is defined in [`nix/packages.nix`](../packages.nix) +and assembled for CI by [`nix/ci-env.nix`](../ci-env.nix). The Docker build +turns that Nix environment into an ordinary container image layered on top of a +conventional base image (Ubuntu, Debian, RHEL, or `nixos/nix`). + +## Images + +The images are built by the [`build-nix-images.yml`](../../.github/workflows/build-nix-images.yml) +workflow and pushed to `ghcr.io/xrplf/xrpld/nix-`. The `` is +selected through the `BASE_IMAGE` build argument; the base images are the +**oldest supported version** of each distribution we target: + +| Image | `BASE_IMAGE` | Notes | +| ------------ | -------------------------------------------- | -------------------------------------------------- | +| `nix-nixos` | `nixos/nix:latest` | Build/lint only; binaries are not run (see below). | +| `nix-ubuntu` | `ubuntu:20.04` | Oldest supported Ubuntu (glibc 2.31). | +| `nix-debian` | `debian:bookworm` | | +| `nix-rhel` | `registry.access.redhat.com/ubi9/ubi:latest` | | + +All images carry the full toolchain on `PATH` (via `/nix/ci-env/bin`) plus the +CA bundle shipped in the Nix environment, so HTTPS clients (git, curl, Conan) +work without `ca-certificates` being installed in the base image. + +## Build stages + +[`Dockerfile`](./Dockerfile) is a multi-stage build: + +1. **`builder`** — On a `nixos/nix` builder, evaluate the flake and build the + CI environment (`nix/ci-env.nix`). The resulting Nix store closure (the + complete set of store paths the toolchain depends on) is copied into a + staging directory. +2. **`final`** — Start from `BASE_IMAGE`, copy in the Nix store closure and the + `ci-env` symlink tree, and wire up `PATH` and the CA bundle. It then: + - installs the dynamic linker if the base image lacks one (see + [How libc is handled](#how-libc-is-handled)), + - runs [`bin/check-tools.sh`](../../bin/check-tools.sh) to verify every + expected tool is present and runnable, and + - compiles the C++ test programs in + [`test_files/`](./test_files) with both `g++` and `clang++`, and sanitizers. +3. **`tester`** — Start again from a clean `BASE_IMAGE` (no Nix toolchain), + install only the sanitizer runtime libraries + ([`install-sanitizer-libs.sh`](./install-sanitizer-libs.sh)), and run the + binaries compiled in `final`. This proves the binaries built with the Nix + toolchain actually run on a vanilla base image. On `nixos/nix` this step is + skipped (the binaries are patched for a conventional FHS loader). +4. **Output** — The final image is gated on the tester succeeding: it copies a + sentinel file out of `tester`, so a failed test run fails the whole build. + +## How libc is handled + +The goal is for binaries built in these images to run on the **oldest supported +base image** (Ubuntu 20.04, glibc 2.31) and newer — without the developer's Nix +toolchain being present at runtime. Two pieces make that work: + +- **Compilers linked against an old glibc.** The Nix CI environment does not use + nixpkgs' current glibc. Instead it pins a 2020 nixpkgs snapshot whose primary + glibc is **2.31** (matching Ubuntu 20.04), via the `nixpkgs-custom-glibc` + flake input. GCC, Clang, binutils and compiler-rt are all rebuilt/wrapped + against this custom glibc (see [`nix/ci-env.nix`](../ci-env.nix)). As a result + the libraries they emit (`libstdc++`, `libgcc_s`, the sanitizer runtimes) + reference only symbols available in glibc 2.31. + +- **An expected dynamic linker in the image.** + Binaries built in Nix environments reference a dynamic linker from Nix store paths, which won't be present in the base image. However, + [`loader-path.sh`](./loader-path.sh) reports the expected loader path for the + current architecture, so we can patch the binaries to use the correct loader. + +The build then verifies all of this end to end: the test programs in +`test_files/` (a regular binary plus ASan/TSan/UBSan variants) are compiled in +`final`, their `PT_INTERP` is patched to the target loader, and they are run in +the clean `tester` stage to confirm each emits the expected sanitizer +diagnostic on a stock base image. + +## Files + +| File | Purpose | +| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- | +| [`./Dockerfile`](./Dockerfile) | Multi-stage build described above. | +| [`./loader-path.sh`](./loader-path.sh) | Print the dynamic-linker (`PT_INTERP`) path for the current architecture. | +| [`./test_files/`](./test_files) | C++ sources and scripts to compile and run the sanitizer smoke tests. | +| [`/bin/check-tools.sh`](../../bin/check-tools.sh) | Verify every expected tools are present and runnable. | +| [`/bin/install-sanitizer-libs.sh`](../../bin/install-sanitizer-libs.sh) | Install `libasan`/`libtsan`/`libubsan` runtimes on the supported base images. | diff --git a/nix/docker/check-tools.sh b/nix/docker/check-tools.sh deleted file mode 100755 index a46c2dd997..0000000000 --- a/nix/docker/check-tools.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -# Verify that every tool expected in the Nix CI env is present and runnable. -set -euo pipefail - -ccache --version -clang --version -clang++ --version -clang-format --version -ClangBuildAnalyzer --version -cmake --version -conan --version -curl --version -doxygen --version -file --version -g++ --version -gcc --version -gcov --version -gcovr --version -gh --version -git --version -git-cliff --version -gpg --version -less --version -make --version -mold --version -netstat --version -ninja --version -perl --version -pkg-config --version -pre-commit --version -python3 --version -run-clang-tidy --help -vim --version - -# A simple test to verify that git can clone a repository over HTTPS -# (i.e. the CA bundle is wired up). Clone to a temp dir and clean up. -tmp_clone="$(mktemp -d)" -git clone --depth 1 https://github.com/XRPLF/actions.git "${tmp_clone}/actions" -rm -rf "${tmp_clone}" From 7b9d55326db2fc9f5da4d16f22985c4670af8082 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 16 Jun 2026 18:35:33 +0100 Subject: [PATCH 117/135] build: Add zip to Nix images (#7551) --- nix/packages.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nix/packages.nix b/nix/packages.nix index 5a7f20ec49..612b1cb215 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -33,5 +33,6 @@ in python3 runClangTidy vim + zip ]; } From 258d5e1f1f295547d8968551ef97a61c45cbaf06 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Tue, 16 Jun 2026 18:47:40 -0400 Subject: [PATCH 118/135] clang-tidy: nodiscard rendezvous, headers, const correctness, {}s --- src/test/app/LedgerMaster_test.cpp | 4 +- src/test/app/SHAMapStore_test.cpp | 39 ++++++++++---------- src/xrpld/app/ledger/detail/LedgerMaster.cpp | 1 + src/xrpld/app/misc/SHAMapStore.h | 2 +- src/xrpld/app/misc/SHAMapStoreImp.cpp | 7 ++-- 5 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/test/app/LedgerMaster_test.cpp b/src/test/app/LedgerMaster_test.cpp index da91206966..738358eb90 100644 --- a/src/test/app/LedgerMaster_test.cpp +++ b/src/test/app/LedgerMaster_test.cpp @@ -140,7 +140,7 @@ class LedgerMaster_test : public beast::unit_test::Suite LedgerIndex minSeq = 2; LedgerIndex maxSeq = env.closed()->header().seq; auto& store = env.app().getSHAMapStore(); - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); LedgerIndex lastRotated = store.getLastRotated(); BEAST_EXPECTS(maxSeq == 3, to_string(maxSeq)); BEAST_EXPECTS(lm.getCompleteLedgers() == "2-3", lm.getCompleteLedgers()); @@ -159,7 +159,7 @@ class LedgerMaster_test : public beast::unit_test::Suite env(noop(alice)); } env.close(); - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); ++maxSeq; diff --git a/src/test/app/SHAMapStore_test.cpp b/src/test/app/SHAMapStore_test.cpp index a814068aef..8f6b4919b1 100644 --- a/src/test/app/SHAMapStore_test.cpp +++ b/src/test/app/SHAMapStore_test.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -145,11 +146,11 @@ class SHAMapStore_test : public beast::unit_test::Suite auto& store = env.app().getSHAMapStore(); int ledgerSeq = 3; - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); BEAST_EXPECT(!store.getLastRotated()); env.close(); - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); auto ledger = env.rpc("ledger", "validated"); BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++))); @@ -229,7 +230,7 @@ public: BEAST_EXPECT(goodLedger(env, ledger, std::to_string(kDeleteInterval + 4))); } - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); BEAST_EXPECT(store.getLastRotated() == kDeleteInterval + 3); lastRotated = store.getLastRotated(); @@ -256,7 +257,7 @@ public: !getHash(ledgers[i]).empty()); } - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); BEAST_EXPECT(store.getLastRotated() == kDeleteInterval + lastRotated); @@ -294,7 +295,7 @@ public: BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true)); } - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); // The database will always have back to ledger 2, // regardless of lastRotated. @@ -309,7 +310,7 @@ public: BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true)); } - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); ledgerCheck(env, ledgerSeq - lastRotated, lastRotated); BEAST_EXPECT(lastRotated != store.getLastRotated()); @@ -325,7 +326,7 @@ public: BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true)); } - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); ledgerCheck(env, kDeleteInterval + 1, lastRotated); BEAST_EXPECT(lastRotated != store.getLastRotated()); @@ -364,7 +365,7 @@ public: BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true)); } - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); ledgerCheck(env, ledgerSeq - 2, 2); BEAST_EXPECT(lastRotated == store.getLastRotated()); @@ -374,7 +375,7 @@ public: BEAST_EXPECT(!RPC::containsError(canDelete[jss::result])); BEAST_EXPECT(canDelete[jss::result][jss::can_delete] == ledgerSeq + (kDeleteInterval / 2)); - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); ledgerCheck(env, ledgerSeq - 2, 2); BEAST_EXPECT(store.getLastRotated() == lastRotated); @@ -387,7 +388,7 @@ public: BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true)); } - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); ledgerCheck(env, ledgerSeq - lastRotated, lastRotated); @@ -403,7 +404,7 @@ public: BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true)); } - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); BEAST_EXPECT(store.getLastRotated() == lastRotated); @@ -415,7 +416,7 @@ public: BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true)); } - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); ledgerCheck(env, ledgerSeq - firstBatch, firstBatch); @@ -437,7 +438,7 @@ public: BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true)); } - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); BEAST_EXPECT(store.getLastRotated() == lastRotated); @@ -449,7 +450,7 @@ public: BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true)); } - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); ledgerCheck(env, ledgerSeq - lastRotated, lastRotated); @@ -470,7 +471,7 @@ public: BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq), true)); } - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); BEAST_EXPECT(store.getLastRotated() == lastRotated); @@ -482,7 +483,7 @@ public: BEAST_EXPECT(goodLedger(env, ledger, std::to_string(ledgerSeq++), true)); } - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); ledgerCheck(env, ledgerSeq - lastRotated, lastRotated); @@ -632,7 +633,7 @@ public: LedgerIndex minSeq = 2; LedgerIndex maxSeq = env.closed()->header().seq; auto& store = env.app().getSHAMapStore(); - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); LedgerIndex lastRotated = store.getLastRotated(); BEAST_EXPECTS(maxSeq == 3, std::to_string(maxSeq)); BEAST_EXPECTS(lm.getCompleteLedgers() == "2-3", lm.getCompleteLedgers()); @@ -651,7 +652,7 @@ public: env(noop(alice)); } env.close(); - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); ++maxSeq; @@ -739,7 +740,7 @@ public: lm.setLedgerRangePresent(deleteSeq, deleteSeq); // Wait for the rotation to finish - store.rendezvous(); + BEAST_EXPECT(store.rendezvous()); minSeq = lastRotated; lastRotated = deleteSeq + 1; diff --git a/src/xrpld/app/ledger/detail/LedgerMaster.cpp b/src/xrpld/app/ledger/detail/LedgerMaster.cpp index 456f55a182..80e07111e4 100644 --- a/src/xrpld/app/ledger/detail/LedgerMaster.cpp +++ b/src/xrpld/app/ledger/detail/LedgerMaster.cpp @@ -56,6 +56,7 @@ #include #include +#include #include #include diff --git a/src/xrpld/app/misc/SHAMapStore.h b/src/xrpld/app/misc/SHAMapStore.h index 30736186c5..bf0f62f3ca 100644 --- a/src/xrpld/app/misc/SHAMapStore.h +++ b/src/xrpld/app/misc/SHAMapStore.h @@ -28,7 +28,7 @@ public: virtual void start() = 0; - virtual bool + [[nodiscard]] virtual bool rendezvous(std::optional const& timeout = {}) const = 0; virtual void diff --git a/src/xrpld/app/misc/SHAMapStoreImp.cpp b/src/xrpld/app/misc/SHAMapStoreImp.cpp index 1b3daa22cd..86e983a4d7 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.cpp +++ b/src/xrpld/app/misc/SHAMapStoreImp.cpp @@ -244,9 +244,10 @@ SHAMapStoreImp::rendezvous(std::optional const& timeo std::unique_lock lock(mutex_); if (timeout) + { return rendezvous_.wait_for(lock, *timeout, notWorking); - else - rendezvous_.wait(lock, notWorking); + } + rendezvous_.wait(lock, notWorking); return true; } @@ -658,7 +659,7 @@ SHAMapStoreImp::healthWait() // lock auto const lowerBound = lastGoodValidatedLedger_; - ScopeUnlock unlock(lock); + ScopeUnlock const unlock(lock); auto const stream = mode != OperatingMode::FULL || age > ageThreshold ? journal_.warn() : journal_.info(); From 45ddc1d868cad0aa1ec9fedbdff3d4eaf882eb72 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 17 Jun 2026 00:13:33 +0100 Subject: [PATCH 119/135] build: Add git-lfs to Nix images (#7561) --- nix/packages.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nix/packages.nix b/nix/packages.nix index 612b1cb215..79de8fac89 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -19,6 +19,7 @@ in gh git git-cliff + git-lfs gnumake gnupg # needed for signing commits & codecov/codecov-action llvmPackages_22.clang-tools From 5de434436e339bfdd91b31319ca87ff8329a3465 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 17 Jun 2026 11:02:17 +0100 Subject: [PATCH 120/135] ci: Make clang-tidy workflow adjustments to stay in sync with Clio (#7563) --- .github/workflows/reusable-clang-tidy.yml | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index 34fa860e12..5d2325cb1d 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -20,9 +20,12 @@ env: BUILD_DIR: build BUILD_TYPE: Debug # Debug so that ASSERTS and such participate in clang-tidy check - OUTPUT_FILE: clang-tidy-output.txt - DIFF_FILE: clang-tidy-git-diff.txt - ISSUE_FILE: clang-tidy-issue.md + OUTPUT_FILE: /tmp/clang-tidy-output.txt + FILTERED_OUTPUT_FILE: /tmp/clang-tidy-filtered-output.txt + DIFF_FILE: /tmp/clang-tidy-git-diff.txt + ISSUE_FILE: /tmp/clang-tidy-issue.md + + COMPILER: clang jobs: determine-files: @@ -59,7 +62,7 @@ jobs: - name: Set compiler environment uses: ./.github/actions/set-compiler-env with: - compiler: clang + compiler: ${{ env.COMPILER }} - name: Setup Conan uses: ./.github/actions/setup-conan @@ -150,21 +153,21 @@ jobs: run: | if [ -f "${OUTPUT_FILE}" ]; then # Extract lines containing 'error:', 'warning:', or 'note:' - grep -E '(error:|warning:|note:)' "${OUTPUT_FILE}" >filtered-output.txt || true + grep -E '(error:|warning:|note:)' "${OUTPUT_FILE}" >"${FILTERED_OUTPUT_FILE}" || true # If filtered output is empty, use original (might be a different error format) - if [ ! -s filtered-output.txt ]; then - cp "${OUTPUT_FILE}" filtered-output.txt + if [ ! -s "${FILTERED_OUTPUT_FILE}" ]; then + cp "${OUTPUT_FILE}" "${FILTERED_OUTPUT_FILE}" fi # Truncate if too large - head -c 60000 filtered-output.txt >>"${ISSUE_FILE}" - if [ "$(wc -c >"${ISSUE_FILE}" + if [ "$(wc -c <"${FILTERED_OUTPUT_FILE}")" -gt 60000 ]; then echo "" >>"${ISSUE_FILE}" echo "... (output truncated, see artifacts for full output)" >>"${ISSUE_FILE}" fi - rm filtered-output.txt + rm "${FILTERED_OUTPUT_FILE}" else echo "No output file found" >>"${ISSUE_FILE}" fi From 044ca7719dda036f1950e599cd636ce8276eb67e Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:58:01 +0100 Subject: [PATCH 121/135] release: Bump version to 3.3.0-b0 Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> --- 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 c488bb20de..820936a22d 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -23,7 +23,7 @@ namespace { //------------------------------------------------------------------------------ // clang-format off // NOLINTNEXTLINE(readability-identifier-naming) -char const* const versionString = "3.2.0" +char const* const versionString = "3.3.0-b0" // clang-format on ; From 7e0ff536f55b684f6c8e73028c20758c03e334bc Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Wed, 17 Jun 2026 13:31:04 +0100 Subject: [PATCH 122/135] refactor: Rerevert "Explicitly trim the heap after cache sweeps (#6022)" Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> --- src/xrpld/app/main/Application.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index af32cf0ba6..67b5e30eb7 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -41,6 +41,7 @@ #include #include +#include #include #include #include @@ -1088,6 +1089,8 @@ public: << "; size after: " << cachedSLEs_.size(); } + mallocTrim("doSweep", journal_); + // Set timer to do another sweep later. setSweepTimer(); } From cb2642be050b2683c929d03ab10d85c6cb01f933 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 17 Jun 2026 14:54:46 +0100 Subject: [PATCH 123/135] build: Add graphviz to Nix images (#7566) --- nix/packages.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nix/packages.nix b/nix/packages.nix index 79de8fac89..6202168733 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -22,6 +22,7 @@ in git-lfs gnumake gnupg # needed for signing commits & codecov/codecov-action + graphviz llvmPackages_22.clang-tools less # needed for git diff mold From f07de6c4540e91ed312404bb8f511fdc562ea9b5 Mon Sep 17 00:00:00 2001 From: Michael Legleux Date: Wed, 17 Jun 2026 06:54:55 -0700 Subject: [PATCH 124/135] ci: Disable assertions on Release builds (#7443) --- .github/scripts/strategy-matrix/generate.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index 6353567f27..a269cb25d4 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -20,8 +20,6 @@ _SANITIZER_SUFFIX: dict[str, str] = { def get_cmake_args(build_type: str, extra_args: str) -> str: """Get the full list of CMake arguments for a config.""" args = _BASE_CMAKE_ARGS.copy() - if build_type == "Release": - args.append("-Dassert=ON") if extra_args: args.extend(extra_args.split()) return " ".join(args) From 480676d0bf9ec7dc6b35267c63dfd162746b5d3b Mon Sep 17 00:00:00 2001 From: solunolab Date: Wed, 17 Jun 2026 21:55:00 +0800 Subject: [PATCH 125/135] docs: Fix some comments to improve readability (#7405) Signed-off-by: solunolab Co-authored-by: Ayaz Salikhov --- include/xrpl/basics/sanitizers.h | 2 +- include/xrpl/ledger/helpers/CredentialHelpers.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/xrpl/basics/sanitizers.h b/include/xrpl/basics/sanitizers.h index b954952848..7937344b53 100644 --- a/include/xrpl/basics/sanitizers.h +++ b/include/xrpl/basics/sanitizers.h @@ -4,7 +4,7 @@ /* ASAN flags some false positives with sudden jumps in control flow, like exceptions, or when encountering coroutine stack switches. This macro can be used to disable ASAN - intrumentation for specific functions. + instrumentation for specific functions. */ #if defined(__GNUC__) || defined(__clang__) #define XRPL_NO_SANITIZE_ADDRESS __attribute__((no_sanitize("address", "hwaddress"))) diff --git a/include/xrpl/ledger/helpers/CredentialHelpers.h b/include/xrpl/ledger/helpers/CredentialHelpers.h index 549644764f..0cfbbde538 100644 --- a/include/xrpl/ledger/helpers/CredentialHelpers.h +++ b/include/xrpl/ledger/helpers/CredentialHelpers.h @@ -36,13 +36,13 @@ checkFields(STTx const& tx, beast::Journal j); TER valid(STTx const& tx, ReadView const& view, AccountID const& src, beast::Journal j); -// Check if subject has any credential maching the given domain. If you call it +// Check if subject has any credential matching the given domain. If you call it // in preclaim and it returns tecEXPIRED, you should call verifyValidDomain in // doApply. This will ensure that expired credentials are deleted. TER validDomain(ReadView const& view, uint256 domainID, AccountID const& subject); -// This function is only called when we about to return tecNO_PERMISSION +// This function is only called when we are about to return tecNO_PERMISSION // because all the checks for the DepositPreauth authorization failed. TER authorizedDepositPreauth(ReadView const& view, STVector256 const& ctx, AccountID const& dst); @@ -58,7 +58,7 @@ checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j); } // namespace credentials -// Check expired credentials and for credentials maching DomainID of the ledger +// Check expired credentials and for credentials matching DomainID of the ledger // object TER verifyValidDomain(ApplyView& view, AccountID const& account, uint256 domainID, beast::Journal j); From 772ea80a25227b5e9fb75ea0cdb62723b099cd33 Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Wed, 17 Jun 2026 19:20:54 -0400 Subject: [PATCH 126/135] fix: Use template for granular delegation permissions (#6613) Co-authored-by: Bart --- include/xrpl/ledger/helpers/DelegateHelpers.h | 10 +- include/xrpl/protocol/Permissions.h | 80 +++- include/xrpl/protocol/detail/features.macro | 2 +- .../xrpl/protocol/detail/permissions.macro | 89 ++-- include/xrpl/tx/Transactor.h | 63 ++- .../xrpl/tx/transactors/account/AccountSet.h | 3 - include/xrpl/tx/transactors/payment/Payment.h | 5 +- .../tx/transactors/token/MPTokenIssuanceSet.h | 3 - include/xrpl/tx/transactors/token/TrustSet.h | 5 +- src/libxrpl/protocol/Permissions.cpp | 317 +++++++++----- src/libxrpl/protocol/STTx.cpp | 2 +- src/libxrpl/tx/Transactor.cpp | 36 +- src/libxrpl/tx/applySteps.cpp | 3 +- .../tx/transactors/account/AccountSet.cpp | 51 --- .../tx/transactors/delegate/DelegateUtils.cpp | 12 +- .../tx/transactors/payment/Payment.cpp | 30 +- .../transactors/token/MPTokenIssuanceSet.cpp | 36 -- src/libxrpl/tx/transactors/token/TrustSet.cpp | 44 +- src/test/app/Delegate_test.cpp | 407 +++++++++++++++++- 19 files changed, 851 insertions(+), 347 deletions(-) diff --git a/include/xrpl/ledger/helpers/DelegateHelpers.h b/include/xrpl/ledger/helpers/DelegateHelpers.h index 9cdad7173d..a517eefdaa 100644 --- a/include/xrpl/ledger/helpers/DelegateHelpers.h +++ b/include/xrpl/ledger/helpers/DelegateHelpers.h @@ -23,13 +23,9 @@ checkTxPermission(SLE::const_ref delegate, STTx const& tx); * @param delegate The delegate account. * @param type Used to determine which granted granular permissions to load, * based on the transaction type. - * @param granularPermissions Granted granular permissions tied to the - * transaction type. + * @return the granted granular permissions tied to the transaction type. */ -void -loadGranularPermission( - SLE::const_ref delegate, - TxType const& type, - std::unordered_set& granularPermissions); +std::unordered_set +getGranularPermission(SLE::const_ref delegate, TxType const& type); } // namespace xrpl diff --git a/include/xrpl/protocol/Permissions.h b/include/xrpl/protocol/Permissions.h index 5d56fa4461..eb161ef7ad 100644 --- a/include/xrpl/protocol/Permissions.h +++ b/include/xrpl/protocol/Permissions.h @@ -7,8 +7,13 @@ #include #include #include +#include +#include namespace xrpl { + +class STTx; + /** * We have both transaction type permissions and granular type permissions. * Since we will reuse the TransactionFormats to parse the Transaction @@ -19,15 +24,15 @@ namespace xrpl { // Macro-generated, complex // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) enum GranularPermissionType : std::uint32_t { -#pragma push_macro("PERMISSION") -#undef PERMISSION +#pragma push_macro("GRANULAR_PERMISSION") +#undef GRANULAR_PERMISSION -#define PERMISSION(type, txType, value) type = (value), +#define GRANULAR_PERMISSION(name, txType, value, ...) name = (value), #include -#undef PERMISSION -#pragma pop_macro("PERMISSION") +#undef GRANULAR_PERMISSION +#pragma pop_macro("GRANULAR_PERMISSION") }; // Injected bare enumerators (xrpl::delegable / xrpl::notDelegable) are required by preprocessor @@ -40,15 +45,30 @@ class Permission private: Permission(); - std::unordered_map txFeatureMap_; + struct GranularPermissionEntry + { + std::string name; + TxType txType; + std::uint32_t permittedFlags; + SOTemplate permittedFields; - std::unordered_map delegableTx_; + GranularPermissionEntry( + std::string name, + TxType txType, + std::uint32_t permittedFlags, + std::vector fields); + }; - std::unordered_map granularPermissionMap_; + struct TxDelegationEntry + { + uint256 amendment; + Delegation delegable{NotDelegable}; + }; - std::unordered_map granularNameMap_; - - std::unordered_map granularTxTypeMap_; + std::unordered_set granularTxTypes_; + std::unordered_map txDelegationMap_; + std::unordered_map granularPermissionsByName_; + std::unordered_map granularPermissions_; public: static Permission const& @@ -59,30 +79,52 @@ public: operator=(Permission const&) = delete; [[nodiscard]] std::optional - getPermissionName(std::uint32_t const value) const; + getPermissionName(std::uint32_t value) const; [[nodiscard]] std::optional getGranularValue(std::string const& name) const; [[nodiscard]] std::optional - getGranularName(GranularPermissionType const& value) const; + getGranularName(GranularPermissionType value) const; [[nodiscard]] std::optional - getGranularTxType(GranularPermissionType const& gpType) const; + getGranularTxType(GranularPermissionType gpType) const; + // Returns a reference to avoid copying uint256 - 32 bytes. std::optional + // cannot hold references directly, so std::reference_wrapper is used. [[nodiscard]] std::optional> getTxFeature(TxType txType) const; [[nodiscard]] bool - isDelegable(std::uint32_t const& permissionValue, Rules const& rules) const; + isDelegable(std::uint32_t permissionValue, Rules const& rules) const; + + [[nodiscard]] bool + hasGranularPermissions(TxType txType) const; // for tx level permission, permission value is equal to tx type plus one - static uint32_t - txToPermissionType(TxType const& type); + [[nodiscard]] static uint32_t + txToPermissionType(TxType type); // tx type value is permission value minus one - static TxType - permissionToTxType(uint32_t const& value); + [[nodiscard]] static TxType + permissionToTxType(std::uint32_t value); + + /** + * @brief Verifies a delegated transaction against its granular permission template. + * + * @note WARNING: Do not move this check before standard transaction-level + * format checks, which is in preclaim. This function assumes the transaction's + * base structural integrity (fees, sequence, signatures) has already been + * validated. + * + * @param tx The transaction to verify. + * @param heldPermissions The granular permissions that the sender hold. + * @return true if the transaction fields and flags comply with the granular template. + */ + [[nodiscard]] bool + checkGranularSandbox( + STTx const& tx, + std::unordered_set const& heldPermissions) const; }; } // namespace xrpl diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index d3500ab144..d25b0b1f2c 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -21,7 +21,7 @@ XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultN XRPL_FIX (Cleanup3_1_3, Supported::Yes, VoteBehavior::DefaultYes) XRPL_FIX (BatchInnerSigs, Supported::No, VoteBehavior::DefaultNo) XRPL_FEATURE(LendingProtocol, Supported::Yes, VoteBehavior::DefaultNo) -XRPL_FEATURE(PermissionDelegationV1_1, Supported::No, VoteBehavior::DefaultNo) +XRPL_FEATURE(PermissionDelegationV1_1, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FIX (DirectoryLimit, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FIX (IncludeKeyletFields, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FEATURE(DynamicMPT, Supported::No, VoteBehavior::DefaultNo) diff --git a/include/xrpl/protocol/detail/permissions.macro b/include/xrpl/protocol/detail/permissions.macro index 729861a013..35532a03ca 100644 --- a/include/xrpl/protocol/detail/permissions.macro +++ b/include/xrpl/protocol/detail/permissions.macro @@ -1,49 +1,74 @@ -#if !defined(PERMISSION) -#error "undefined macro: PERMISSION" +#if !defined(GRANULAR_PERMISSION) +#error "undefined macro: GRANULAR_PERMISSION" #endif /** - * PERMISSION(name, type, txType, value) + * GRANULAR_PERMISSION(name, txType, value, allowedFlags, allowedFields) * - * This macro defines a permission: - * name: the name of the permission. - * type: the GranularPermissionType enum. - * txType: the corresponding TxType for this permission. - * value: the uint32 numeric value for the enum type. + * Defines a granular permission: + * name: the granular permission name. + * txType: the corresponding TxType for this permission. + * value: the uint32 numeric value for the enum type. + * allowedFlags: transaction flags permitted under this permission. + * allowedFields: transaction fields permitted under this permission. */ -/** This permission grants the delegated account the ability to authorize a trustline. */ -PERMISSION(TrustlineAuthorize, ttTRUST_SET, 65537) +/** Grants the ability to authorize a trustline. */ +GRANULAR_PERMISSION(TrustlineAuthorize, ttTRUST_SET, 65537, tfUniversal | tfSetfAuth, + ({{sfLimitAmount, SoeRequired}})) -/** This permission grants the delegated account the ability to freeze a trustline. */ -PERMISSION(TrustlineFreeze, ttTRUST_SET, 65538) +/** Grants the ability to freeze a trustline. */ +GRANULAR_PERMISSION(TrustlineFreeze, ttTRUST_SET, 65538, tfUniversal | tfSetFreeze, + ({{sfLimitAmount, SoeRequired}})) -/** This permission grants the delegated account the ability to unfreeze a trustline. */ -PERMISSION(TrustlineUnfreeze, ttTRUST_SET, 65539) +/** Grants the ability to unfreeze a trustline. */ +GRANULAR_PERMISSION(TrustlineUnfreeze, ttTRUST_SET, 65539, tfUniversal | tfClearFreeze, + ({{sfLimitAmount, SoeRequired}})) -/** This permission grants the delegated account the ability to set Domain. */ -PERMISSION(AccountDomainSet, ttACCOUNT_SET, 65540) +/** Grants the ability to set Domain. */ +GRANULAR_PERMISSION(AccountDomainSet, ttACCOUNT_SET, 65540, tfUniversal, + ({{sfDomain, SoeOptional}})) -/** This permission grants the delegated account the ability to set EmailHashSet. */ -PERMISSION(AccountEmailHashSet, ttACCOUNT_SET, 65541) +/** Grants the ability to set EmailHash. */ +GRANULAR_PERMISSION(AccountEmailHashSet, ttACCOUNT_SET, 65541, tfUniversal, + ({{sfEmailHash, SoeOptional}})) -/** This permission grants the delegated account the ability to set MessageKey. */ -PERMISSION(AccountMessageKeySet, ttACCOUNT_SET, 65542) +/** Grants the ability to set MessageKey. */ +GRANULAR_PERMISSION(AccountMessageKeySet, ttACCOUNT_SET, 65542, tfUniversal, + ({{sfMessageKey, SoeOptional}})) -/** This permission grants the delegated account the ability to set TransferRate. */ -PERMISSION(AccountTransferRateSet, ttACCOUNT_SET, 65543) +/** Grants the ability to set TransferRate. */ +GRANULAR_PERMISSION(AccountTransferRateSet, ttACCOUNT_SET, 65543, tfUniversal, + ({{sfTransferRate, SoeOptional}})) -/** This permission grants the delegated account the ability to set TickSize. */ -PERMISSION(AccountTickSizeSet, ttACCOUNT_SET, 65544) +/** Grants the ability to set TickSize. */ +GRANULAR_PERMISSION(AccountTickSizeSet, ttACCOUNT_SET, 65544, tfUniversal, + ({{sfTickSize, SoeOptional}})) -/** This permission grants the delegated account the ability to mint payment, which means sending a payment for a currency where the sending account is the issuer. */ -PERMISSION(PaymentMint, ttPAYMENT, 65545) +/** Grants the ability to mint payment (sending account is the issuer). Cross-currency payments are disallowed. */ +GRANULAR_PERMISSION(PaymentMint, ttPAYMENT, 65545, tfUniversal, + ({{sfDestination, SoeRequired}, + {sfAmount, SoeRequired}, + {sfSendMax, SoeOptional}, + {sfInvoiceID, SoeOptional}, + {sfDestinationTag, SoeOptional}, + {sfCredentialIDs, SoeOptional}})) -/** This permission grants the delegated account the ability to burn payment, which means sending a payment for a currency where the destination account is the issuer */ -PERMISSION(PaymentBurn, ttPAYMENT, 65546) +/** Grants the ability to burn payment (destination account is the issuer). Cross-currency payments are disallowed. */ +GRANULAR_PERMISSION(PaymentBurn, ttPAYMENT, 65546, tfUniversal, + ({{sfDestination, SoeRequired}, + {sfAmount, SoeRequired}, + {sfSendMax, SoeOptional}, + {sfInvoiceID, SoeOptional}, + {sfDestinationTag, SoeOptional}, + {sfCredentialIDs, SoeOptional}})) -/** This permission grants the delegated account the ability to lock MPToken. */ -PERMISSION(MPTokenIssuanceLock, ttMPTOKEN_ISSUANCE_SET, 65547) +/** Grants the ability to lock an MPToken. */ +GRANULAR_PERMISSION(MPTokenIssuanceLock, ttMPTOKEN_ISSUANCE_SET, 65547, tfUniversal | tfMPTLock, + ({{sfMPTokenIssuanceID, SoeRequired}, + {sfHolder, SoeOptional}})) -/** This permission grants the delegated account the ability to unlock MPToken. */ -PERMISSION(MPTokenIssuanceUnlock, ttMPTOKEN_ISSUANCE_SET, 65548) +/** Grants the ability to unlock an MPToken. */ +GRANULAR_PERMISSION(MPTokenIssuanceUnlock, ttMPTOKEN_ISSUANCE_SET, 65548, tfUniversal | tfMPTUnlock, + ({{sfMPTokenIssuanceID, SoeRequired}, + {sfHolder, SoeOptional}})) diff --git a/include/xrpl/tx/Transactor.h b/include/xrpl/tx/Transactor.h index 86b1e856b3..a27d638107 100644 --- a/include/xrpl/tx/Transactor.h +++ b/include/xrpl/tx/Transactor.h @@ -222,8 +222,63 @@ public: return tesSUCCESS; } + /** + * This function can be overridden to introduce additional semantic constraints beyond the + * granular template validation for granular permissions. It is called by the base + * invokeCheckPermission method only after the transaction has successfully passed + * checkGranularSandbox. + */ static NotTEC - checkPermission(ReadView const& view, STTx const& tx); + checkGranularSemantics( + ReadView const& view, + STTx const& tx, + std::unordered_set const& heldGranularPermissions) + { + return tesSUCCESS; + } + + /** + * Checks whether the transaction is authorized to be executed by the delegated account. + * This function enforces the strict permission check hierarchy. It is explicitly + * designed NOT to be overridden. Derived transactors must instead implement + * checkGranularSemantics to add custom validation logic for granular permissions. + * + * The evaluation proceeds as follows: + * - If transaction-level permission is granted, the function immediately returns tesSUCCESS. + * - If transaction-level permission is not granted, the function checks whether the transaction + * matches the granular permission template defined in permissions.macro. If it does, it then + * calls checkGranularSemantics to perform any additional, fine-grained validation. + * + */ + template + static NotTEC + invokeCheckPermission(ReadView const& view, STTx const& tx) + { + // heldGranularPermissions is passed by reference into checkPermission. + // It is populated with the sender’s granular permissions only when the sender + // lacks tx-level permission but has granular permissions that satisfy the + // granular permission template. + // + // - result is terNO_DELEGATE_PERMISSION: return immediately. + // - result is tesSUCCESS and heldGranularPermissions is empty: tx-level permission was + // granted, so we returned success before populating it. + // - result is tesSUCCESS and heldGranularPermissions is not empty: tx-level permission was + // not granted, but the held granular permissions passed checkGranularSandbox, so we proceed + // to checkGranularSemantics. + // + // WARNING: Do not simplify checkPermission to return only + // heldGranularPermissions or the ter code. Both the result and the + // populated set are required to enforce the strict permission hierarchy + // described above. + std::unordered_set heldGranularPermissions; + if (NotTEC const result = checkPermission(view, tx, heldGranularPermissions); + !isTesSuccess(result) || heldGranularPermissions.empty()) + { + return result; + } + + return T::checkGranularSemantics(view, tx, heldGranularPermissions); + } ///////////////////////////////////////////////////// // Interface used by AccountDelete @@ -353,6 +408,12 @@ protected: unit::ValueUnit min = unit::ValueUnit{}); private: + static NotTEC + checkPermission( + ReadView const& view, + STTx const& tx, + std::unordered_set& heldGranularPermissions); + std::pair reset(XRPAmount fee); diff --git a/include/xrpl/tx/transactors/account/AccountSet.h b/include/xrpl/tx/transactors/account/AccountSet.h index a40a9ec963..91b38e7968 100644 --- a/include/xrpl/tx/transactors/account/AccountSet.h +++ b/include/xrpl/tx/transactors/account/AccountSet.h @@ -23,9 +23,6 @@ public: static NotTEC preflight(PreflightContext const& ctx); - static NotTEC - checkPermission(ReadView const& view, STTx const& tx); - static TER preclaim(PreclaimContext const& ctx); diff --git a/include/xrpl/tx/transactors/payment/Payment.h b/include/xrpl/tx/transactors/payment/Payment.h index 14897b4efe..dd792aa1c2 100644 --- a/include/xrpl/tx/transactors/payment/Payment.h +++ b/include/xrpl/tx/transactors/payment/Payment.h @@ -32,7 +32,10 @@ public: preflight(PreflightContext const& ctx); static NotTEC - checkPermission(ReadView const& view, STTx const& tx); + checkGranularSemantics( + ReadView const& view, + STTx const& tx, + std::unordered_set const& heldGranularPermissions); static TER preclaim(PreclaimContext const& ctx); diff --git a/include/xrpl/tx/transactors/token/MPTokenIssuanceSet.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceSet.h index 6a6d1fc445..428c573e2f 100644 --- a/include/xrpl/tx/transactors/token/MPTokenIssuanceSet.h +++ b/include/xrpl/tx/transactors/token/MPTokenIssuanceSet.h @@ -22,9 +22,6 @@ public: static NotTEC preflight(PreflightContext const& ctx); - static NotTEC - checkPermission(ReadView const& view, STTx const& tx); - static TER preclaim(PreclaimContext const& ctx); diff --git a/include/xrpl/tx/transactors/token/TrustSet.h b/include/xrpl/tx/transactors/token/TrustSet.h index dcf454bea1..d719f06326 100644 --- a/include/xrpl/tx/transactors/token/TrustSet.h +++ b/include/xrpl/tx/transactors/token/TrustSet.h @@ -21,7 +21,10 @@ public: preflight(PreflightContext const& ctx); static NotTEC - checkPermission(ReadView const& view, STTx const& tx); + checkGranularSemantics( + ReadView const& view, + STTx const& tx, + std::unordered_set const& heldGranularPermissions); static TER preclaim(PreclaimContext const& ctx); diff --git a/src/libxrpl/protocol/Permissions.cpp b/src/libxrpl/protocol/Permissions.cpp index ce3baeb35e..3aa9705b03 100644 --- a/src/libxrpl/protocol/Permissions.cpp +++ b/src/libxrpl/protocol/Permissions.cpp @@ -1,91 +1,136 @@ #include #include +#include #include -#include // IWYU pragma: keep #include +#include +#include +#include +#include // IWYU pragma: keep #include +#include #include #include #include +#include #include +#include +#include +#include +#include namespace xrpl { +Permission::GranularPermissionEntry::GranularPermissionEntry( + std::string name, + TxType txType, + std::uint32_t permittedFlags, + std::vector permittedFields) + : name(std::move(name)) + , txType(txType) + , permittedFlags(permittedFlags) + , permittedFields(std::move(permittedFields), TxFormats::getCommonFields()) +{ +} + Permission::Permission() { - txFeatureMap_ = { -#pragma push_macro("TRANSACTION") -#undef TRANSACTION - -#define TRANSACTION(tag, value, name, delegable, amendment, ...) {value, amendment}, - -#include - -#undef TRANSACTION -#pragma pop_macro("TRANSACTION") - }; - - delegableTx_ = { -#pragma push_macro("TRANSACTION") -#undef TRANSACTION - -#define TRANSACTION(tag, value, name, delegable, ...) {value, delegable}, - -#include - -#undef TRANSACTION -#pragma pop_macro("TRANSACTION") - }; - - granularPermissionMap_ = { -#pragma push_macro("PERMISSION") -#undef PERMISSION - -#define PERMISSION(type, txType, value) {#type, type}, - -#include - -#undef PERMISSION -#pragma pop_macro("PERMISSION") - }; - - granularNameMap_ = { -#pragma push_macro("PERMISSION") -#undef PERMISSION - -#define PERMISSION(type, txType, value) {type, #type}, - -#include - -#undef PERMISSION -#pragma pop_macro("PERMISSION") - }; - - granularTxTypeMap_ = { -#pragma push_macro("PERMISSION") -#undef PERMISSION - -#define PERMISSION(type, txType, value) {type, txType}, - -#include - -#undef PERMISSION -#pragma pop_macro("PERMISSION") - }; - - XRPL_ASSERT( - txFeatureMap_.size() == delegableTx_.size(), - "xrpl::Permission : txFeatureMap_ and delegableTx_ must have same " - "size"); - - for ([[maybe_unused]] auto const& permission : granularPermissionMap_) { - XRPL_ASSERT( - permission.second > UINT16_MAX, - "xrpl::Permission::granularPermissionMap_ : granular permission " - "value must not exceed the maximum uint16_t value."); +#pragma push_macro("TRANSACTION") +#undef TRANSACTION + +#define TRANSACTION(tag, value, name, delegable, amendment, ...) \ + txDelegationMap_[static_cast(value)] = {amendment, delegable}; + +#include + +#undef TRANSACTION +#pragma pop_macro("TRANSACTION") + } + + granularPermissionsByName_ = { +#pragma push_macro("GRANULAR_PERMISSION") +#undef GRANULAR_PERMISSION + +#define GRANULAR_PERMISSION(type, ...) {#type, type}, + +#include + +#undef GRANULAR_PERMISSION +#pragma pop_macro("GRANULAR_PERMISSION") + }; + + { +#pragma push_macro("GRANULAR_PERMISSION") +#undef GRANULAR_PERMISSION + +// NOLINTBEGIN(bugprone-macro-parentheses) +#define GRANULAR_PERMISSION(type, txType, value, flags, fields) \ + granularPermissions_.emplace( \ + std::piecewise_construct, \ + std::forward_as_tuple(GranularPermissionType::type), \ + std::forward_as_tuple( \ + #type, txType, static_cast(flags), std::vector fields)); + // NOLINTEND(bugprone-macro-parentheses) + +#include + +#undef GRANULAR_PERMISSION +#pragma pop_macro("GRANULAR_PERMISSION") + } + + if (granularPermissionsByName_.size() != granularPermissions_.size()) + { + // LCOV_EXCL_START + Throw( + "granularPermissionsByName_ and granularPermissions_ must have same size"); + // LCOV_EXCL_STOP + } + + for (auto const& [name, type] : granularPermissionsByName_) + { + if (type <= UINT16_MAX) + { + // LCOV_EXCL_START + Throw( + "Granular permission value must exceed the maximum uint16_t value: " + name); + // LCOV_EXCL_STOP + } + } + + for (auto const& [type, entry] : granularPermissions_) + granularTxTypes_.insert(entry.txType); + + // Validate that all fields listed in permissions.macro exist in the + // corresponding transaction type's format, catching typos at startup. + for (auto const& [type, entry] : granularPermissions_) + { + if (!txDelegationMap_.contains(entry.txType)) + { + // LCOV_EXCL_START + Throw("Invalid granular permission txType in txDelegationMap_"); + // LCOV_EXCL_STOP + } + + auto const* fmt = TxFormats::getInstance().findByType(entry.txType); + if (fmt == nullptr) + { + // LCOV_EXCL_START + Throw("Invalid granular permission txType"); + // LCOV_EXCL_STOP + } + + for (auto const& field : entry.permittedFields) + { + if (fmt->getSOTemplate().getIndex(field.sField()) == -1) + { + // LCOV_EXCL_START + Throw("Invalid granular permission field"); + // LCOV_EXCL_STOP + } + } } } @@ -97,8 +142,11 @@ Permission::getInstance() } std::optional -Permission::getPermissionName(std::uint32_t const value) const +Permission::getPermissionName(std::uint32_t value) const { + if (value == 0) + return std::nullopt; + auto const permissionValue = static_cast(value); if (auto const granular = getGranularName(permissionValue)) return granular; @@ -114,90 +162,131 @@ Permission::getPermissionName(std::uint32_t const value) const std::optional Permission::getGranularValue(std::string const& name) const { - auto const it = granularPermissionMap_.find(name); - if (it != granularPermissionMap_.end()) + auto const it = granularPermissionsByName_.find(name); + if (it != granularPermissionsByName_.end()) return static_cast(it->second); return std::nullopt; } std::optional -Permission::getGranularName(GranularPermissionType const& value) const +Permission::getGranularName(GranularPermissionType value) const { - auto const it = granularNameMap_.find(value); - if (it != granularNameMap_.end()) - return it->second; + auto const it = granularPermissions_.find(value); + if (it != granularPermissions_.end()) + return it->second.name; return std::nullopt; } std::optional -Permission::getGranularTxType(GranularPermissionType const& gpType) const +Permission::getGranularTxType(GranularPermissionType gpType) const { - auto const it = granularTxTypeMap_.find(gpType); - if (it != granularTxTypeMap_.end()) - return it->second; + auto const it = granularPermissions_.find(gpType); + if (it != granularPermissions_.end()) + return it->second.txType; return std::nullopt; } +bool +Permission::hasGranularPermissions(TxType txType) const +{ + return granularTxTypes_.contains(txType); +} + std::optional> Permission::getTxFeature(TxType txType) const { - auto const txFeaturesIt = txFeatureMap_.find(txType); + auto const it = txDelegationMap_.find(txType); XRPL_ASSERT( - txFeaturesIt != txFeatureMap_.end(), - "xrpl::Permissions::getTxFeature : tx exists in txFeatureMap_"); + it != txDelegationMap_.end(), + "xrpl::Permission::getTxFeature : tx exists in txDelegationMap_"); - if (txFeaturesIt->second == uint256{}) + if (it->second.amendment == uint256{}) return std::nullopt; - return txFeaturesIt->second; + + return std::optional{std::cref(it->second.amendment)}; } bool -Permission::isDelegable(std::uint32_t const& permissionValue, Rules const& rules) const +Permission::isDelegable(std::uint32_t permissionValue, Rules const& rules) const { - auto const granularPermission = - getGranularName(static_cast(permissionValue)); - if (granularPermission) + if (permissionValue == 0) + return false; // LCOV_EXCL_LINE + + auto const amendmentEnabled = [&rules](TxDelegationEntry const& entry) { + return entry.amendment == uint256{} || rules.enabled(entry.amendment); + }; + + // Granular permissions may authorize a limited subset of a tx type even + // when the full tx type is not delegable. They still require the + // underlying transaction amendment to be enabled. + if (auto const granularIt = + granularPermissions_.find(static_cast(permissionValue)); + granularIt != granularPermissions_.end()) { - // granular permissions are always allowed to be delegated - return true; + auto const txIt = txDelegationMap_.find(granularIt->second.txType); + return txIt != txDelegationMap_.end() && amendmentEnabled(txIt->second); } auto const txType = permissionToTxType(permissionValue); - auto const it = delegableTx_.find(txType); + auto const txIt = txDelegationMap_.find(txType); - if (it == delegableTx_.end()) - return false; - - auto const txFeaturesIt = txFeatureMap_.find(txType); - XRPL_ASSERT( - txFeaturesIt != txFeatureMap_.end(), - "xrpl::Permissions::isDelegable : tx exists in txFeatureMap_"); - - // Delegation is only allowed if the required amendment for the transaction - // is enabled. For transactions that do not require an amendment, delegation - // is always allowed. - if (txFeaturesIt->second != uint256{} && !rules.enabled(txFeaturesIt->second)) - return false; - - if (it->second == Delegation::NotDelegable) - return false; - - return true; + // Tx-level permissions require the transaction type itself to be delegable, and + // the corresponding amendment enabled. + return txIt != txDelegationMap_.end() && txIt->second.delegable != NotDelegable && + amendmentEnabled(txIt->second); } uint32_t -Permission::txToPermissionType(TxType const& type) +Permission::txToPermissionType(TxType const type) { return static_cast(type) + 1; } TxType -Permission::permissionToTxType(uint32_t const& value) +Permission::permissionToTxType(uint32_t value) { + XRPL_ASSERT(value > 0, "xrpl::Permission::permissionToTxType : value is greater than 0"); return static_cast(value - 1); } +bool +Permission::checkGranularSandbox( + STTx const& tx, + std::unordered_set const& heldPermissions) const +{ + // Build union of flags upfront to enable an early exit. Fields are not stored and + // grouped in advance to avoid heap allocation. + std::uint32_t unionFlags = 0; + for (auto const& gp : heldPermissions) + { + auto const it = granularPermissions_.find(gp); + if (it != granularPermissions_.end()) + unionFlags |= it->second.permittedFlags; + } + + // Check if flags are permitted + if ((tx.getFlags() & ~unionFlags) != 0) + return false; + + // Check if fields are permitted. Every present field must appear in at least one held + // permission's template. The common fields are included in the constructor. + for (auto const& field : tx) + { + if (field.getSType() == STI_NOTPRESENT) + continue; + + if (!std::ranges::any_of(heldPermissions, [&](auto const& gp) { + auto const it = granularPermissions_.find(gp); + return it != granularPermissions_.end() && + it->second.permittedFields.getIndex(field.getFName()) != -1; + })) + return false; + } + + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 55f0ea1289..be3b1a082f 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -217,7 +217,7 @@ STTx::getFeePayer() const { // If sfDelegate is present, the delegate account is the payer // note: if a delegate is specified, its authorization to act on behalf of the account is - // enforced in `Transactor::checkPermission` + // enforced in `Transactor::invokeCheckPermission` // cryptographic signature validity is checked separately (e.g., in `Transactor::checkSign`) if (isFieldPresent(sfDelegate)) return getAccountID(sfDelegate); diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index aa7b81c015..b57d30d2b3 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,7 @@ #include #include #include +#include #include #include @@ -175,6 +177,16 @@ Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask) if (ctx.tx[sfDelegate] == ctx.tx[sfAccount]) return temBAD_SIGNER; + + auto const& perm = Permission::getInstance(); + auto const txType = ctx.tx.getTxnType(); + + // If the transaction is not delegable and does not have granular permissions, fail earlier + // with temINVALID. This is to prevent transactions that are not delegable at all from + // being processed further in the invokeCheckPermission function. + if (!perm.isDelegable(Permission::txToPermissionType(txType), ctx.rules) && + !perm.hasGranularPermissions(txType)) + return temINVALID; } if (auto const ret = preflight0(ctx, flagMask)) @@ -295,19 +307,33 @@ Transactor::preflightSigValidated(PreflightContext const& ctx) } NotTEC -Transactor::checkPermission(ReadView const& view, STTx const& tx) +Transactor::checkPermission( + ReadView const& view, + STTx const& tx, + std::unordered_set& heldGranularPermissions) { auto const delegate = tx[~sfDelegate]; if (!delegate) return tesSUCCESS; - auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate); - auto const sle = view.read(delegateKey); - + auto const sle = view.read(keylet::delegate(tx[sfAccount], *delegate)); if (!sle) return terNO_DELEGATE_PERMISSION; - return checkTxPermission(sle, tx); + if (isTesSuccess(checkTxPermission(sle, tx))) + return tesSUCCESS; + + if (!Permission::getInstance().hasGranularPermissions(tx.getTxnType())) + return terNO_DELEGATE_PERMISSION; + + heldGranularPermissions = getGranularPermission(sle, tx.getTxnType()); + if (heldGranularPermissions.empty()) + return terNO_DELEGATE_PERMISSION; + + if (!Permission::getInstance().checkGranularSandbox(tx, heldGranularPermissions)) + return terNO_DELEGATE_PERMISSION; + + return tesSUCCESS; } XRPAmount diff --git a/src/libxrpl/tx/applySteps.cpp b/src/libxrpl/tx/applySteps.cpp index 336bb2004b..caaacfd010 100644 --- a/src/libxrpl/tx/applySteps.cpp +++ b/src/libxrpl/tx/applySteps.cpp @@ -181,7 +181,8 @@ invokePreclaim(PreclaimContext const& ctx) if (NotTEC const result = T::checkPriorTxAndLastLedger(ctx)) return result; - if (NotTEC const result = T::checkPermission(ctx.view, ctx.tx)) + if (NotTEC const result = + Transactor::invokeCheckPermission(ctx.view, ctx.tx)) return result; if (NotTEC const result = T::checkSign(ctx)) diff --git a/src/libxrpl/tx/transactors/account/AccountSet.cpp b/src/libxrpl/tx/transactors/account/AccountSet.cpp index bc207b39dc..36a7e7419f 100644 --- a/src/libxrpl/tx/transactors/account/AccountSet.cpp +++ b/src/libxrpl/tx/transactors/account/AccountSet.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -20,13 +19,11 @@ #include #include #include -#include #include #include #include #include -#include namespace xrpl { @@ -168,54 +165,6 @@ AccountSet::preflight(PreflightContext const& ctx) return tesSUCCESS; } -NotTEC -AccountSet::checkPermission(ReadView const& view, STTx const& tx) -{ - // AccountSet is prohibited to be granted on a transaction level, - // but some granular permissions are allowed. - auto const delegate = tx[~sfDelegate]; - if (!delegate) - return tesSUCCESS; - - auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate); - auto const sle = view.read(delegateKey); - - if (!sle) - return terNO_DELEGATE_PERMISSION; - - std::unordered_set granularPermissions; - loadGranularPermission(sle, ttACCOUNT_SET, granularPermissions); - - auto const uSetFlag = tx.getFieldU32(sfSetFlag); - auto const uClearFlag = tx.getFieldU32(sfClearFlag); - // We don't support any flag based granular permission under - // AccountSet transaction. If any delegated account is trying to - // update the flag on behalf of another account, it is not - // authorized. - if (uSetFlag != 0 || uClearFlag != 0 || ((tx.getFlags() & tfUniversalMask) != 0u)) - return terNO_DELEGATE_PERMISSION; - - if (tx.isFieldPresent(sfEmailHash) && !granularPermissions.contains(AccountEmailHashSet)) - return terNO_DELEGATE_PERMISSION; - - if (tx.isFieldPresent(sfWalletLocator) || tx.isFieldPresent(sfNFTokenMinter)) - return terNO_DELEGATE_PERMISSION; - - if (tx.isFieldPresent(sfMessageKey) && !granularPermissions.contains(AccountMessageKeySet)) - return terNO_DELEGATE_PERMISSION; - - if (tx.isFieldPresent(sfDomain) && !granularPermissions.contains(AccountDomainSet)) - return terNO_DELEGATE_PERMISSION; - - if (tx.isFieldPresent(sfTransferRate) && !granularPermissions.contains(AccountTransferRateSet)) - return terNO_DELEGATE_PERMISSION; - - if (tx.isFieldPresent(sfTickSize) && !granularPermissions.contains(AccountTickSizeSet)) - return terNO_DELEGATE_PERMISSION; - - return tesSUCCESS; -} - TER AccountSet::preclaim(PreclaimContext const& ctx) { diff --git a/src/libxrpl/tx/transactors/delegate/DelegateUtils.cpp b/src/libxrpl/tx/transactors/delegate/DelegateUtils.cpp index dc6c98f95e..6def542c7d 100644 --- a/src/libxrpl/tx/transactors/delegate/DelegateUtils.cpp +++ b/src/libxrpl/tx/transactors/delegate/DelegateUtils.cpp @@ -29,14 +29,12 @@ checkTxPermission(SLE::const_ref delegate, STTx const& tx) return terNO_DELEGATE_PERMISSION; } -void -loadGranularPermission( - SLE::const_ref delegate, - TxType const& txType, - std::unordered_set& granularPermissions) +std::unordered_set +getGranularPermission(SLE::const_ref delegate, TxType const& txType) { + std::unordered_set granularPermissions; if (!delegate) - return; + return granularPermissions; auto const permissionArray = delegate->getFieldArray(sfPermissions); for (auto const& permission : permissionArray) @@ -47,6 +45,8 @@ loadGranularPermission( if (type && *type == txType) granularPermissions.insert(granularValue); } + + return granularPermissions; } } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/payment/Payment.cpp b/src/libxrpl/tx/transactors/payment/Payment.cpp index 805ebe3684..9a9a01ec19 100644 --- a/src/libxrpl/tx/transactors/payment/Payment.cpp +++ b/src/libxrpl/tx/transactors/payment/Payment.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -29,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -273,38 +271,24 @@ Payment::preflight(PreflightContext const& ctx) } NotTEC -Payment::checkPermission(ReadView const& view, STTx const& tx) +Payment::checkGranularSemantics( + ReadView const& view, + STTx const& tx, + std::unordered_set const& heldGranularPermissions) { - auto const delegate = tx[~sfDelegate]; - if (!delegate) - return tesSUCCESS; - - auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate); - auto const sle = view.read(delegateKey); - - if (!sle) - return terNO_DELEGATE_PERMISSION; - - if (isTesSuccess(checkTxPermission(sle, tx))) - return tesSUCCESS; - - std::unordered_set granularPermissions; - loadGranularPermission(sle, ttPAYMENT, granularPermissions); - auto const& dstAmount = tx.getFieldAmount(sfAmount); auto const& amountAsset = dstAmount.asset(); // Granular permissions are only valid for direct payments. - if ((tx.isFieldPresent(sfSendMax) && tx[sfSendMax].asset() != amountAsset) || - tx.isFieldPresent(sfPaths)) + if (tx.isFieldPresent(sfSendMax) && tx[sfSendMax].asset() != amountAsset) return terNO_DELEGATE_PERMISSION; // PaymentMint and PaymentBurn apply to both IOU and MPT direct payments. - if (granularPermissions.contains(PaymentMint) && !isXRP(amountAsset) && + if (heldGranularPermissions.contains(PaymentMint) && !isXRP(amountAsset) && amountAsset.getIssuer() == tx[sfAccount]) return tesSUCCESS; - if (granularPermissions.contains(PaymentBurn) && !isXRP(amountAsset) && + if (heldGranularPermissions.contains(PaymentBurn) && !isXRP(amountAsset) && amountAsset.getIssuer() == tx[sfDestination]) return tesSUCCESS; diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp index 9b25531161..683d65b6f9 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -16,14 +15,12 @@ #include #include #include -#include #include #include #include #include #include -#include namespace xrpl { @@ -138,39 +135,6 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx) return tesSUCCESS; } -NotTEC -MPTokenIssuanceSet::checkPermission(ReadView const& view, STTx const& tx) -{ - auto const delegate = tx[~sfDelegate]; - if (!delegate) - return tesSUCCESS; - - auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate); - auto const sle = view.read(delegateKey); - - if (!sle) - return terNO_DELEGATE_PERMISSION; - - if (isTesSuccess(checkTxPermission(sle, tx))) - return tesSUCCESS; - - // this is added in case more flags will be added for MPTokenIssuanceSet - // in the future. Currently unreachable. - if ((tx.getFlags() & tfMPTokenIssuanceSetMask) != 0u) - return terNO_DELEGATE_PERMISSION; // LCOV_EXCL_LINE - - std::unordered_set granularPermissions; - loadGranularPermission(sle, ttMPTOKEN_ISSUANCE_SET, granularPermissions); - - if (tx.isFlag(tfMPTLock) && !granularPermissions.contains(MPTokenIssuanceLock)) - return terNO_DELEGATE_PERMISSION; - - if (tx.isFlag(tfMPTUnlock) && !granularPermissions.contains(MPTokenIssuanceUnlock)) - return terNO_DELEGATE_PERMISSION; - - return tesSUCCESS; -} - TER MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) { diff --git a/src/libxrpl/tx/transactors/token/TrustSet.cpp b/src/libxrpl/tx/transactors/token/TrustSet.cpp index 1d2bc96693..7838b212b2 100644 --- a/src/libxrpl/tx/transactors/token/TrustSet.cpp +++ b/src/libxrpl/tx/transactors/token/TrustSet.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -21,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -124,51 +122,21 @@ TrustSet::preflight(PreflightContext const& ctx) } NotTEC -TrustSet::checkPermission(ReadView const& view, STTx const& tx) +TrustSet::checkGranularSemantics( + ReadView const& view, + STTx const& tx, + std::unordered_set const& heldGranularPermissions) { - auto const delegate = tx[~sfDelegate]; - if (!delegate) - return tesSUCCESS; - - auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate); - auto const sle = view.read(delegateKey); - - if (!sle) - return terNO_DELEGATE_PERMISSION; - - if (isTesSuccess(checkTxPermission(sle, tx))) - return tesSUCCESS; - - // Currently we only support TrustlineAuthorize, TrustlineFreeze and - // TrustlineUnfreeze granular permission. Setting other flags returns - // error. - if ((tx.getFlags() & tfTrustSetPermissionMask) != 0u) - return terNO_DELEGATE_PERMISSION; - - if (tx.isFieldPresent(sfQualityIn) || tx.isFieldPresent(sfQualityOut)) - return terNO_DELEGATE_PERMISSION; - auto const saLimitAmount = tx.getFieldAmount(sfLimitAmount); auto const sleRippleState = view.read( keylet::line( tx[sfAccount], saLimitAmount.getIssuer(), saLimitAmount.get().currency)); - // if the trustline does not exist, granular permissions are - // not allowed to create trustline + // granular permissions are not allowed to create a trustline if (!sleRippleState) return terNO_DELEGATE_PERMISSION; - std::unordered_set granularPermissions; - loadGranularPermission(sle, ttTRUST_SET, granularPermissions); - - if (tx.isFlag(tfSetfAuth) && !granularPermissions.contains(TrustlineAuthorize)) - return terNO_DELEGATE_PERMISSION; - if (tx.isFlag(tfSetFreeze) && !granularPermissions.contains(TrustlineFreeze)) - return terNO_DELEGATE_PERMISSION; - if (tx.isFlag(tfClearFreeze) && !granularPermissions.contains(TrustlineUnfreeze)) - return terNO_DELEGATE_PERMISSION; - - // updating LimitAmount is not allowed only with granular permissions, + // updating LimitAmount is not allowed with granular permissions, // unless there's a new granular permission for this in the future. auto const curLimit = tx[sfAccount] > saLimitAmount.getIssuer() ? sleRippleState->getFieldAmount(sfHighLimit) diff --git a/src/test/app/Delegate_test.cpp b/src/test/app/Delegate_test.cpp index 70b091290c..20668a42bf 100644 --- a/src/test/app/Delegate_test.cpp +++ b/src/test/app/Delegate_test.cpp @@ -5,8 +5,11 @@ #include #include #include +#include #include +#include #include +#include #include #include #include @@ -22,7 +25,9 @@ #include #include #include +#include +#include #include #include #include @@ -33,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +47,7 @@ #include #include #include +#include #include #include @@ -1063,6 +1070,93 @@ class Delegate_test : public beast::unit_test::Suite } } + // PaymentMint/PaymentBurn with sfSendMax of the same asset is allowed, + // same-asset SendMax is still a direct payment, not cross-currency. + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const gw{"gw"}; + auto const usd = gw["USD"]; + env.fund(XRP(10000), alice, bob, gw); + env.trust(usd(200), alice); + env.close(); + + env(delegate::set(gw, bob, {"PaymentMint"})); + env.close(); + + // sfSendMax with same asset as sfAmount, still a direct payment + env(pay(gw, alice, usd(50)), Sendmax(usd(50)), delegate::As(bob)); + env.require(Balance(alice, usd(50))); + + env(delegate::set(alice, bob, {"PaymentBurn"})); + env.close(); + + env(pay(alice, gw, usd(30)), Sendmax(usd(30)), delegate::As(bob)); + env.require(Balance(alice, usd(20))); + } + + // Test invalid fields or flags not allowed in granular permission template + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const gw{"gw"}; + auto const usd = gw["USD"]; + env.fund(XRP(10000), alice, bob, gw); + env.trust(usd(200), alice); + env.close(); + + env(delegate::set(gw, bob, {"PaymentMint"})); + env(delegate::set(alice, bob, {"PaymentBurn"})); + env.close(); + + // sfDeliverMin (with tfPartialPayment) is not in the PaymentMint + // or PaymentBurn template. + env(pay(gw, alice, usd(100)), + DeliverMin(usd(50)), + Txflags(tfPartialPayment), + delegate::As(bob), + Ter(terNO_DELEGATE_PERMISSION)); + env(pay(alice, gw, usd(50)), + DeliverMin(usd(25)), + Txflags(tfPartialPayment), + delegate::As(bob), + Ter(terNO_DELEGATE_PERMISSION)); + + // sfDomainID is not in the PaymentMint or PaymentBurn template. + env(pay(gw, alice, usd(100)), + Domain(uint256{1}), + delegate::As(bob), + Ter(terNO_DELEGATE_PERMISSION)); + env(pay(alice, gw, usd(50)), + Domain(uint256{1}), + delegate::As(bob), + Ter(terNO_DELEGATE_PERMISSION)); + } + + // Delegate account holds no granular permissions for the tx type: + // getGranularPermission returns empty set. + { + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const gw{"gw"}; + auto const usd = gw["USD"]; + env.fund(XRP(10000), alice, bob, gw); + env.trust(usd(200), alice); + env.close(); + + // Bob holds only an AccountSet granular permission. + env(delegate::set(alice, bob, {"AccountDomainSet"})); + env.close(); + + // Payment has granular permissions defined in permissions.macro, + // but bob only holds AccountSet's granular permission, + // getGranularPermission returns empty. + env(pay(alice, gw, usd(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION)); + } + // PaymentMint and PaymentBurn for MPT { std::string logs; @@ -1119,6 +1213,40 @@ class Delegate_test : public beast::unit_test::Suite BEAST_EXPECT(env.balance(bob, MPT) == bobMPT + MPT(100)); } } + + // Verify granular permissions of different tx types in the same SLE are scoped + // correctly. AccountSet permissions don't apply to Payment and vice versa + { + Env env(*this); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const gw{"gw"}; + auto const usd = gw["USD"]; + env.fund(XRP(10000), alice, bob, gw); + env.trust(usd(200), alice); + env.close(); + + // Alice granted bob with both AccountDomainSet and PaymentMint. + env(delegate::set(alice, bob, {"AccountDomainSet", "PaymentMint"})); + env.close(); + + // PaymentMint fails at granular semantic check because alice is not the issuer. + env(pay(alice, gw, usd(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION)); + + // AccountDomainSet applies correctly to AccountSet + std::string const domain = "example.com"; + auto jt = noop(alice); + jt[sfDomain] = strHex(domain); + jt[sfDelegate] = bob.human(); + env(jt); + BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain)); + + // gw gives bob PaymentMint and bob can mint on gw's behalf + env(delegate::set(gw, bob, {"PaymentMint"})); + env.close(); + env(pay(gw, alice, usd(50)), delegate::As(bob)); + env.require(Balance(alice, usd(50))); + } } void @@ -1301,6 +1429,34 @@ class Delegate_test : public beast::unit_test::Suite env(trust(gw, gw["USD"](0), alice, tfSetfAuth | tfFullyCanonicalSig), delegate::As(bob)); } + + { + Env env(*this); + Account const gw{"gw"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(10000), gw, alice, bob); + + env(fset(gw, asfRequireAuth)); + env.close(); + env(trust(alice, gw["USD"](50))); + env.close(); + env(delegate::set(gw, bob, {"TrustlineAuthorize"})); + env.close(); + + env(trust(gw, gw["USD"](0), alice, tfSetfAuth), delegate::As(bob)); + env.close(); + + // sfQualityOut is a valid TrustSet field, but not permitted in granular template + json::Value txJson = trust(gw, gw["USD"](0), alice, tfSetfAuth); + txJson[sfQualityOut.jsonName] = 100; + env(txJson, delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION)); + + // tfSetNoRipple is a valid flag for TrustSet, but not permitted in granular template + env(trust(gw, gw["USD"](0), alice, tfSetfAuth | tfSetNoRipple), + delegate::As(bob), + Ter(terNO_DELEGATE_PERMISSION)); + } } void @@ -1456,7 +1612,9 @@ class Delegate_test : public beast::unit_test::Suite env(jv2, Ter(terNO_DELEGATE_PERMISSION)); } - // can not set AccountSet flags on behalf of other account + // can not set AccountSet flags on behalf of other account, + // in permissions.macro, the template for AccountSet does + // not allow any flag set or clear. { Env env(*this); auto const alice = Account{"alice"}; @@ -1552,6 +1710,71 @@ class Delegate_test : public beast::unit_test::Suite env(jt); BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain)); } + + // setting invalid field not in permissions.macro template will be rejected. + { + Env env(*this); + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10000), alice, bob); + env.close(); + + // Alice gives Bob permission to set her Domain + env(delegate::set(alice, bob, {"AccountDomainSet"})); + env.close(); + + std::string const domain = "example.com"; + auto txJson = noop(alice); + txJson[sfDomain] = strHex(domain); + txJson[sfDelegate] = bob.human(); + + // sfNFTokenMinter is a valid field in AccountSet tx, but + // it is not permitted for granular template + txJson[sfNFTokenMinter] = bob.human(); + + env(txJson, Ter(terNO_DELEGATE_PERMISSION)); + } + + // Delegated AccountSet with no fields and no flags is allowed, + // because it is allowed in the non-delegated case as well. + { + Env env(*this); + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(10000), alice, bob); + env.close(); + + env(delegate::set(alice, bob, {"AccountDomainSet"})); + env.close(); + + auto jt = noop(alice); + jt[sfDelegate] = bob.human(); + env(jt); + } + + // Revoking all permissions deletes the SLE and subsequent attempts are rejected. + { + Env env(*this); + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(10000), alice, bob); + env.close(); + + env(delegate::set(alice, bob, {"AccountDomainSet"})); + env.close(); + + std::string const domain = "example.com"; + auto jt = noop(alice); + jt[sfDomain] = strHex(domain); + jt[sfDelegate] = bob.human(); + env(jt); + + // empty DelegateSet deletes the SLE + env(delegate::set(alice, bob, {})); + env.close(); + + env(jt, Ter(terNO_DELEGATE_PERMISSION)); + } } void @@ -1672,6 +1895,37 @@ class Delegate_test : public beast::unit_test::Suite env.close(); mpt.set({.account = alice, .flags = tfMPTLock | tfFullyCanonicalSig, .delegate = bob}); } + + // field not permitted to exist in granular delegation + { + Env env(*this); + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), alice, bob); + + MPTTester mpt(env, alice, {.fund = false}); + mpt.create({.flags = tfMPTCanLock}); + env.close(); + + // alice gives granular permission to bob for MPTokenIssuanceLock + env(delegate::set(alice, bob, {"MPTokenIssuanceLock"})); + env.close(); + + // Field is not permitted, permitted fields for delegation is defined in + // permissions.macro. + mpt.set( + {.account = alice, + .mutableFlags = 2, + .delegate = bob, + .err = terNO_DELEGATE_PERMISSION}); + + // Notice: flags not defined in permissions.macro are not permitted for delegation. + // Since preflight will check invalid flag for the tx, it is not reachable. + // If any new flag is defined into the transaction in the future, + // but is not allowed for delegation, the transaction will be rejected with + // terNO_DELEGATE_PERMISSION. The set of permitted flags for delegation is defined in + // permissions.macro. + } } void @@ -2141,6 +2395,62 @@ class Delegate_test : public beast::unit_test::Suite for (auto const& tx : txRequiredFeatures) txAmendmentEnabled(tx.first); } + + // Granular permissions also require the amendment for their underlying + // transaction type. + { + for (auto const permission : {"MPTokenIssuanceLock", "MPTokenIssuanceUnlock"}) + { + Env env(*this, features - featureMPTokensV1); + + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), alice, bob); + env.close(); + + env(delegate::set(alice, bob, {permission}), Ter(temMALFORMED)); + } + } + } + + void + testGranularSandboxCheckOrder() + { + testcase("Make sure GranularSandbox is checked after transaction-level permission"); + + using namespace jtx; + + Env env(*this); + Account const gw{"gw"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(10000), gw, alice, bob); + + env(fset(gw, asfRequireAuth)); + env.close(); + env(trust(alice, gw["USD"](50))); + env.close(); + env(delegate::set(gw, bob, {"TrustlineAuthorize"})); + env.close(); + + env(trust(gw, gw["USD"](0), alice, tfSetfAuth), delegate::As(bob)); + env.close(); + + // sfQualityOut is a valid TrustSet field, but not permitted in granular template + json::Value txJson = trust(gw, gw["USD"](0), alice, tfSetfAuth); + txJson[sfQualityOut.jsonName] = 100; + env(txJson, delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION)); + + // Now Alice grants Bob with transaction level permission + env(delegate::set(gw, bob, {"TrustlineAuthorize", "TrustSet"})); + env.close(); + + // NOTE: This case is to ensure that if a delegate possesses a + // transaction-level permission (e.g., TrustSet), the granular sandbox must not incorrectly + // block the transaction. The function checkGranularSandbox MUST be called after the + // transaction-level permission check. This test case is to avoid future refactor mistakes, + // modifying the order will fail here. + env(txJson, delegate::As(bob)); } void @@ -2193,6 +2503,94 @@ class Delegate_test : public beast::unit_test::Suite "\n Action: Verify security requirements to interact with Delegation feature"); } + void + testNonDelegableTxWithDelegate(FeatureBitset features) + { + testcase("non-delegable tx with sfDelegate is rejected at preflight"); + using namespace jtx; + + Env env(*this, features); + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(10000), alice, bob); + env.close(); + + // Transactions that are notDelegable and have no granular permissions + // will be rejected with temINVALID at preflight. + // Note: pseudo-transactions (EnableAmendment, SetFee and UNLModify) are also + // notDelegable but are excluded here — passesLocalChecks() blocks them + // before preflight1 is ever reached. + { + // SetRegularKey, SignerListSet, AccountDelete, DelegateSet. + env(regkey(alice, bob), delegate::As(bob), Ter(temINVALID)); + env(signers(alice, 1, {{bob, 1}}), delegate::As(bob), Ter(temINVALID)); + env(acctdelete(alice, bob), delegate::As(bob), Ter(temINVALID)); + env(delegate::set(alice, bob, {"Payment"}), delegate::As(bob), Ter(temINVALID)); + + // SAV transactions. + { + Vault const vault{env}; + auto [createTx, keylet] = vault.create({.owner = alice, .asset = xrpIssue()}); + env(createTx, delegate::As(bob), Ter(temINVALID)); + + env(vault.set({.owner = alice, .id = keylet.key}), + delegate::As(bob), + Ter(temINVALID)); + env(vault.del({.owner = alice, .id = keylet.key}), + delegate::As(bob), + Ter(temINVALID)); + env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = XRP(1)}), + delegate::As(bob), + Ter(temINVALID)); + env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = XRP(1)}), + delegate::As(bob), + Ter(temINVALID)); + env(vault.clawback({.issuer = alice, .id = keylet.key, .holder = bob}), + delegate::As(bob), + Ter(temINVALID)); + } + + // Batch transaction: the outer Batch itself is non-delegable. + { + auto const seq = env.seq(alice); + auto const batchFee = batch::calcBatchFee(env, 0, 1); + env(batch::outer(alice, seq, batchFee, tfAllOrNothing), + batch::Inner(pay(alice, bob, XRP(1)), seq + 1), + delegate::As(bob), + Ter(temINVALID)); + } + + // Lending protocol transactions + { + Vault const vault{env}; + auto [createTx, keylet] = vault.create({.owner = alice, .asset = xrpIssue()}); + env(createTx); + + env(loanBroker::set(alice, keylet.key), delegate::As(bob), Ter(temINVALID)); + env(loanBroker::del(alice, keylet.key), delegate::As(bob), Ter(temINVALID)); + env(loanBroker::coverDeposit(alice, keylet.key, XRP(1)), + delegate::As(bob), + Ter(temINVALID)); + env(loanBroker::coverWithdraw(alice, keylet.key, XRP(1)), + delegate::As(bob), + Ter(temINVALID)); + env(loanBroker::coverClawback(alice), delegate::As(bob), Ter(temINVALID)); + + env(loan::set(alice, keylet.key, Number(100)), delegate::As(bob), Ter(temINVALID)); + env(loan::manage(alice, keylet.key, 0), delegate::As(bob), Ter(temINVALID)); + env(loan::del(alice, keylet.key), delegate::As(bob), Ter(temINVALID)); + env(loan::pay(alice, keylet.key, XRP(1)), delegate::As(bob), Ter(temINVALID)); + } + } + + // AccountSet is notDelegable at tx level but has granular permissions, + // so sfDelegate passes preflight and is rejected at invokeCheckPermission with + // terNO_DELEGATE_PERMISSION. + { + env(fset(alice, asfDefaultRipple), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION)); + } + } + void testDelegateUtilsNullptrCheck() { @@ -2202,9 +2600,8 @@ class Delegate_test : public beast::unit_test::Suite STTx const tx{ttPAYMENT, [](STObject&) {}}; BEAST_EXPECT(checkTxPermission(nullptr, tx) == terNO_DELEGATE_PERMISSION); - // loadGranularPermission nullptr check - std::unordered_set granularPermissions; - loadGranularPermission(nullptr, ttPAYMENT, granularPermissions); + // getGranularPermission nullptr check + auto const granularPermissions = getGranularPermission(nullptr, ttPAYMENT); BEAST_EXPECT(granularPermissions.empty()); } @@ -2234,7 +2631,9 @@ class Delegate_test : public beast::unit_test::Suite testSignForDelegated(); testPermissionValue(all); testTxRequireFeatures(all); + testGranularSandboxCheckOrder(); testTxDelegableCount(); + testNonDelegableTxWithDelegate(all); testDelegateUtilsNullptrCheck(); } }; From b6a1ad5bb3cea836b4d4571238640c8007ed27d9 Mon Sep 17 00:00:00 2001 From: Michael Legleux Date: Thu, 18 Jun 2026 12:21:12 -0700 Subject: [PATCH 127/135] fix: Ensure xrpld service directories exist at startup (#7565) --- package/shared/xrpld.service | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package/shared/xrpld.service b/package/shared/xrpld.service index 7f3496acbb..f54e47aa14 100644 --- a/package/shared/xrpld.service +++ b/package/shared/xrpld.service @@ -17,6 +17,10 @@ ProtectHome=true PrivateTmp=true User=xrpld Group=xrpld +StateDirectory=xrpld +StateDirectoryMode=0750 +LogsDirectory=xrpld +LogsDirectoryMode=0750 LimitNOFILE=65536 SystemCallArchitectures=native From b1f794f06792a55bd33e5b544593425713c6cd0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 09:39:38 -0400 Subject: [PATCH 128/135] ci: [DEPENDABOT] bump actions/checkout from 6.0.3 to 7.0.0 (#7585) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-pr-description.yml | 2 +- .github/workflows/on-pr.yml | 2 +- .github/workflows/publish-docs.yml | 2 +- .github/workflows/reusable-build-test-config.yml | 2 +- .github/workflows/reusable-check-levelization.yml | 2 +- .github/workflows/reusable-check-rename.yml | 2 +- .github/workflows/reusable-clang-tidy.yml | 2 +- .github/workflows/reusable-package.yml | 6 +++--- .github/workflows/reusable-strategy-matrix.yml | 2 +- .github/workflows/reusable-upload-recipe.yml | 2 +- .github/workflows/upload-conan-deps.yml | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/check-pr-description.yml b/.github/workflows/check-pr-description.yml index a60b83738a..744449f216 100644 --- a/.github/workflows/check-pr-description.yml +++ b/.github/workflows/check-pr-description.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Write PR body to file env: diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 4b2edeb93d..0cc9b375a7 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Determine changed files # This step checks whether any files have changed that should # cause the next jobs to run. We do it this way rather than diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 0de5347aab..cc7b6b6e7e 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -44,7 +44,7 @@ jobs: container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fe4c8ae steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Prepare runner uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8 diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 95e6b0cbe2..3e6464aaba 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -110,7 +110,7 @@ jobs: uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4 - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Prepare runner uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8 diff --git a/.github/workflows/reusable-check-levelization.yml b/.github/workflows/reusable-check-levelization.yml index 813c0e1e36..88c95ac3ba 100644 --- a/.github/workflows/reusable-check-levelization.yml +++ b/.github/workflows/reusable-check-levelization.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Check levelization run: python .github/scripts/levelization/generate.py - name: Check for differences diff --git a/.github/workflows/reusable-check-rename.yml b/.github/workflows/reusable-check-rename.yml index 5002cc7f40..9a91e98ee3 100644 --- a/.github/workflows/reusable-check-rename.yml +++ b/.github/workflows/reusable-check-rename.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Check definitions run: .github/scripts/rename/definitions.sh . - name: Check copyright notices diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index 5d2325cb1d..c663f71842 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -45,7 +45,7 @@ jobs: issues: write steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Prepare runner uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8 diff --git a/.github/workflows/reusable-package.yml b/.github/workflows/reusable-package.yml index 0e3f657006..eed4bfc4a3 100644 --- a/.github/workflows/reusable-package.yml +++ b/.github/workflows/reusable-package.yml @@ -27,7 +27,7 @@ jobs: matrix: ${{ steps.generate.outputs.matrix }} steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 @@ -45,7 +45,7 @@ jobs: version: ${{ steps.version.outputs.version }} steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: sparse-checkout: | .github/actions/generate-version @@ -69,7 +69,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Download pre-built binary uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 diff --git a/.github/workflows/reusable-strategy-matrix.yml b/.github/workflows/reusable-strategy-matrix.yml index 4518a8ffef..c1a1c1a78b 100644 --- a/.github/workflows/reusable-strategy-matrix.yml +++ b/.github/workflows/reusable-strategy-matrix.yml @@ -23,7 +23,7 @@ jobs: matrix: ${{ steps.generate.outputs.matrix }} steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/reusable-upload-recipe.yml b/.github/workflows/reusable-upload-recipe.yml index ba7a0943d9..a389e98771 100644 --- a/.github/workflows/reusable-upload-recipe.yml +++ b/.github/workflows/reusable-upload-recipe.yml @@ -43,7 +43,7 @@ jobs: container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fe4c8ae steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Generate build version number id: version diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 7ca9d13007..5d3712cf9e 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -65,7 +65,7 @@ jobs: uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4 - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Prepare runner uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8 From e29b523620ee47cc1ecd1df523b80ffdcacc1c9e Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 22 Jun 2026 18:00:40 +0100 Subject: [PATCH 129/135] ci: Build and push docker images in forks too (#7588) --- .github/workflows/build-nix-images.yml | 6 +++--- .github/workflows/build-packaging-images.yml | 6 +++--- .github/workflows/pre-commit.yml | 2 +- .github/workflows/reusable-clang-tidy.yml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-nix-images.yml b/.github/workflows/build-nix-images.yml index 3af6a3b1d4..54911ef6e0 100644 --- a/.github/workflows/build-nix-images.yml +++ b/.github/workflows/build-nix-images.yml @@ -54,9 +54,9 @@ jobs: base_image: debian:bookworm - name: rhel base_image: registry.access.redhat.com/ubi9/ubi:latest - uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@c1b480188519e0cad040e6aa70db1cbc5a797e07 + uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@ee03d31bcc4501d7599dc1b1ecd7a34af582ad1c with: - image_name: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro.name }} + image_name: xrpld/nix-${{ matrix.distro.name }} dockerfile: nix/docker/Dockerfile base_image: ${{ matrix.distro.base_image }} - push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} + push: ${{ github.event_name == 'push' }} diff --git a/.github/workflows/build-packaging-images.yml b/.github/workflows/build-packaging-images.yml index d6dabb0f95..3633847ef3 100644 --- a/.github/workflows/build-packaging-images.yml +++ b/.github/workflows/build-packaging-images.yml @@ -38,9 +38,9 @@ jobs: base_image: debian:bookworm - name: rhel base_image: registry.access.redhat.com/ubi9/ubi:latest - uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@c1b480188519e0cad040e6aa70db1cbc5a797e07 + uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@ee03d31bcc4501d7599dc1b1ecd7a34af582ad1c with: - image_name: ghcr.io/xrplf/xrpld/packaging-${{ matrix.distro.name }} + image_name: xrpld/packaging-${{ matrix.distro.name }} dockerfile: package/Dockerfile base_image: ${{ matrix.distro.base_image }} - push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} + push: ${{ github.event_name == 'push' }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index aecf0c2a8b..0363534af5 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -14,7 +14,7 @@ on: jobs: # Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks. run-hooks: - uses: XRPLF/actions/.github/workflows/pre-commit.yml@312aaab296060ff89d7f798dcab59f019bea6e02 + uses: XRPLF/actions/.github/workflows/pre-commit.yml@e06d4138c9ec8dceeb7c818645faa38087ea9e3d with: runs_on: ubuntu-latest container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }' diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index c663f71842..e99ef574bf 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -32,7 +32,7 @@ jobs: if: ${{ inputs.check_only_changed }} permissions: contents: read - uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@312aaab296060ff89d7f798dcab59f019bea6e02 + uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@c7045074aafe9fb92fa537aa4446f81fbfc17e8b run-clang-tidy: name: Run clang tidy From 997267f84555ca9758394681e26b888eb0699bed Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Mon, 22 Jun 2026 13:36:06 -0400 Subject: [PATCH 130/135] feat: Remove clear mutable flags for DynamicMPT XLS-94 (#7439) --- include/xrpl/protocol/LedgerFormats.h | 12 +- include/xrpl/protocol/TxFlags.h | 42 +- include/xrpl/protocol/detail/features.macro | 2 +- .../transactors/token/MPTokenIssuanceSet.cpp | 66 +- src/test/app/AMMMPT_test.cpp | 16 +- src/test/app/Loan_test.cpp | 189 +-- src/test/app/MPToken_test.cpp | 1260 ++++++++--------- src/test/app/Vault_test.cpp | 243 +--- src/test/jtx/impl/mpt.cpp | 72 +- 9 files changed, 684 insertions(+), 1218 deletions(-) diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index 99d5d818f1..c1274e9e91 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -180,12 +180,12 @@ enum LedgerEntryType : std::uint16_t { LSF_FLAG(lsfMPTCanClawback, 0x00000040)) \ \ LEDGER_OBJECT(MPTokenIssuanceMutable, \ - LSF_FLAG(lsmfMPTCanMutateCanLock, 0x00000002) \ - LSF_FLAG(lsmfMPTCanMutateRequireAuth, 0x00000004) \ - LSF_FLAG(lsmfMPTCanMutateCanEscrow, 0x00000008) \ - LSF_FLAG(lsmfMPTCanMutateCanTrade, 0x00000010) \ - LSF_FLAG(lsmfMPTCanMutateCanTransfer, 0x00000020) \ - LSF_FLAG(lsmfMPTCanMutateCanClawback, 0x00000040) \ + LSF_FLAG(lsmfMPTCanEnableCanLock, 0x00000002) \ + LSF_FLAG(lsmfMPTCanEnableRequireAuth, 0x00000004) \ + LSF_FLAG(lsmfMPTCanEnableCanEscrow, 0x00000008) \ + LSF_FLAG(lsmfMPTCanEnableCanTrade, 0x00000010) \ + LSF_FLAG(lsmfMPTCanEnableCanTransfer, 0x00000020) \ + LSF_FLAG(lsmfMPTCanEnableCanClawback, 0x00000040) \ LSF_FLAG(lsmfMPTCanMutateMetadata, 0x00010000) \ LSF_FLAG(lsmfMPTCanMutateTransferFee, 0x00020000)) \ \ diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 4652cc1bf0..f9c7bc1a5d 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -341,38 +341,32 @@ inline constexpr FlagValue tfTrustSetPermissionMask = // MPTokenIssuanceCreate MutableFlags: // Indicating specific fields or flags may be changed after issuance. -inline constexpr FlagValue tmfMPTCanMutateCanLock = lsmfMPTCanMutateCanLock; -inline constexpr FlagValue tmfMPTCanMutateRequireAuth = lsmfMPTCanMutateRequireAuth; -inline constexpr FlagValue tmfMPTCanMutateCanEscrow = lsmfMPTCanMutateCanEscrow; -inline constexpr FlagValue tmfMPTCanMutateCanTrade = lsmfMPTCanMutateCanTrade; -inline constexpr FlagValue tmfMPTCanMutateCanTransfer = lsmfMPTCanMutateCanTransfer; -inline constexpr FlagValue tmfMPTCanMutateCanClawback = lsmfMPTCanMutateCanClawback; +inline constexpr FlagValue tmfMPTCanEnableCanLock = lsmfMPTCanEnableCanLock; +inline constexpr FlagValue tmfMPTCanEnableRequireAuth = lsmfMPTCanEnableRequireAuth; +inline constexpr FlagValue tmfMPTCanEnableCanEscrow = lsmfMPTCanEnableCanEscrow; +inline constexpr FlagValue tmfMPTCanEnableCanTrade = lsmfMPTCanEnableCanTrade; +inline constexpr FlagValue tmfMPTCanEnableCanTransfer = lsmfMPTCanEnableCanTransfer; +inline constexpr FlagValue tmfMPTCanEnableCanClawback = lsmfMPTCanEnableCanClawback; inline constexpr FlagValue tmfMPTCanMutateMetadata = lsmfMPTCanMutateMetadata; inline constexpr FlagValue tmfMPTCanMutateTransferFee = lsmfMPTCanMutateTransferFee; inline constexpr FlagValue tmfMPTokenIssuanceCreateMutableMask = - ~(tmfMPTCanMutateCanLock | tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanEscrow | - tmfMPTCanMutateCanTrade | tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanClawback | + ~(tmfMPTCanEnableCanLock | tmfMPTCanEnableRequireAuth | tmfMPTCanEnableCanEscrow | + tmfMPTCanEnableCanTrade | tmfMPTCanEnableCanTransfer | tmfMPTCanEnableCanClawback | tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee); // MPTokenIssuanceSet MutableFlags: -// Set or Clear flags. +// Enable mutable capability flags. These flags are one-way: once enabled, +// the corresponding capability cannot be disabled by MPTokenIssuanceSet. inline constexpr FlagValue tmfMPTSetCanLock = 0x00000001; -inline constexpr FlagValue tmfMPTClearCanLock = 0x00000002; -inline constexpr FlagValue tmfMPTSetRequireAuth = 0x00000004; -inline constexpr FlagValue tmfMPTClearRequireAuth = 0x00000008; -inline constexpr FlagValue tmfMPTSetCanEscrow = 0x00000010; -inline constexpr FlagValue tmfMPTClearCanEscrow = 0x00000020; -inline constexpr FlagValue tmfMPTSetCanTrade = 0x00000040; -inline constexpr FlagValue tmfMPTClearCanTrade = 0x00000080; -inline constexpr FlagValue tmfMPTSetCanTransfer = 0x00000100; -inline constexpr FlagValue tmfMPTClearCanTransfer = 0x00000200; -inline constexpr FlagValue tmfMPTSetCanClawback = 0x00000400; -inline constexpr FlagValue tmfMPTClearCanClawback = 0x00000800; -inline constexpr FlagValue tmfMPTokenIssuanceSetMutableMask = ~( - tmfMPTSetCanLock | tmfMPTClearCanLock | tmfMPTSetRequireAuth | tmfMPTClearRequireAuth | - tmfMPTSetCanEscrow | tmfMPTClearCanEscrow | tmfMPTSetCanTrade | tmfMPTClearCanTrade | - tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | tmfMPTSetCanClawback | tmfMPTClearCanClawback); +inline constexpr FlagValue tmfMPTSetRequireAuth = 0x00000002; +inline constexpr FlagValue tmfMPTSetCanEscrow = 0x00000004; +inline constexpr FlagValue tmfMPTSetCanTrade = 0x00000008; +inline constexpr FlagValue tmfMPTSetCanTransfer = 0x00000010; +inline constexpr FlagValue tmfMPTSetCanClawback = 0x00000020; +inline constexpr FlagValue tmfMPTokenIssuanceSetMutableMask = + ~(tmfMPTSetCanLock | tmfMPTSetRequireAuth | tmfMPTSetCanEscrow | tmfMPTSetCanTrade | + tmfMPTSetCanTransfer | tmfMPTSetCanClawback); // Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between accounts allowed a // TrustLine to be added to the issuer of that token without explicit permission from that issuer. diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index d25b0b1f2c..573dca951d 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -24,7 +24,7 @@ XRPL_FEATURE(LendingProtocol, Supported::Yes, VoteBehavior::DefaultN XRPL_FEATURE(PermissionDelegationV1_1, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FIX (DirectoryLimit, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FIX (IncludeKeyletFields, Supported::Yes, VoteBehavior::DefaultNo) -XRPL_FEATURE(DynamicMPT, Supported::No, VoteBehavior::DefaultNo) +XRPL_FEATURE(DynamicMPT, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FIX (TokenEscrowV1, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FIX (PriceOracleOrder, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FIX (MPTDeliveredAmount, Supported::Yes, VoteBehavior::DefaultNo) diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp index 683d65b6f9..1cb12709d5 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp @@ -38,35 +38,34 @@ MPTokenIssuanceSet::getFlagsMask(PreflightContext const& ctx) return tfMPTokenIssuanceSetMask; } -// Maps set/clear mutable flags in an MPTokenIssuanceSet transaction to the -// corresponding ledger mutable flags that control whether the change is -// allowed. +// Maps each MPTokenIssuanceSet MutableFlags to the corresponding mutable +// flag and the target ledger flag to mutate. struct MPTMutabilityFlags { std::uint32_t setFlag; - std::uint32_t clearFlag; - std::uint32_t canMutateFlag; + std::uint32_t canEnableFlag; + std::uint32_t ledgerFlag; }; static constexpr std::array kMptMutabilityFlags = { {{.setFlag = tmfMPTSetCanLock, - .clearFlag = tmfMPTClearCanLock, - .canMutateFlag = lsmfMPTCanMutateCanLock}, + .canEnableFlag = lsmfMPTCanEnableCanLock, + .ledgerFlag = lsfMPTCanLock}, {.setFlag = tmfMPTSetRequireAuth, - .clearFlag = tmfMPTClearRequireAuth, - .canMutateFlag = lsmfMPTCanMutateRequireAuth}, + .canEnableFlag = lsmfMPTCanEnableRequireAuth, + .ledgerFlag = lsfMPTRequireAuth}, {.setFlag = tmfMPTSetCanEscrow, - .clearFlag = tmfMPTClearCanEscrow, - .canMutateFlag = lsmfMPTCanMutateCanEscrow}, + .canEnableFlag = lsmfMPTCanEnableCanEscrow, + .ledgerFlag = lsfMPTCanEscrow}, {.setFlag = tmfMPTSetCanTrade, - .clearFlag = tmfMPTClearCanTrade, - .canMutateFlag = lsmfMPTCanMutateCanTrade}, + .canEnableFlag = lsmfMPTCanEnableCanTrade, + .ledgerFlag = lsfMPTCanTrade}, {.setFlag = tmfMPTSetCanTransfer, - .clearFlag = tmfMPTClearCanTransfer, - .canMutateFlag = lsmfMPTCanMutateCanTransfer}, + .canEnableFlag = lsmfMPTCanEnableCanTransfer, + .ledgerFlag = lsfMPTCanTransfer}, {.setFlag = tmfMPTSetCanClawback, - .clearFlag = tmfMPTClearCanClawback, - .canMutateFlag = lsmfMPTCanMutateCanClawback}}}; + .canEnableFlag = lsmfMPTCanEnableCanClawback, + .ledgerFlag = lsfMPTCanClawback}}}; NotTEC MPTokenIssuanceSet::preflight(PreflightContext const& ctx) @@ -118,17 +117,6 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx) { if ((*mutableFlags == 0u) || ((*mutableFlags & tmfMPTokenIssuanceSetMutableMask) != 0u)) return temINVALID_FLAG; - - // Can not set and clear the same flag - if (std::ranges::any_of(kMptMutabilityFlags, [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) != 0u) && ((*mutableFlags & tmfMPTClearCanTransfer) != 0u)) - return temMALFORMED; } } @@ -196,16 +184,9 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) if (auto const mutableFlags = ctx.tx[~sfMutableFlags]) { if (std::ranges::any_of(kMptMutabilityFlags, [mutableFlags, &isMutableFlag](auto const& f) { - return !isMutableFlag(f.canMutateFlag) && - ((*mutableFlags & (f.setFlag | f.clearFlag))); + return !isMutableFlag(f.canEnableFlag) && ((*mutableFlags & f.setFlag) != 0u); })) return tecNO_PERMISSION; - - // Clearing lsfMPTRequireAuth is invalid when the issuance already has - // a DomainID set, because a DomainID requires RequireAuth to be active. - if ((*mutableFlags & tmfMPTClearRequireAuth) != 0u && - sleMptIssuance->isFieldPresent(sfDomainID)) - return tecNO_PERMISSION; } if (!isMutableFlag(lsmfMPTCanMutateMetadata) && ctx.tx.isFieldPresent(sfMPTokenMetadata)) @@ -265,19 +246,8 @@ MPTokenIssuanceSet::doApply() { if ((mutableFlags & f.setFlag) != 0u) { - flagsOut |= f.canMutateFlag; + flagsOut |= f.ledgerFlag; } - else if ((mutableFlags & f.clearFlag) != 0u) - { - flagsOut &= ~f.canMutateFlag; - } - } - - if ((mutableFlags & tmfMPTClearCanTransfer) != 0u) - { - // If the lsfMPTCanTransfer flag is being cleared, then also clear - // the TransferFee field. - sle->makeFieldAbsent(sfTransferFee); } } diff --git a/src/test/app/AMMMPT_test.cpp b/src/test/app/AMMMPT_test.cpp index 31b54ceee0..5b576b41e4 100644 --- a/src/test/app/AMMMPT_test.cpp +++ b/src/test/app/AMMMPT_test.cpp @@ -2240,7 +2240,9 @@ private: .err = Ter(tecNO_AUTH)}); } - // MPTCanTransfer is not set and the account is not the issuer of MPT + // MPTCanTransfer is not set and the account is not the issuer of MPT. + // The issuer can create the AMM, and an existing LP token holder can + // still withdraw. { Env env{*this}; env.fund(XRP(30'000), gw_, alice_); @@ -2250,15 +2252,17 @@ private: .issuer = gw_, .holders = {alice_}, .pay = 30'000, - .flags = kMptDexFlags, - .mutableFlags = tmfMPTCanMutateCanTransfer, + .flags = tfMPTCanTrade, .authHolder = true}); AMM amm(env, gw_, XRP(10'000), btc(10'000)); - amm.deposit(DepositArg{.account = alice_, .asset1In = XRP(200), .asset2In = btc(200)}); - // Allow to withdraw if transfer is disabled - btc.set({.mutableFlags = tmfMPTClearCanTransfer}); + auto const lpIssue = amm.lptIssue(); + env.trust(STAmount{lpIssue, 20'000'000}, alice_); + env.close(); + env(pay(gw_, alice_, LPToken(1'000'000).tokens(lpIssue))); + env.close(); + amm.withdraw( WithdrawArg{.account = alice_, .asset1Out = btc(100), .assets = {{XRP, btc}}}); } diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index c3b5850231..dbb0033368 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -5423,110 +5422,12 @@ protected: } void - testCoverDepositWithdrawNonTransferableMPT(FeatureBitset feature) + testLendingCanTradeDisabledNoImpact() { - testcase("CoverDeposit blocked, CoverWithdraw allowed when CanTransfer cleared"); - using namespace jtx; - using namespace loanBroker; - - Env env(*this, feature); - - Account const issuer{"issuer"}; - Account const alice{"alice"}; - - env.fund(XRP(100'000), issuer, alice); - env.close(); - - MPTTester mpt( - {.env = env, - .issuer = issuer, - .holders = {alice}, - .pay = 100, - .flags = tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - PrettyAsset const asset = mpt["MPT"]; - - Vault const vault{env}; - auto const [createTx, vaultKeylet] = vault.create({.owner = alice, .asset = asset}); - env(createTx); - env.close(); - - auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice)); - env(set(alice, vaultKeylet.key)); - env.close(); - - auto const brokerSle = env.le(brokerKeylet); - if (!BEAST_EXPECT(brokerSle)) - return; - - Account const pseudoAccount{"Loan Broker pseudo-account", brokerSle->at(sfAccount)}; - - // First, deposit some cover while CanTransfer is set so we have an - // existing position to withdraw from after the governance action. - auto const depositAmount = asset(1); - env(coverDeposit(alice, brokerKeylet.key, depositAmount)); - env.close(); - - if (auto const refreshed = env.le(brokerKeylet); BEAST_EXPECT(refreshed)) - { - BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 1); - env.require(Balance(pseudoAccount, depositAmount)); - } - - // Issuer governance: clear CanTransfer. - mpt.set({.mutableFlags = tmfMPTClearCanTransfer}); - env.close(); - - // Standard Payment path still forbids third-party transfers. - auto const err = feature[featureMPTokensV2] ? tecNO_PERMISSION : tecNO_AUTH; - env(pay(alice, pseudoAccount, asset(1)), Ter(err)); - env.close(); - - // New cover deposits are blocked - this would create new exposure. - env(coverDeposit(alice, brokerKeylet.key, depositAmount), Ter{tecNO_AUTH}); - env.close(); - - if (auto const refreshed = env.le(brokerKeylet); BEAST_EXPECT(refreshed)) - { - BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 1); - env.require(Balance(pseudoAccount, depositAmount)); - } - - bool const postAmendment = feature[fixCleanup3_2_0]; - if (postAmendment) - { - // Post-fixCleanup3_2_0: existing cover can always be withdrawn - // even when CanTransfer is cleared, so the broker is not trapped. - env(coverWithdraw(alice, brokerKeylet.key, depositAmount)); - env.close(); - - if (auto const refreshed = env.le(brokerKeylet); BEAST_EXPECT(refreshed)) - { - BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 0); - env.require(Balance(pseudoAccount, asset(0))); - } - } - else - { - // Pre-fixCleanup3_2_0 regression: cover withdraw was blocked, - // trapping the broker's first-loss capital. - env(coverWithdraw(alice, brokerKeylet.key, depositAmount), Ter{tecNO_AUTH}); - env.close(); - - if (auto const refreshed = env.le(brokerKeylet); BEAST_EXPECT(refreshed)) - { - BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 1); - env.require(Balance(pseudoAccount, depositAmount)); - } - } - } - - void - testLoanSetBlockedLoanPayAllowedWhenCanTransferCleared() - { - testcase("LoanSet blocked, LoanPay allowed when CanTransfer cleared"); + testcase("Lending: CanTrade disabled has no impact"); using namespace jtx; using namespace loan; + using namespace loanBroker; Env env(*this, all_); @@ -5542,67 +5443,7 @@ protected: .issuer = issuer, .holders = {lender, borrower}, .flags = tfMPTCanTransfer | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - PrettyAsset const asset = mpt.issuanceID(); - env(pay(issuer, lender, asset(10'000'000))); - // Fund the borrower with enough to cover principal+interest+fees - env(pay(issuer, borrower, asset(100'000))); - env.close(); - - // Create vault and broker while CanTransfer is set. - auto const broker = createVaultAndBroker(env, asset, lender); - - auto const loanSetFee = Fee(env.current()->fees().base * 2); - - // Create an existing loan while CanTransfer is set. - env(set(borrower, broker.brokerID, 1'000), - Sig(sfCounterpartySignature, lender), - loanSetFee); - env.close(); - auto const loanKeylet = keylet::loan(broker.brokerID, 1); - BEAST_EXPECT(env.le(loanKeylet)); - - // Issuer governance: clear CanTransfer. - mpt.set({.mutableFlags = tmfMPTClearCanTransfer}); - env.close(); - - // Issuing a NEW loan is blocked - it would create new exposure into - // a pool the issuer is restricting. - env(set(borrower, broker.brokerID, 1'000), - Sig(sfCounterpartySignature, lender), - loanSetFee, - Ter{tecNO_AUTH}); - env.close(); - - // Repaying an existing loan is always allowed - blocking it would - // create irrecoverable bad debt and trap SAV depositor principal. - env(pay(borrower, loanKeylet.key, asset(1'000))); - env.close(); - } - - void - testLendingCanTradeClearedNoImpact() - { - testcase("Lending: CanTrade cleared has no impact"); - using namespace jtx; - using namespace loan; - using namespace loanBroker; - - Env env(*this, all_); - - Account const issuer{"issuer"}; - Account const lender{"lender"}; - Account const borrower{"borrower"}; - - env.fund(XRP(1'000'000), issuer, lender, borrower); - env.close(); - - MPTTester mpt( - {.env = env, - .issuer = issuer, - .holders = {lender, borrower}, - .flags = tfMPTCanTransfer | tfMPTCanTrade | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTrade}); + .mutableFlags = tmfMPTCanEnableCanTrade}); PrettyAsset const asset = mpt.issuanceID(); env(pay(issuer, lender, asset(10'000'000))); env(pay(issuer, borrower, asset(100'000))); @@ -5610,16 +5451,7 @@ protected: auto const broker = createVaultAndBroker(env, asset, lender); - // Sanity: while CanTrade is set, the asset can be placed on the DEX. - env(offer(lender, XRP(1), asset(10))); - env.close(); - - // Issuer governance: clear CanTrade. Loan origination and repayment - // are not trades: nothing in the Lending Protocol should be impacted. - mpt.set({.mutableFlags = tmfMPTClearCanTrade}); - env.close(); - - // Control: clearing CanTrade is observable on the DEX path. + // CanTrade is not set env(offer(lender, XRP(1), asset(10)), Ter{tecNO_PERMISSION}); env.close(); @@ -5644,6 +5476,13 @@ protected: // Cover withdrawal still works. env(coverWithdraw(lender, broker.brokerID, asset(100))); env.close(); + + // Enable CanTrade and verify the DEX path is restored. + mpt.set({.mutableFlags = tmfMPTSetCanTrade}); + env.close(); + + env(offer(lender, XRP(1), asset(10))); + env.close(); } #if LOAN_TODO @@ -8716,8 +8555,7 @@ protected: testRIPD3901(); testBorrowerIsBroker(); testLimitExceeded(); - testLoanSetBlockedLoanPayAllowedWhenCanTransferCleared(); - testLendingCanTradeClearedNoImpact(); + testLendingCanTradeDisabledNoImpact(); testBugOverpaymentPrincipalChange(); testBugOverpayUnroundedAmount(); @@ -8747,7 +8585,6 @@ protected: testPoCUnsignedUnderflowOnFullPayAfterEarlyPeriodic(features); testBatchBypassCounterparty(features); testLoanNextPaymentDueDateOverflow(features); - testCoverDepositWithdrawNonTransferableMPT(features); testSequentialFLCDepletion(features); // Invariants diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 3d6cff0885..2cab3e7c89 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -3485,7 +3485,7 @@ class MPToken_test : public beast::unit_test::Suite MPTTester mptAlice(env, alice, {.holders = {bob}}); mptAlice.create( {.ownerCount = 1, - .mutableFlags = tmfMPTCanMutateMetadata | tmfMPTCanMutateCanLock | + .mutableFlags = tmfMPTCanMutateMetadata | tmfMPTCanEnableCanLock | tmfMPTCanMutateTransferFee}); // Setting flags is not allowed when MutableFlags is present @@ -3533,33 +3533,6 @@ class MPToken_test : public beast::unit_test::Suite } } - // 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 = { - tmfMPTSetCanLock | tmfMPTClearCanLock, - tmfMPTSetRequireAuth | tmfMPTClearRequireAuth, - tmfMPTSetCanEscrow | tmfMPTClearCanEscrow, - tmfMPTSetCanTrade | tmfMPTClearCanTrade, - tmfMPTSetCanTransfer | tmfMPTClearCanTransfer, - tmfMPTSetCanClawback | tmfMPTClearCanClawback, - tmfMPTSetCanLock | tmfMPTClearCanLock | tmfMPTClearCanTrade, - tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | tmfMPTSetCanEscrow | - tmfMPTClearCanClawback}; - - 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}; @@ -3569,17 +3542,11 @@ class MPToken_test : public beast::unit_test::Suite auto const mutableFlags = { tmfMPTSetCanLock, - tmfMPTClearCanLock, tmfMPTSetRequireAuth, - tmfMPTClearRequireAuth, tmfMPTSetCanEscrow, - tmfMPTClearCanEscrow, tmfMPTSetCanTrade, - tmfMPTClearCanTrade, tmfMPTSetCanTransfer, - tmfMPTClearCanTransfer, - tmfMPTSetCanClawback, - tmfMPTClearCanClawback}; + tmfMPTSetCanClawback}; for (auto const& mutableFlag : mutableFlags) { @@ -3623,34 +3590,6 @@ class MPToken_test : public beast::unit_test::Suite .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 = tmfMPTCanMutateTransferFee | tmfMPTCanMutateCanTransfer}); - - // Can not set non-zero transfer fee and clear MPTCanTransfer at the - // same time - mptAlice.set( - {.account = alice, - .mutableFlags = tmfMPTClearCanTransfer, - .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 = tmfMPTClearCanTransfer, .transferFee = 0}); - BEAST_EXPECT(!mptAlice.isTransferFeePresent()); - } - // Can not set non-zero transfer fee when MPTCanTransfer is not set { Env env{*this, features}; @@ -3658,7 +3597,7 @@ class MPToken_test : public beast::unit_test::Suite mptAlice.create( {.ownerCount = 1, - .mutableFlags = tmfMPTCanMutateTransferFee | tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanMutateTransferFee | tmfMPTCanEnableCanTransfer}); mptAlice.set({.account = alice, .transferFee = 100, .err = tecNO_PERMISSION}); @@ -3691,21 +3630,14 @@ class MPToken_test : public beast::unit_test::Suite mptAlice.create( {.ownerCount = 1, - .mutableFlags = tmfMPTCanMutateCanTrade | tmfMPTCanMutateCanTransfer | + .mutableFlags = tmfMPTCanEnableCanTrade | tmfMPTCanEnableCanTransfer | tmfMPTCanMutateMetadata}); // Can not mutate transfer fee mptAlice.set({.account = alice, .transferFee = 100, .err = tecNO_PERMISSION}); auto const invalidFlags = { - tmfMPTSetCanLock, - tmfMPTClearCanLock, - tmfMPTSetRequireAuth, - tmfMPTClearRequireAuth, - tmfMPTSetCanEscrow, - tmfMPTClearCanEscrow, - tmfMPTSetCanClawback, - tmfMPTClearCanClawback}; + tmfMPTSetCanLock, tmfMPTSetRequireAuth, tmfMPTSetCanEscrow, tmfMPTSetCanClawback}; // Can not mutate flags which are not mutable for (auto const& mutableFlag : invalidFlags) @@ -3716,11 +3648,9 @@ class MPToken_test : public beast::unit_test::Suite // Can mutate MPTCanTrade mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanTrade}); - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanTrade}); // Can mutate MPTCanTransfer mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanTransfer}); - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanTransfer}); // Can mutate metadata mptAlice.set({.account = alice, .metadata = "test"}); @@ -3789,37 +3719,26 @@ class MPToken_test : public beast::unit_test::Suite BEAST_EXPECT(mptAlice.checkTransferFee(10)); } - // Test flag toggling + // Test mutable flag enablement { - auto testFlagToggle = [&](std::uint32_t createFlags, - std::uint32_t setFlags, - std::uint32_t clearFlags) { + auto testFlagSet = [&](std::uint32_t createFlags, std::uint32_t setFlags) { 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}); + // Setting the same mutable capability more than once is harmless. 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(tmfMPTCanMutateCanLock, tfMPTCanLock, tmfMPTClearCanLock); - testFlagToggle( - tmfMPTCanMutateRequireAuth, tmfMPTSetRequireAuth, tmfMPTClearRequireAuth); - testFlagToggle(tmfMPTCanMutateCanEscrow, tmfMPTSetCanEscrow, tmfMPTClearCanEscrow); - testFlagToggle(tmfMPTCanMutateCanTrade, tmfMPTSetCanTrade, tmfMPTClearCanTrade); - testFlagToggle( - tmfMPTCanMutateCanTransfer, tmfMPTSetCanTransfer, tmfMPTClearCanTransfer); - testFlagToggle( - tmfMPTCanMutateCanClawback, tmfMPTSetCanClawback, tmfMPTClearCanClawback); + testFlagSet(tmfMPTCanEnableCanLock, tmfMPTSetCanLock); + testFlagSet(tmfMPTCanEnableRequireAuth, tmfMPTSetRequireAuth); + testFlagSet(tmfMPTCanEnableCanEscrow, tmfMPTSetCanEscrow); + testFlagSet(tmfMPTCanEnableCanTrade, tmfMPTSetCanTrade); + testFlagSet(tmfMPTCanEnableCanTransfer, tmfMPTSetCanTransfer); + testFlagSet(tmfMPTCanEnableCanClawback, tmfMPTSetCanClawback); } } @@ -3840,7 +3759,7 @@ class MPToken_test : public beast::unit_test::Suite {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock | tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateCanLock | tmfMPTCanMutateCanTrade | + .mutableFlags = tmfMPTCanEnableCanLock | tmfMPTCanEnableCanTrade | tmfMPTCanMutateTransferFee}); mptAlice.authorize({.account = bob, .holderCount = 1}); @@ -3848,11 +3767,8 @@ class MPToken_test : public beast::unit_test::Suite mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); // Can mutate the mutable flags and fields - mptAlice.set({.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}); } @@ -3864,7 +3780,7 @@ class MPToken_test : public beast::unit_test::Suite {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanLock | tmfMPTCanMutateCanClawback | + .mutableFlags = tmfMPTCanEnableCanLock | tmfMPTCanEnableCanClawback | tmfMPTCanMutateMetadata}); mptAlice.authorize({.account = bob, .holderCount = 1}); @@ -3872,36 +3788,23 @@ class MPToken_test : public beast::unit_test::Suite mptAlice.set({.account = alice, .flags = tfMPTLock}); // Can mutate the mutable flags and fields - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanLock}); mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanLock}); - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanLock}); mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanClawback}); - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanClawback}); mptAlice.set({.account = alice, .metadata = "mutate"}); } - // Test lock and unlock after mutating MPTCanLock + // Test lock and unlock after enabling MPTCanLock { Env env{*this, features}; MPTTester mptAlice(env, alice, {.holders = {bob}}); mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanLock | tmfMPTCanMutateCanClawback | + .mutableFlags = tmfMPTCanEnableCanLock | tmfMPTCanEnableCanClawback | tmfMPTCanMutateMetadata}); 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 = tmfMPTClearCanLock}); - - // Can not lock or unlock + // Can not lock or unlock before MPTCanLock is enabled mptAlice.set({.account = alice, .flags = tfMPTLock, .err = tecNO_PERMISSION}); mptAlice.set({.account = alice, .flags = tfMPTUnlock, .err = tecNO_PERMISSION}); mptAlice.set( @@ -3909,10 +3812,10 @@ class MPToken_test : public beast::unit_test::Suite mptAlice.set( {.account = alice, .holder = bob, .flags = tfMPTUnlock, .err = tecNO_PERMISSION}); - // Set MPTCanLock again + // Set MPTCanLock mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanLock}); - // Can lock and unlock again + // Can lock and unlock mptAlice.set({.account = alice, .flags = tfMPTLock}); mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); mptAlice.set({.account = alice, .flags = tfMPTUnlock}); @@ -3926,83 +3829,30 @@ class MPToken_test : public beast::unit_test::Suite testcase("Mutate MPTRequireAuth"); using namespace test::jtx; - // test mutating RequireAuth flag on the issuance and its effect on payment authorization - { - Env env{*this, features}; - Account const alice("alice"); - Account const bob("bob"); + // test enabling RequireAuth flag on the issuance and its effect on payment + // authorization + 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 = tmfMPTCanMutateRequireAuth}); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.ownerCount = 1, + .flags = tfMPTCanTransfer, + .mutableFlags = tmfMPTCanEnableRequireAuth}); - mptAlice.authorize({.account = bob}); - mptAlice.authorize({.account = alice, .holder = bob}); + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 1000); - // Pay to bob - mptAlice.pay(alice, bob, 1000); + // Set RequireAuth because it is mutable. + mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetRequireAuth}); - // Unauthorize bob - mptAlice.authorize({.account = alice, .holder = bob, .flags = tfMPTUnauthorize}); + // This should fail because bob is not authorized yet. + mptAlice.pay(alice, bob, 1000, tecNO_AUTH); - // Can not pay to bob - mptAlice.pay(bob, alice, 100, tecNO_AUTH); - - // Clear RequireAuth - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearRequireAuth}); - - // Can pay to bob - mptAlice.pay(alice, bob, 1000); - - // Set RequireAuth again - mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetRequireAuth}); - - // 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); - } - - // Cannot clear RequireAuth when a DomainID is set on the issuance - { - Account const alice{"alice"}; - Account const bob{"bob"}; - Account const credIssuer{"credIssuer"}; - pdomain::Credentials const credentials{ - {.issuer = credIssuer, .credType = "credential"}}; - - Env env{*this, features}; - env.fund(XRP(1000), credIssuer); - env.close(); - - env(pdomain::setTx(credIssuer, credentials)); - env.close(); - auto const domainId = pdomain::getNewDomain(env.meta()); - - MPTTester mptAlice(env, alice, {.holders = {bob}}); - mptAlice.create({ - .ownerCount = 1, - .flags = tfMPTRequireAuth, - .mutableFlags = tmfMPTCanMutateRequireAuth, - .domainID = domainId, - }); - - // Clearing RequireAuth while a DomainID is present must be rejected, - mptAlice.set({ - .account = alice, - .mutableFlags = tmfMPTClearRequireAuth, - .err = tecNO_PERMISSION, - }); - - // Setting RequireAuth (already set) is still allowed, though it has no effect. - mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetRequireAuth}); - } + // Issuer authorizes bob and pay should succeed. + mptAlice.authorize({.account = alice, .holder = bob}); + mptAlice.pay(alice, bob, 1000); } void @@ -4023,7 +3873,7 @@ class MPToken_test : public beast::unit_test::Suite {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateCanEscrow}); + .mutableFlags = tmfMPTCanEnableCanEscrow}); mptAlice.authorize({.account = carol}); mptAlice.authorize({.account = bob}); @@ -4045,14 +3895,6 @@ class MPToken_test : public beast::unit_test::Suite escrow::kCondition(escrow::kCb1), escrow::kFinishTime(env.now() + 1s), Fee(baseFee * 150)); - - // Clear MPTCanEscrow - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanEscrow}); - env(escrow::create(carol, bob, mpt(3)), - escrow::kCondition(escrow::kCb1), - escrow::kFinishTime(env.now() + 1s), - Fee(baseFee * 150), - Ter(tecNO_PERMISSION)); } void @@ -4071,7 +3913,7 @@ class MPToken_test : public beast::unit_test::Suite MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); mptAlice.create( {.ownerCount = 1, - .mutableFlags = tmfMPTCanMutateCanTransfer | tmfMPTCanMutateTransferFee}); + .mutableFlags = tmfMPTCanEnableCanTransfer | tmfMPTCanMutateTransferFee}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = carol}); @@ -4115,19 +3957,9 @@ class MPToken_test : public beast::unit_test::Suite env(pay(bob, carol, mptAlice(50)), Txflags(tfPartialPayment)); BEAST_EXPECT(env.balance(carol, mptc) == mptc(49)); } - - // Alice clears MPTCanTransfer - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanTransfer}); - - // 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 - // tmfMPTCanMutateTransferFee is set. + // Can set transfer fee to zero when tmfMPTCanMutateTransferFee is set. { Env env{*this, features}; @@ -4136,18 +3968,12 @@ class MPToken_test : public beast::unit_test::Suite {.transferFee = 100, .ownerCount = 1, .flags = tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateTransferFee | tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanMutateTransferFee}); BEAST_EXPECT(mptAlice.checkTransferFee(100)); - // Clear MPTCanTransfer and transfer fee is removed - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanTransfer}); - BEAST_EXPECT(!mptAlice.isTransferFeePresent()); - - // Can still set transfer fee to zero, although it is already zero + // Setting transfer fee to zero removes the field. mptAlice.set({.account = alice, .transferFee = 0}); - - // TransferFee field is still not present BEAST_EXPECT(!mptAlice.isTransferFeePresent()); } } @@ -4165,7 +3991,7 @@ class MPToken_test : public beast::unit_test::Suite MPTTester mptAlice(env, alice, {.holders = {bob}}); mptAlice.create( - {.ownerCount = 1, .holderCount = 0, .mutableFlags = tmfMPTCanMutateCanClawback}); + {.ownerCount = 1, .holderCount = 0, .mutableFlags = tmfMPTCanEnableCanClawback}); // Bob creates an MPToken mptAlice.authorize({.account = bob}); @@ -4181,12 +4007,6 @@ class MPToken_test : public beast::unit_test::Suite // Can clawback now mptAlice.claw(alice, bob, 1); - - // Clear MPTCanClawback - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanClawback}); - - // Can not clawback - mptAlice.claw(alice, bob, 1, tecNO_PERMISSION); } void @@ -4536,27 +4356,22 @@ class MPToken_test : public beast::unit_test::Suite { Env env(*this); env.fund(XRP(1'000), gw, alice, carol); - MPTTester btc( + MPTTester const btc( {.env = env, .issuer = gw, .holders = {alice, carol}, .pay = 100, - .flags = tfMPTCanTrade, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - MPTTester eth( + .flags = tfMPTCanTrade | tfMPTCanTransfer}); + MPTTester const eth( {.env = env, .issuer = gw, .holders = {alice, carol}, .pay = 100, - .flags = tfMPTCanTrade | tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateCanTransfer}); + .flags = tfMPTCanTrade}); // Can create env(offer(alice, eth(10), btc(10)), Txflags(tfPassive)); - btc.set({.mutableFlags = tmfMPTSetCanTransfer}); - eth.set({.mutableFlags = tmfMPTClearCanTransfer}); - env(offer(alice, eth(10), btc(10)), Txflags(tfPassive)); - BEAST_EXPECT(getAccountOffers(env, alice)[jss::offers].size() == 2); + BEAST_EXPECT(getAccountOffers(env, alice)[jss::offers].size() == 1); // issuer can create env(offer(gw, eth(10), btc(10)), Txflags(tfPassive)); @@ -4584,14 +4399,14 @@ class MPToken_test : public beast::unit_test::Suite .holders = {alice, carol}, .pay = 100, .flags = tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateCanTrade}); + .mutableFlags = tmfMPTCanEnableCanTrade}); MPTTester const eth( {.env = env, .issuer = gw, .holders = {alice, carol}, .pay = 100, .flags = tfMPTCanTrade, - .mutableFlags = tmfMPTCanMutateCanTrade}); + .mutableFlags = tmfMPTCanEnableCanTrade}); // Can't create env(offer(gw, eth(10), btc(10)), Ter(tecNO_PERMISSION)); @@ -4828,29 +4643,29 @@ class MPToken_test : public beast::unit_test::Suite .holders = {alice, carol, bob}, .pay = 1'000, .flags = tfMPTCanLock | kMptDexFlags, - .mutableFlags = tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanTrade | - tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanEnableRequireAuth | tmfMPTCanEnableCanTrade | + tmfMPTCanEnableCanTransfer}); MPTTester eth( {.env = env, .issuer = gw, .holders = {alice, carol, bob}, .pay = 1'000, .flags = tfMPTCanLock | kMptDexFlags, - .mutableFlags = tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanEnableCanTransfer}); MPTTester const usd( {.env = env, .issuer = gw, .holders = {alice, carol, bob}, .pay = 1'000, .flags = kMptDexFlags | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanEnableCanTransfer}); MPTTester const cad( {.env = env, .issuer = gw, .holders = {alice, carol, bob}, .pay = 1'000, .flags = kMptDexFlags | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanEnableCanTransfer}); env(offer(bob, eth(1'000), btc(1'000)), Txflags(tfPassive)); env.close(); @@ -4896,13 +4711,33 @@ class MPToken_test : public beast::unit_test::Suite // BTC is transferred from ed to bob, ed is not authorized env(pay(ed, gw, eth(10)), Path(~eth), Sendmax(btc(10)), Ter(tecNO_AUTH)); env.close(); - btc.set({.mutableFlags = tmfMPTClearRequireAuth}); + } - // MPTCanTransfer is not set + // MPTCanTransfer is not set. + { + auto const ed = Account{"ed"}; + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol, bob, ed); + MPTTester const btc( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob, ed}, + .pay = 1'000, + .flags = tfMPTCanTrade}); + MPTTester const eth( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob, ed}, + .pay = 1'000, + .flags = kMptDexFlags}); + + env(offer(bob, eth(1'000), btc(1'000)), Txflags(tfPassive)); + env.close(); + env(offer(bob, btc(1'000), eth(1'000)), Txflags(tfPassive)); + env.close(); // Fail regardless if source/destination is the issuer or // not since the offer is owned by a holder. - btc.set({.mutableFlags = tmfMPTClearCanTransfer}); env(pay(ed, carol, btc(10)), Path(~btc), Sendmax(eth(10)), Ter(tecPATH_PARTIAL)); env(pay(carol, ed, btc(10)), Path(~btc), Sendmax(eth(10)), Ter(tecPATH_PARTIAL)); env(pay(ed, carol, eth(10)), Path(~eth), Sendmax(btc(10)), Ter(tecPATH_PARTIAL)); @@ -4926,124 +4761,166 @@ class MPToken_test : public beast::unit_test::Suite env(pay(ed, gw, btc(10)), Path(~btc), Sendmax(eth(10))); env.close(); } - // Multiple steps: CAD/USD, USD/BTC, BTC/ETH + + // Multiple steps: CAD/USD, USD/BTC, BTC/ETH. + // takerGets can transfer if: + // - CanTransfer is set + // - The offer's owner is the issuer + // - BookStep is the last step, which means strand's destination is + // the issuer + // takerPays can transfer if + // - BookStep is the first step, which means strand's source is + // the issuer + // - The offer's owner is the issuer + // - Previous step is BookStep, which transfers per above + // - CanTransfer is set { - auto const ed = Account{"ed"}; - Env env{*this, features}; - env.fund(XRP(1'000), gw, alice, carol, bob, ed); - env.close(); - MPTTester btc( - {.env = env, - .issuer = gw, - .holders = {alice, carol, bob}, - .pay = 1'000, - .flags = tfMPTCanLock | kMptDexFlags, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - MPTTester eth( - {.env = env, - .issuer = gw, - .holders = {alice, carol, bob}, - .pay = 1'000, - .flags = tfMPTCanLock | kMptDexFlags, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - MPTTester usd( - {.env = env, - .issuer = gw, - .holders = {alice, carol, bob}, - .pay = 1'000, - .flags = kMptDexFlags | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - MPTTester cad( - {.env = env, - .issuer = gw, - .holders = {alice, carol, bob}, - .pay = 1'000, - .flags = kMptDexFlags | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - // takerGets can transfer if: - // - CanTransfer is set - // - The offer's owner is the issuer - // - BookStep is the last step, which means strand's destination is - // the issuer - // takerPays can transfer if - // - BookStep is the first step, which means strand's source is - // the issuer - // - The offer's owner is the issuer - // - Previous step is BookStep, which transfers per above - // - CanTransfer is set - env(offer(bob, cad(100), usd(100)), Txflags(tfPassive)); - env(offer(bob, usd(100), btc(100)), Txflags(tfPassive)); - env(offer(bob, btc(100), eth(100)), Txflags(tfPassive)); - env.close(); - BEAST_EXPECT(expectOffers(env, bob, 3)); - btc.set({.mutableFlags = tmfMPTSetCanTransfer}); - usd.set({.mutableFlags = tmfMPTClearCanTransfer}); - // TakerGets - // fail - CAD/USD is owned by bob - env(pay(alice, carol, eth(1)), - Path(~usd, ~btc, ~eth), - Sendmax(cad(1)), - Ter(tecPATH_PARTIAL)); - auto seq(env.seq(gw)); - env(offer(gw, usd(1), btc(1)), Txflags(tfPassive)); - env.close(); - // fail - CAD/USD is owned by bob - env(pay(alice, carol, eth(1)), - Path(~usd, ~btc, ~eth), - Sendmax(cad(1)), - Ter(tecPATH_PARTIAL)); - env.close(); - env(offerCancel(gw, seq)); - env(offer(gw, cad(1), usd(1)), Txflags(tfPassive)); - env.close(); - BEAST_EXPECT(expectOffers(env, bob, 3)); - // succeed - CAD/USD is owned by issuer - env(pay(alice, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); - env.close(); - // bob's CAD/USD is deleted - BEAST_EXPECT(expectOffers(env, bob, 2)); - env(offer(bob, cad(100), usd(100)), Txflags(tfPassive)); - BEAST_EXPECT(expectOffers(env, gw, 0)); - usd.set({.mutableFlags = tmfMPTSetCanTransfer}); - eth.set({.mutableFlags = tmfMPTClearCanTransfer}); - // fail - BTC/ETH is owned by bob, destination is carol - env(pay(alice, carol, eth(1)), - Path(~usd, ~btc, ~eth), - Sendmax(cad(1)), - Ter(tecPATH_PARTIAL)); - env.close(); - BEAST_EXPECT(expectOffers(env, bob, 3)); - // succeed - destination is an issuer - env(pay(alice, gw, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); - env.close(); - BEAST_EXPECT(expectOffers(env, bob, 3)); - // TakerPays - eth.set({.mutableFlags = tmfMPTSetCanTransfer}); - cad.set({.mutableFlags = tmfMPTClearCanTransfer}); - // fail - CAD/USD is owned by bob, source is alice - env(pay(alice, carol, eth(1)), - Path(~usd, ~btc, ~eth), - Sendmax(cad(1)), - Ter(tecPATH_PARTIAL)); - // succeed - source is the issuer - env(pay(gw, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); - env.close(); - env(offer(gw, cad(1), usd(1)), Txflags(tfPassive)); - env.close(); - // succeed - CAD/USD is owned by issuer - env(pay(alice, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); - env.close(); - BEAST_EXPECT(expectOffers(env, gw, 0)); - BEAST_EXPECT(expectOffers(env, bob, 2)); - cad.set({.mutableFlags = tmfMPTSetCanTransfer}); - btc.set({.mutableFlags = tmfMPTClearCanTransfer}); - env(offer(bob, cad(1), usd(1)), Txflags(tfPassive)); - env(offer(gw, usd(1), btc(1)), Txflags(tfPassive)); - env.close(); - // succeed - USD/BTC is owned by issuer - env(pay(alice, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); - env.close(); - BEAST_EXPECT(expectOffers(env, gw, 0)); + // enum to indicate which MPT doesn't set CanTransfer flag. + enum class NoTransferMPT { BTC, ETH, USD, CAD }; + + // Lambda to test multi-step payment with one of the MPTs not setting CanTransfer flag. + auto const testMultiStepMPTCanTransfer = [&](NoTransferMPT const noTransferMPT, + auto const& test) { + auto const getFlags = [&](NoTransferMPT const mpt) { + return mpt == noTransferMPT ? tfMPTCanTrade : kMptDexFlags; + }; + + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol, bob); + env.close(); + MPTTester const btc( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = getFlags(NoTransferMPT::BTC)}); + MPTTester const eth( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = getFlags(NoTransferMPT::ETH)}); + MPTTester const usd( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = getFlags(NoTransferMPT::USD)}); + MPTTester const cad( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = getFlags(NoTransferMPT::CAD)}); + + env(offer(bob, cad(100), usd(100)), Txflags(tfPassive)); + env(offer(bob, usd(100), btc(100)), Txflags(tfPassive)); + env(offer(bob, btc(100), eth(100)), Txflags(tfPassive)); + env.close(); + + test(env, btc, eth, usd, cad); + }; + + // USD starts without MPTCanTransfer. + testMultiStepMPTCanTransfer( + NoTransferMPT::USD, + [&](Env& env, + MPTTester const& btc, + MPTTester const& eth, + MPTTester const& usd, + MPTTester const& cad) { + BEAST_EXPECT(expectOffers(env, bob, 3)); + + // fail - CAD/USD is owned by bob + env(pay(alice, carol, eth(1)), + Path(~usd, ~btc, ~eth), + Sendmax(cad(1)), + Ter(tecPATH_PARTIAL)); + + auto seq(env.seq(gw)); + env(offer(gw, usd(1), btc(1)), Txflags(tfPassive)); + env.close(); + // fail - CAD/USD is owned by bob + env(pay(alice, carol, eth(1)), + Path(~usd, ~btc, ~eth), + Sendmax(cad(1)), + Ter(tecPATH_PARTIAL)); + env.close(); + env(offerCancel(gw, seq)); + env(offer(gw, cad(1), usd(1)), Txflags(tfPassive)); + env.close(); + BEAST_EXPECT(expectOffers(env, bob, 3)); + // succeed - CAD/USD is owned by issuer + env(pay(alice, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); + env.close(); + // bob's CAD/USD is deleted. + BEAST_EXPECT(expectOffers(env, bob, 2)); + env(offer(bob, cad(100), usd(100)), Txflags(tfPassive)); + BEAST_EXPECT(expectOffers(env, gw, 0)); + }); + + // ETH starts without MPTCanTransfer. + testMultiStepMPTCanTransfer( + NoTransferMPT::ETH, + [&](Env& env, + MPTTester const& btc, + MPTTester const& eth, + MPTTester const& usd, + MPTTester const& cad) { + // fail - BTC/ETH is owned by bob, destination is carol + env(pay(alice, carol, eth(1)), + Path(~usd, ~btc, ~eth), + Sendmax(cad(1)), + Ter(tecPATH_PARTIAL)); + env.close(); + BEAST_EXPECT(expectOffers(env, bob, 3)); + + // succeed - destination is an issuer + env(pay(alice, gw, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); + env.close(); + BEAST_EXPECT(expectOffers(env, bob, 3)); + }); + + // CAD starts without MPTCanTransfer. + testMultiStepMPTCanTransfer( + NoTransferMPT::CAD, + [&](Env& env, + MPTTester const& btc, + MPTTester const& eth, + MPTTester const& usd, + MPTTester const& cad) { + // fail - CAD/USD is owned by bob, source is alice + env(pay(alice, carol, eth(1)), + Path(~usd, ~btc, ~eth), + Sendmax(cad(1)), + Ter(tecPATH_PARTIAL)); + // succeed - source is the issuer + env(pay(gw, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); + env.close(); + env(offer(gw, cad(1), usd(1)), Txflags(tfPassive)); + env.close(); + // succeed - CAD/USD is owned by issuer + env(pay(alice, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); + env.close(); + BEAST_EXPECT(expectOffers(env, gw, 0)); + BEAST_EXPECT(expectOffers(env, bob, 2)); + }); + + // BTC starts without MPTCanTransfer. + testMultiStepMPTCanTransfer( + NoTransferMPT::BTC, + [&](Env& env, + MPTTester const& btc, + MPTTester const& eth, + MPTTester const& usd, + MPTTester const& cad) { + env(offer(gw, usd(1), btc(1)), Txflags(tfPassive)); + env.close(); + // succeed - USD/BTC is owned by issuer + env(pay(alice, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); + env.close(); + BEAST_EXPECT(expectOffers(env, gw, 0)); + }); } // MPTCanTrade is not set @@ -5057,48 +4934,38 @@ class MPToken_test : public beast::unit_test::Suite .holders = {alice, carol, bob}, .pay = 1'000, .flags = tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateCanTrade}); + .mutableFlags = tmfMPTCanEnableCanTrade}); MPTTester const eth( {.env = env, .issuer = gw, .holders = {alice, carol, bob}, .pay = 1'000, - .flags = tfMPTCanTransfer | tfMPTCanTrade, - .mutableFlags = tmfMPTCanMutateCanTrade}); + .flags = kMptDexFlags}); MPTTester const usd( {.env = env, .issuer = gw, .holders = {alice, carol, bob}, .pay = 1'000, - .flags = tfMPTCanTransfer | tfMPTCanTrade, - .mutableFlags = tmfMPTCanMutateCanTrade}); + .flags = kMptDexFlags}); env(pay(alice, carol, eth(1)), Path(~eth), Sendmax(btc(1)), Ter(tecNO_PERMISSION)); env(pay(alice, carol, btc(1)), Path(~btc), Sendmax(eth(1)), Ter(tecNO_PERMISSION)); env.close(); + // Enable MPTCanTrade so BTC can be crossed through offers. btc.set({.mutableFlags = tmfMPTSetCanTrade}); env(offer(bob, XRP(1), btc(1))); env(offer(bob, btc(1), eth(1))); env(offer(bob, eth(1), usd(1))); env.close(); - btc.set({.mutableFlags = tmfMPTClearCanTrade}); + BEAST_EXPECT(expectOffers(env, bob, 3)); + env(pay(gw, carol, usd(1)), Path(~btc, ~eth, ~usd), Sendmax(XRP(1)), - Txflags(tfPartialPayment | tfNoRippleDirect), - Ter(tecNO_PERMISSION)); + Txflags(tfPartialPayment | tfNoRippleDirect)); env.close(); - BEAST_EXPECT(expectOffers(env, bob, 3)); - - env(pay(carol, bob, btc(10)), Sendmax(XRP(10)), Ter(tecNO_PERMISSION)); - env(pay(carol, bob, XRP(10)), Sendmax(btc(10)), Ter(tecNO_PERMISSION)); - env(pay(gw, bob, btc(10)), Sendmax(XRP(10)), Ter(tecNO_PERMISSION)); - env(pay(gw, bob, XRP(10)), Sendmax(btc(10)), Ter(tecNO_PERMISSION)); - env(pay(carol, gw, btc(10)), Sendmax(XRP(10)), Ter(tecNO_PERMISSION)); - env(pay(carol, gw, XRP(10)), Sendmax(btc(10)), Ter(tecNO_PERMISSION)); - env.close(); - BEAST_EXPECT(expectOffers(env, bob, 3)); + BEAST_EXPECT(expectOffers(env, bob, 0)); } // Holders are locked @@ -6891,7 +6758,7 @@ class MPToken_test : public beast::unit_test::Suite .issuer = gw, .holders = {alice, carol}, .flags = tfMPTCanTrade, - .mutableFlags = tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanEnableCanTransfer}); // src is issuer uint256 checkId{keylet::check(gw, env.seq(gw)).key}; @@ -6933,13 +6800,8 @@ class MPToken_test : public beast::unit_test::Suite env.close(); env(pay(gw, alice, mpt(10))); env.close(); - // can't cash - mpt.set({.account = gw, .mutableFlags = tmfMPTClearCanTransfer}); - env.close(); - env(check::cash(carol, checkId, mpt(10)), Ter(tecNO_AUTH)); - env.close(); - // can cash - mpt.set({.account = gw, .mutableFlags = tmfMPTSetCanTransfer}); + + // can cash since MPTCanTransfer is enabled env(check::cash(carol, checkId, mpt(10))); env.close(); } @@ -7358,296 +7220,332 @@ class MPToken_test : public beast::unit_test::Suite Env env(*this); env.fund(XRP(1'000'000), gw, alice, carol); - auto usd = MPTTester( - {.env = env, - .issuer = gw, - .flags = tfMPTCanLock | kMptDexFlags, - .mutableFlags = tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanTransfer | - tmfMPTCanMutateCanClawback | tmfMPTCanMutateCanTrade}); - auto eur = MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 1'000'000}); - auto const increment = env.current()->fees().increment; auto const txfee = Fee(drops(increment)); auto const badMPT = MPT(gw, 1'000); - auto createDeleteAMM = [&](Account const& lp) { - AMM amm( - env, - lp, - usd(1'000), - eur(1'000), - CreateArg{.fee = static_cast(increment.value())}); - amm.withdrawAll(lp); - BEAST_EXPECT(!amm.ammExists()); + auto const makeMPT = [&](std::uint32_t const flags, + Holders holders = {}, + std::uint64_t const pay = 0, + std::optional const mutableFlags = + std::nullopt) { + return MPTTester( + {.env = env, + .issuer = gw, + .holders = holders, + .pay = pay ? std::optional{pay} : std::nullopt, + .flags = flags, + .mutableFlags = mutableFlags}); + }; + + auto const makeDexMPT = [&](Holders holders = {}, std::uint64_t const pay = 0) { + return makeMPT( + tfMPTCanLock | kMptDexFlags, + holders, + pay, + tmfMPTCanEnableRequireAuth | tmfMPTCanEnableCanTransfer | + tmfMPTCanEnableCanTrade); + }; + + auto const makeNoTransferMPT = [&](Holders holders = {}, std::uint64_t const pay = 0) { + return makeMPT( + tfMPTCanLock | tfMPTCanTrade, holders, pay, tmfMPTCanEnableCanTransfer); + }; + + auto const makeNoTradeMPT = [&](Holders holders = {}, std::uint64_t const pay = 0) { + return makeMPT( + tfMPTCanLock | tfMPTCanTransfer, holders, pay, tmfMPTCanEnableCanTrade); }; - // // AMMCreate - // - - auto createJv = AMM::createJv(alice, badMPT(1'000), eur(1'000), 0); - - auto createFail = [&](Account const& account, auto const& err) { - createJv[sfAccount] = account.human(); - env(createJv, txfee, Ter(err)); - env.close(); - }; - - // MPTokenIssuance doesn't exist - - createFail(alice, tecOBJECT_NOT_FOUND); - - // MPToken doesn't exist - - createJv[sfAmount] = STAmount{usd(1'000)}.getJson(); - createFail(alice, tecNO_AUTH); - - // alice authorizes MPToken, can create - usd.authorize({.account = alice}); - env(pay(gw, alice, usd(1'000'000)), txfee); - env.close(); - createDeleteAMM(alice); - - // MPTLock is set - - // alice and issuer can't create - usd.set({.flags = tfMPTLock}); - createFail(alice, tecLOCKED); - createFail(gw, tecLOCKED); - - // MPTRequireAuth is set - - // alice is not authorized - usd.set({.flags = tfMPTUnlock}); - usd.set({.mutableFlags = tmfMPTSetRequireAuth}); - createFail(alice, tecNO_AUTH); - // issuer can create - createDeleteAMM(gw); - - // alice is authorized, can create - usd.authorize({.account = gw, .holder = alice}); - createDeleteAMM(alice); - - // MPTCanTransfer is not set - - usd.set({.mutableFlags = tmfMPTClearRequireAuth}); - usd.set({.mutableFlags = tmfMPTClearCanTransfer}); - // alice can't create - createFail(alice, tecNO_AUTH); - // issuer can create - createDeleteAMM(gw); - usd.set({.mutableFlags = tmfMPTSetCanTransfer}); - // alice can create - createDeleteAMM(alice); - - // MPTCanTrade is not set - - usd.set({.mutableFlags = tmfMPTSetCanTransfer}); - usd.set({.mutableFlags = tmfMPTClearCanTrade}); - // alice and issuer can't create - createFail(alice, tecNO_PERMISSION); - createFail(gw, tecNO_PERMISSION); - usd.set({.mutableFlags = tmfMPTSetCanTrade}); - - // - // AMMDeposit - // - - AMM amm(env, gw, usd(1'000), eur(1'000)); - - // MPTokenIssuance doesn't exist - - amm.deposit( - {.account = alice, - .asset1In = badMPT(1), - .asset2In = eur(1), - .assets = std::make_pair(badMPT, eur), - .err = Ter(terNO_AMM)}); - - // MPToken doesn't exist - - amm.deposit( - {.account = carol, .asset1In = usd(1), .asset2In = eur(1), .err = Ter(tecNO_AUTH)}); - - // MPTLock is set - - usd.set({.flags = tfMPTLock}); - // alice and issuer can't deposit - for (auto const& account : {carol, gw}) { + auto usd = makeDexMPT(); + auto eur = makeDexMPT({alice}, 1'000'000); + + auto createDeleteAMM = [&](auto const& asset, Account const& lp) { + AMM amm( + env, + lp, + asset(1'000), + eur(1'000), + CreateArg{.fee = static_cast(increment.value())}); + amm.withdrawAll(lp); + BEAST_EXPECT(!amm.ammExists()); + }; + + auto createFail = [&](auto const& asset, Account const& account, auto const& err) { + auto const createJv = AMM::createJv(account, asset(1'000), eur(1'000), 0); + env(createJv, txfee, Ter(err)); + env.close(); + }; + + // MPTokenIssuance doesn't exist + createFail(badMPT, alice, tecOBJECT_NOT_FOUND); + + // MPToken doesn't exist + createFail(usd, alice, tecNO_AUTH); + + // alice authorizes MPToken, can create + usd.authorize({.account = alice}); + env(pay(gw, alice, usd(1'000'000)), txfee); + env.close(); + createDeleteAMM(usd, alice); + + // MPTLock is set + // alice and issuer can't create + usd.set({.flags = tfMPTLock}); + createFail(usd, alice, tecLOCKED); + createFail(usd, gw, tecLOCKED); + + // MPTRequireAuth is set + // alice is not authorized + usd.set({.flags = tfMPTUnlock}); + usd.set({.mutableFlags = tmfMPTSetRequireAuth}); + createFail(usd, alice, tecNO_AUTH); + // issuer can create + createDeleteAMM(usd, gw); + + // alice is authorized, can create + usd.authorize({.account = gw, .holder = alice}); + createDeleteAMM(usd, alice); + + // MPTCanTransfer is not set + { + auto usd2 = makeNoTransferMPT({alice}, 1'000'000); + + // alice can't create + createFail(usd2, alice, tecNO_AUTH); + // issuer can create + createDeleteAMM(usd2, gw); + usd2.set({.mutableFlags = tmfMPTSetCanTransfer}); + // alice can create + createDeleteAMM(usd2, alice); + } + + // MPTCanTrade is not set + { + auto usd3 = makeNoTradeMPT({alice}, 1'000'000); + + // alice and issuer can't create + createFail(usd3, alice, tecNO_PERMISSION); + createFail(usd3, gw, tecNO_PERMISSION); + usd3.set({.mutableFlags = tmfMPTSetCanTrade}); + // alice can create + createDeleteAMM(usd3, alice); + } + } + + // AMMDeposit + { + auto usd = makeDexMPT(); + auto eur = makeDexMPT({alice}, 1'000'000); + AMM amm(env, gw, usd(1'000), eur(1'000)); + + // MPTokenIssuance doesn't exist amm.deposit( - {.account = account, + {.account = alice, + .asset1In = badMPT(1), + .asset2In = eur(1), + .assets = std::make_pair(badMPT, eur), + .err = Ter(terNO_AMM)}); + + // MPToken doesn't exist + amm.deposit( + {.account = carol, .asset1In = usd(1), .asset2In = eur(1), - .err = Ter(tecLOCKED)}); + .err = Ter(tecNO_AUTH)}); + + // Fund carol for the AMMDeposit checks. + usd.authorize({.account = carol}); + env(pay(gw, carol, usd(1'000'000))); + eur.authorize({.account = carol}); + env(pay(gw, carol, eur(1'000'000))); + env.close(); + + // MPTLock is set + usd.set({.flags = tfMPTLock}); + + // alice and issuer can't deposit + for (auto const& account : {carol, gw}) + { + amm.deposit( + {.account = account, + .asset1In = usd(1), + .asset2In = eur(1), + .err = Ter(tecLOCKED)}); + amm.deposit( + {.account = account, + .asset1In = eur(1), + .assets = std::make_pair(eur, usd), + .err = Ter(tecLOCKED)}); + } + usd.set({.flags = tfMPTUnlock}); + + // MPTRequireAuth is set + // carol is not authorized by the issuer + usd.set({.mutableFlags = tmfMPTSetRequireAuth}); + env.close(); amm.deposit( - {.account = account, + {.account = carol, + .asset1In = usd(1), + .asset2In = eur(1), + .err = Ter(tecNO_AUTH)}); + amm.deposit( + {.account = carol, .asset1In = eur(1), .assets = std::make_pair(eur, usd), - .err = Ter(tecLOCKED)}); + .err = Ter(tecNO_AUTH)}); + // issuer can deposit + amm.deposit({.account = gw, .tokens = 1'000}); + // carol is authorized, can deposit + usd.authorize({.account = gw, .holder = carol}); + amm.deposit({.account = carol, .tokens = 1'000}); + // Can't authorize or unauthorize AMM pseudo-account + usd.authorize( + {.account = gw, + .holder = Account{"amm", amm.ammAccount()}, + .err = tecNO_PERMISSION}); + usd.authorize( + {.account = gw, + .holder = Account{"amm", amm.ammAccount()}, + .flags = tfMPTUnauthorize, + .err = tecNO_PERMISSION}); + + // MPTCanTransfer is not set + { + auto usd2 = makeNoTransferMPT({carol}, 1'000'000); + AMM amm2(env, gw, usd2(1'000), eur(1'000)); + + // carol can't deposit + amm2.deposit( + {.account = carol, + .asset1In = usd2(1), + .asset2In = eur(1), + .err = Ter(tecNO_AUTH)}); + amm2.deposit( + {.account = carol, + .asset1In = eur(1), + .assets = std::make_pair(eur, usd2), + .err = Ter(tecNO_AUTH)}); + // issuer can deposit + amm2.deposit({.account = gw, .tokens = 1'000}); + usd2.set({.mutableFlags = tmfMPTSetCanTransfer}); + // carol can deposit + amm2.deposit({.account = carol, .tokens = 1'000}); + } } - usd.set({.flags = tfMPTUnlock}); - // MPTRequireAuth is set - - // carol authorizes MPToken but is not authorized by the issuer - usd.authorize({.account = carol}); - env(pay(gw, carol, usd(1'000'000))); - // carol authorizes EUR - eur.authorize({.account = carol}); - env(pay(gw, carol, eur(1'000'000))); - usd.set({.mutableFlags = tmfMPTSetRequireAuth}); - env.close(); - amm.deposit( - {.account = carol, .asset1In = usd(1), .asset2In = eur(1), .err = Ter(tecNO_AUTH)}); - amm.deposit( - {.account = carol, - .asset1In = eur(1), - .assets = std::make_pair(eur, usd), - .err = Ter(tecNO_AUTH)}); - // issuer can deposit - amm.deposit({.account = gw, .tokens = 1'000}); - // carol is authorized, can deposit - usd.authorize({.account = gw, .holder = carol}); - amm.deposit({.account = carol, .tokens = 1'000}); - // Can't authorize or unauthorize AMM pseudo-account - usd.authorize( - {.account = gw, - .holder = Account{"amm", amm.ammAccount()}, - .err = tecNO_PERMISSION}); - usd.authorize( - {.account = gw, - .holder = Account{"amm", amm.ammAccount()}, - .flags = tfMPTUnauthorize, - .err = tecNO_PERMISSION}); - - // MPTCanTransfer is not set - - usd.set({.mutableFlags = tmfMPTClearRequireAuth}); - usd.set({.mutableFlags = tmfMPTClearCanTransfer}); - // carol can't deposit - amm.deposit( - {.account = carol, .asset1In = usd(1), .asset2In = eur(1), .err = Ter(tecNO_AUTH)}); - amm.deposit( - {.account = carol, - .asset1In = eur(1), - .assets = std::make_pair(eur, usd), - .err = Ter(tecNO_AUTH)}); - // issuer can deposit - amm.deposit({.account = gw, .tokens = 1'000}); - // carol can deposit - usd.set({.mutableFlags = tmfMPTSetCanTransfer}); - amm.deposit({.account = carol, .tokens = 1'000}); - - // MPTCanTrade is not set - - usd.set({.mutableFlags = tmfMPTSetCanTransfer}); - usd.set({.mutableFlags = tmfMPTClearCanTrade}); - amm.deposit({.account = gw, .tokens = 1'000, .err = Ter(tecNO_PERMISSION)}); - amm.deposit({.account = carol, .tokens = 1'000, .err = Ter(tecNO_PERMISSION)}); - usd.set({.mutableFlags = tmfMPTSetCanTrade}); - - // // AMMWithdraw - // - - // MPTokenIssuance doesn't exist - - amm.withdraw( - WithdrawArg{ - .account = carol, - .asset1Out = badMPT(1), - .asset2Out = eur(1), - .assets = std::make_pair(badMPT, eur), - .err = Ter(terNO_AMM)}); - - // MPToken doesn't exist - doesn't apply since MPToken is created - // on withdraw in this case - - // MPTLock is set - - usd.set({.flags = tfMPTLock}); - // carol and issuer can't withdraw - for (auto const& account : {carol, gw}) { + auto usd = makeDexMPT(); + auto eur = makeDexMPT({carol}, 1'000'000); + AMM amm(env, gw, usd(1'000), eur(1'000)); + + usd.authorize({.account = carol}); + env(pay(gw, carol, usd(1'000'000))); + env.close(); + amm.deposit({.account = carol, .tokens = 1'000}); + + // MPTokenIssuance doesn't exist amm.withdraw( - {.account = account, + WithdrawArg{ + .account = carol, + .asset1Out = badMPT(1), + .asset2Out = eur(1), + .assets = std::make_pair(badMPT, eur), + .err = Ter(terNO_AMM)}); + + // MPToken doesn't exist - doesn't apply since MPToken is created + // on withdraw in this case + + // MPTLock is set + usd.set({.flags = tfMPTLock}); + // carol and issuer can't withdraw + for (auto const& account : {carol, gw}) + { + amm.withdraw( + {.account = account, + .asset1Out = usd(1), + .asset2Out = eur(1), + .err = Ter(tecLOCKED)}); + amm.withdraw({.account = account, .tokens = 1'000, .err = Ter(tecLOCKED)}); + // can single withdraw another asset + amm.withdraw( + {.account = account, + .asset1Out = eur(1), + .assets = std::make_pair(eur, usd)}); + } + usd.set({.flags = tfMPTUnlock}); + + // MPTRequireAuth is set + usd.set({.mutableFlags = tmfMPTSetRequireAuth}); + usd.authorize({.account = gw, .holder = carol, .flags = tfMPTUnauthorize}); + // carol can't withdraw + amm.withdraw( + {.account = carol, .asset1Out = usd(1), .asset2Out = eur(1), - .err = Ter(tecLOCKED)}); - amm.withdraw({.account = account, .tokens = 1'000, .err = Ter(tecLOCKED)}); - // can single withdraw another asset + .err = Ter(tecNO_AUTH)}); + // can withdraw another asset amm.withdraw( - {.account = account, .asset1Out = eur(1), .assets = std::make_pair(eur, usd)}); + {.account = carol, .asset1Out = eur(1), .assets = std::make_pair(eur, usd)}); + // issuer can withdraw + amm.withdraw({.account = gw, .asset1Out = usd(1), .asset2Out = eur(1)}); + // carol is authorized, can withdraw + usd.authorize({.account = gw, .holder = carol}); + amm.withdraw({.account = carol, .asset1Out = usd(1), .asset2Out = eur(1)}); + + // MPTCanTransfer is not set, allow to withdraw + { + auto usd2 = makeNoTransferMPT({carol}, 1'000'000); + AMM amm2(env, gw, usd2(1'000), eur(1'000)); + + // carol cannot deposit usd2 without MPTCanTransfer, so give her + // LP tokens directly to test the withdraw path. + env.trust(STAmount{amm2.lptIssue(), 1'000}, carol); + env(pay(gw, carol, STAmount{amm2.lptIssue(), 100})); + env.close(); + + // carol can withdraw + amm2.withdraw({.account = carol, .asset1Out = usd2(1), .asset2Out = eur(1)}); + // can withdraw another asset + amm2.withdraw( + {.account = carol, + .asset1Out = eur(1), + .assets = std::make_pair(eur, usd2)}); + // issuer can withdraw + amm2.withdraw({.account = gw, .asset1Out = usd2(1), .asset2Out = eur(1)}); + // Holder can't transfer to another holder + env.fund(XRP(1'000), bob); + usd2.authorize({.account = bob}); + env(pay(carol, bob, usd2(1)), Ter(tecNO_AUTH)); + usd2.authorize({.account = bob, .flags = tfMPTUnauthorize}); + // Can redeem + env(pay(carol, gw, usd2(1))); + usd2.set({.mutableFlags = tmfMPTSetCanTransfer}); + // carol can withdraw + amm2.withdraw({.account = carol, .asset1Out = usd2(1), .asset2Out = eur(1)}); + } + + // MPToken created on withdraw + { + auto usd3 = makeDexMPT(); + auto eur3 = makeDexMPT({carol}, 1'000'000); + AMM amm3(env, gw, usd3(1'000), eur3(1'000)); + + BEAST_EXPECT(env.le(keylet::mptoken(usd3.issuanceID(), carol)) == nullptr); + // single-deposit EUR + amm3.deposit( + {.account = carol, + .asset1In = eur3(1'000), + .assets = std::make_pair(eur3, usd3)}); + BEAST_EXPECT(env.le(keylet::mptoken(usd3.issuanceID(), carol)) == nullptr); + // withdraw in USD to create MPToken + amm3.withdraw({.account = carol, .asset1Out = usd3(100)}); + BEAST_EXPECT(env.le(keylet::mptoken(usd3.issuanceID(), carol))); + } } - usd.set({.flags = tfMPTUnlock}); - - // MPTRequireAuth is set - - usd.set({.mutableFlags = tmfMPTSetRequireAuth}); - usd.authorize({.account = gw, .holder = carol, .flags = tfMPTUnauthorize}); - // carol can't withdraw - amm.withdraw( - {.account = carol, - .asset1Out = usd(1), - .asset2Out = eur(1), - .err = Ter(tecNO_AUTH)}); - // can withdraw another asset - amm.withdraw( - {.account = carol, .asset1Out = eur(1), .assets = std::make_pair(eur, usd)}); - // issuer can withdraw - amm.withdraw({.account = gw, .asset1Out = usd(1), .asset2Out = eur(1)}); - // carol is authorized, can withdraw - usd.authorize({.account = gw, .holder = carol}); - amm.withdraw({.account = carol, .asset1Out = usd(1), .asset2Out = eur(1)}); - - // MPTCanTransfer is not set, allow to withdraw - - usd.set({.mutableFlags = tmfMPTClearRequireAuth}); - usd.set({.mutableFlags = tmfMPTClearCanTransfer}); - // carol can withdraw - amm.withdraw({.account = carol, .asset1Out = usd(1), .asset2Out = eur(1)}); - // can withdraw another asset - amm.withdraw( - {.account = carol, .asset1Out = eur(1), .assets = std::make_pair(eur, usd)}); - // issuer can withdraw - amm.withdraw({.account = gw, .asset1Out = usd(1), .asset2Out = eur(1)}); - // Holder can't transfer to another holder - env.fund(XRP(1'000), bob); - usd.authorize({.account = bob}); - env(pay(carol, bob, usd(1)), Ter(tecNO_AUTH)); - usd.authorize({.account = bob, .flags = tfMPTUnauthorize}); - // Can redeem - env(pay(carol, gw, usd(1))); - // carol can withdraw - usd.set({.mutableFlags = tmfMPTSetCanTransfer}); - amm.withdraw({.account = carol, .asset1Out = usd(1), .asset2Out = eur(1)}); - - usd.set({.mutableFlags = tmfMPTSetCanTransfer}); - - // MPTCanTrade is not set, allow to withdraw - - usd.set({.mutableFlags = tmfMPTClearCanTrade}); - amm.withdraw({.account = gw, .tokens = 1'000}); - amm.withdraw({.account = carol, .tokens = 1'000}); - // Can't DEX - amm.deposit( - DepositArg{.account = carol, .asset1In = usd(1), .err = Ter(tecNO_PERMISSION)}); - usd.set({.mutableFlags = tmfMPTSetCanTrade}); - - // MPToken created on withdraw - - // redeem all carol's USD and unauthorize USD - amm.withdrawAll(carol); - env(pay(carol, gw, env.balance(carol, usd))); - usd.authorize({.account = carol, .flags = tfMPTUnauthorize}); - BEAST_EXPECT(env.le(keylet::mptoken(usd.issuanceID(), carol)) == nullptr); - // single-deposit EUR - amm.deposit( - {.account = carol, .asset1In = eur(1'000), .assets = std::make_pair(eur, usd)}); - // withdraw in USD to create MPToken - amm.withdraw({.account = carol, .asset1Out = usd(100)}); - BEAST_EXPECT(env.le(keylet::mptoken(usd.issuanceID(), carol))); } } @@ -7707,41 +7605,37 @@ class MPToken_test : public beast::unit_test::Suite Env env(*this); env.fund(XRP(1'000), gw, alice, carol); - MPTTester mpt( - {.env = env, - .issuer = gw, - .holders = {alice, carol}, - .pay = 100, - .flags = kMptDexFlags, - .mutableFlags = tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanTrade}); + auto const checkCanTradeCanTransfer = [&](std::uint32_t const flags, + TER const gwToGw, + TER const gwToAlice, + TER const aliceToAlice, + TER const aliceToCarol) { + MPTTester const mpt( + {.env = env, .issuer = gw, .holders = {alice, carol}, .pay = 100, .flags = flags}); + + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, gw) == gwToGw); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, alice) == gwToAlice); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, alice) == aliceToAlice); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, carol) == aliceToCarol); + }; // Both flags are enabled - BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, gw, gw))); - BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, gw, alice))); - BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, alice, alice))); - BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, alice, carol))); + checkCanTradeCanTransfer(kMptDexFlags, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS); // MPTCanTrade is disabled - mpt.set({.mutableFlags = tmfMPTClearCanTrade}); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, gw) == tecNO_PERMISSION); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, alice) == tecNO_PERMISSION); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, alice) == tecNO_PERMISSION); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, carol) == tecNO_PERMISSION); + checkCanTradeCanTransfer( + tfMPTCanTransfer, + tecNO_PERMISSION, + tecNO_PERMISSION, + tecNO_PERMISSION, + tecNO_PERMISSION); // MPTCanTransfer is disabled - mpt.set({.mutableFlags = tmfMPTSetCanTrade}); - mpt.set({.mutableFlags = tmfMPTClearCanTransfer}); - BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, gw, gw))); - BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, gw, alice))); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, alice) == tecNO_AUTH); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, carol) == tecNO_AUTH); + checkCanTradeCanTransfer(tfMPTCanTrade, tesSUCCESS, tesSUCCESS, tecNO_AUTH, tecNO_AUTH); // Both flags are disabled - mpt.set({.mutableFlags = tmfMPTClearCanTrade}); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, gw) == tecNO_PERMISSION); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, alice) == tecNO_PERMISSION); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, alice) == tecNO_PERMISSION); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, carol) == tecNO_PERMISSION); + checkCanTradeCanTransfer( + 0, tecNO_PERMISSION, tecNO_PERMISSION, tecNO_PERMISSION, tecNO_PERMISSION); } public: diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index 2c83ad91ec..065b6b0044 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -1599,7 +1599,7 @@ class Vault_test : public beast::unit_test::Suite {.flags = tfMPTCanTransfer | tfMPTCanLock | (args.enableClawback ? tfMPTCanClawback : kNone) | (args.requireAuth ? tfMPTRequireAuth : kNone), - .mutableFlags = tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanEnableCanTransfer}); PrettyAsset const asset = mptt.issuanceID(); mptt.authorize({.account = owner}); mptt.authorize({.account = depositor}); @@ -2238,149 +2238,6 @@ class Vault_test : public beast::unit_test::Suite env.close(); } - testCase([this]( - Env& env, - Account const&, - Account const& owner, - Account const& depositor, - PrettyAsset const& asset, - Vault& vault, - MPTTester& mptt) { - testcase("MPT non-transferable: block deposit, allow withdraw"); - - auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); - env(tx); - env.close(); - - tx = vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(100)}); - env(tx); - env.close(); - - // Issuer governance: clear CanTransfer. New exposure must be - // blocked, but recovery paths must remain open so existing - // depositors are not trapped. - mptt.set({.mutableFlags = tmfMPTClearCanTransfer}); - env.close(); - - // New deposit is blocked. - env(tx, Ter{tecNO_AUTH}); - env.close(); - - // Existing depositor can always withdraw, even though the asset - // is no longer freely transferable. - tx = vault.withdraw({.depositor = depositor, .id = keylet.key, .amount = asset(100)}); - env(tx); - env.close(); - - // Delete vault with zero balance - env(vault.del({.owner = owner, .id = keylet.key})); - }); - - { - testcase("MPT non-transferable: pre-fixCleanup3_2_0 withdraw blocked"); - - // Regression: before fixCleanup3_2_0 a depositor was trapped if - // the issuer cleared lsfMPTCanTransfer. Verify that the legacy - // (broken) behavior is preserved when the amendment is disabled. - Env env{*this, testableAmendments() - fixCleanup3_2_0}; - Account const issuer{"issuer"}; - Account const owner{"owner"}; - Account const depositor{"depositor"}; - env.fund(XRP(10'000), issuer, owner, depositor); - env.close(); - Vault const vault{env}; - - MPTTester mptt{env, issuer, kMptInitNoFund}; - mptt.create( - {.flags = tfMPTCanTransfer | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - PrettyAsset const asset = mptt.issuanceID(); - mptt.authorize({.account = owner}); - mptt.authorize({.account = depositor}); - env(pay(issuer, depositor, asset(1'000))); - env.close(); - - auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); - env(tx); - env.close(); - - env(vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(100)})); - env.close(); - - mptt.set({.mutableFlags = tmfMPTClearCanTransfer}); - env.close(); - - // Pre-amendment: deposit blocked (matches new behavior). - env(vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(100)}), - Ter{tecNO_AUTH}); - env.close(); - - // Pre-amendment: withdraw is also blocked - this is the bug - // that fixCleanup3_2_0 fixes. - env(vault.withdraw({.depositor = depositor, .id = keylet.key, .amount = asset(100)}), - Ter{tecNO_AUTH}); - env.close(); - } - - { - testcase("MPT non-transferable: vault shares inherit restriction"); - - Env env{*this, testableAmendments()}; - Account const issuer{"issuer"}; - Account const owner{"owner"}; - Account const alice{"alice"}; - Account const bob{"bob"}; - env.fund(XRP(10'000), issuer, owner, alice, bob); - env.close(); - Vault const vault{env}; - - MPTTester mptt{env, issuer, kMptInitNoFund}; - mptt.create( - {.flags = tfMPTCanTransfer | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - PrettyAsset const asset = mptt.issuanceID(); - mptt.authorize({.account = owner}); - mptt.authorize({.account = alice}); - mptt.authorize({.account = bob}); - env(pay(issuer, alice, asset(1'000))); - env(pay(issuer, bob, asset(1'000))); - env.close(); - - auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); - env(tx); - env.close(); - - env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(500)})); - // Bob also deposits so he has a share MPToken to receive into. - env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(500)})); - env.close(); - - auto const shares = [&]() -> PrettyAsset { - auto const sle = env.le(keylet); - BEAST_EXPECT(sle != nullptr); - return MPTIssue(sle->at(sfShareMPTID)); - }(); - - // Sanity: while CanTransfer is set on the underlying, peer-to-peer - // share transfers are allowed. - env(pay(alice, bob, shares(1))); - env.close(); - - // Issuer governance: clear CanTransfer on the underlying. - mptt.set({.mutableFlags = tmfMPTClearCanTransfer}); - env.close(); - - // Vault shares inherit the restriction: third-party share-to-share - // payments are blocked. - env(pay(alice, bob, shares(1)), Ter{tecNO_AUTH}); - env.close(); - - // Recovery path: existing share holders can still redeem shares - // for the underlying asset via VaultWithdraw. - env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = shares(1)})); - env.close(); - } - { testcase("MPT locked: vault shares inherit underlying lock"); @@ -2458,56 +2315,6 @@ class Vault_test : public beast::unit_test::Suite BEAST_EXPECT(expectOffers(env, alice, 1)); } - { - testcase("MPT non-transferable: pre-fixCleanup3_2_0 share transfer succeeds"); - - // Regression: before fixCleanup3_2_0 a peer-to-peer share Payment - // succeeded even when the underlying asset's lsfMPTCanTransfer - // was cleared. Verify that the legacy (non-inheriting) behavior - // is preserved when the amendment is disabled. - Env env{*this, testableAmendments() - fixCleanup3_2_0}; - Account const issuer{"issuer"}; - Account const owner{"owner"}; - Account const alice{"alice"}; - Account const bob{"bob"}; - env.fund(XRP(10'000), issuer, owner, alice, bob); - env.close(); - Vault const vault{env}; - - MPTTester mptt{env, issuer, kMptInitNoFund}; - mptt.create( - {.flags = tfMPTCanTransfer | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - PrettyAsset const asset = mptt.issuanceID(); - mptt.authorize({.account = owner}); - mptt.authorize({.account = alice}); - mptt.authorize({.account = bob}); - env(pay(issuer, alice, asset(1'000))); - env(pay(issuer, bob, asset(1'000))); - env.close(); - - auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); - env(tx); - env.close(); - - env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(500)})); - env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(500)})); - env.close(); - - auto const shares = [&]() -> PrettyAsset { - auto const sle = env.le(keylet); - BEAST_EXPECT(sle != nullptr); - return MPTIssue(sle->at(sfShareMPTID)); - }(); - - mptt.set({.mutableFlags = tmfMPTClearCanTransfer}); - env.close(); - - // Pre-amendment: share transfer leaks past underlying restriction. - env(pay(alice, bob, shares(1))); - env.close(); - } - { testcase("MPT CanTrade governance: share inherits underlying on DEX and AMM"); @@ -2522,8 +2329,8 @@ class Vault_test : public beast::unit_test::Suite MPTTester mptt{env, issuer, kMptInitNoFund}; mptt.create( - {.flags = tfMPTCanTransfer | tfMPTCanTrade | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTrade}); + {.flags = tfMPTCanTransfer | tfMPTCanLock, + .mutableFlags = tmfMPTCanEnableCanTrade}); PrettyAsset const asset = mptt.issuanceID(); mptt.authorize({.account = owner}); mptt.authorize({.account = alice}); @@ -2547,38 +2354,18 @@ class Vault_test : public beast::unit_test::Suite return MPTIssue(sle->at(sfShareMPTID)); }(); - // Sanity: while CanTrade is set on the underlying, both the asset - // and the vault share can be placed on the DEX. - env(offer(alice, XRP(1), asset(10))); - env(offer(alice, XRP(1), shares(1))); - env.close(); - - // Issuer governance: clear CanTrade on the underlying. - mptt.set({.mutableFlags = tmfMPTClearCanTrade}); - env.close(); - - // Control: clearing CanTrade on the underlying is observable on - // the DEX path for that asset. + // CanTrade is not set on the underlying, both the asset and + // the vault share are blocked on the DEX. env(offer(alice, XRP(1), asset(10)), Ter{tecNO_PERMISSION}); + env(offer(alice, XRP(1), shares(1)), Ter{tecNO_PERMISSION}); env.close(); - // Control: clearing CanTrade on the underlying is also observable - // on the AMM path for that asset. - AMM const ammUnderlyingFails( + // The inherited CanTrade restriction also blocks AMM creation. + AMM const ammUnderlyingFail( env, alice, XRP(1'000), asset(1'000), Ter{tecNO_PERMISSION}); - - // Post-fixCleanup3_2_0: vault shares inherit the underlying's - // CanTrade restriction on the DEX path (canTrade reads the - // share's sfReferenceHolding and dispatches to the underlying). - env(offer(bob, XRP(1), shares(1)), Ter{tecNO_PERMISSION}); - env.close(); - - // checkMPTAllowed mirrors the inheritance for AMM/Offer- - // crossing/Check paths, so a share AMM also cannot be created - // when the underlying CanTrade is cleared. AMM const ammShares(env, alice, XRP(1'000), shares(100), Ter{tecNO_PERMISSION}); - // Deposit still works (canAddHolding does not consult the field). + // Deposit still works before enabling CanTrade. env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(100)})); env.close(); @@ -2587,9 +2374,19 @@ class Vault_test : public beast::unit_test::Suite env(pay(alice, bob, shares(1))); env.close(); - // Withdraw still works. + // Withdraw still works before enabling CanTrade. env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = asset(100)})); env.close(); + + // Enable CanTrade on the underlying. + mptt.set({.mutableFlags = tmfMPTSetCanTrade}); + env.close(); + + env(offer(alice, XRP(1), asset(10))); + env(offer(alice, XRP(1), shares(1))); + env.close(); + + AMM const ammUnderlying(env, alice, XRP(1'000), asset(1'000)); } { diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 1e127e7c05..7da3305eec 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -40,6 +41,21 @@ namespace xrpl::test::jtx { +struct MPTSetFlagMapping +{ + std::uint32_t setFlag; + std::uint32_t ledgerFlag; +}; + +static constexpr std::array mptSetFlagMappings = {{ + {.setFlag = tmfMPTSetCanLock, .ledgerFlag = lsfMPTCanLock}, + {.setFlag = tmfMPTSetRequireAuth, .ledgerFlag = lsfMPTRequireAuth}, + {.setFlag = tmfMPTSetCanEscrow, .ledgerFlag = lsfMPTCanEscrow}, + {.setFlag = tmfMPTSetCanClawback, .ledgerFlag = lsfMPTCanClawback}, + {.setFlag = tmfMPTSetCanTrade, .ledgerFlag = lsfMPTCanTrade}, + {.setFlag = tmfMPTSetCanTransfer, .ledgerFlag = lsfMPTCanTransfer}, +}}; + void MptFlags::operator()(Env& env) const { @@ -424,58 +440,12 @@ MPTTester::set(MPTSet const& arg) if (arg.mutableFlags) { - if (*arg.mutableFlags & tmfMPTSetCanLock) + for (auto const& [setFlag, ledgerFlag] : mptSetFlagMappings) { - flags |= lsfMPTCanLock; - } - else if (*arg.mutableFlags & tmfMPTClearCanLock) - { - flags &= ~lsfMPTCanLock; - } - - if (*arg.mutableFlags & tmfMPTSetRequireAuth) - { - flags |= lsfMPTRequireAuth; - } - else if (*arg.mutableFlags & tmfMPTClearRequireAuth) - { - flags &= ~lsfMPTRequireAuth; - } - - if (*arg.mutableFlags & tmfMPTSetCanEscrow) - { - flags |= lsfMPTCanEscrow; - } - else if (*arg.mutableFlags & tmfMPTClearCanEscrow) - { - flags &= ~lsfMPTCanEscrow; - } - - if (*arg.mutableFlags & tmfMPTSetCanClawback) - { - flags |= lsfMPTCanClawback; - } - else if (*arg.mutableFlags & tmfMPTClearCanClawback) - { - flags &= ~lsfMPTCanClawback; - } - - if (*arg.mutableFlags & tmfMPTSetCanTrade) - { - flags |= lsfMPTCanTrade; - } - else if (*arg.mutableFlags & tmfMPTClearCanTrade) - { - flags &= ~lsfMPTCanTrade; - } - - if (*arg.mutableFlags & tmfMPTSetCanTransfer) - { - flags |= lsfMPTCanTransfer; - } - else if (*arg.mutableFlags & tmfMPTClearCanTransfer) - { - flags &= ~lsfMPTCanTransfer; + if ((*arg.mutableFlags & setFlag) != 0u) + { + flags |= ledgerFlag; + } } } } From 93eab33dc23e0a026c55234ae7b5190bb195d93e Mon Sep 17 00:00:00 2001 From: Zhiyuan Wang <96991820+Kassaking7@users.noreply.github.com> Date: Mon, 22 Jun 2026 13:45:42 -0400 Subject: [PATCH 131/135] fix: Improve ValidAMM invariant (#7295) --- include/xrpl/tx/invariants/AMMInvariant.h | 11 ++- src/libxrpl/tx/invariants/AMMInvariant.cpp | 92 +++++++++++++++++++--- src/test/app/Invariants_test.cpp | 84 ++++++++++++++++++++ 3 files changed, 175 insertions(+), 12 deletions(-) diff --git a/include/xrpl/tx/invariants/AMMInvariant.h b/include/xrpl/tx/invariants/AMMInvariant.h index ee2fb66a1c..4b56370774 100644 --- a/include/xrpl/tx/invariants/AMMInvariant.h +++ b/include/xrpl/tx/invariants/AMMInvariant.h @@ -15,7 +15,9 @@ class ValidAMM std::optional ammAccount_; std::optional lptAMMBalanceAfter_; std::optional lptAMMBalanceBefore_; + std::optional lptAMMBalanceBeforeDeletion_; bool ammPoolChanged_{false}; + bool ammDeleted_{false}; public: enum class ZeroAllowed : bool { No = false, Yes = true }; @@ -35,12 +37,17 @@ private: [[nodiscard]] bool finalizeCreate(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const; [[nodiscard]] bool - finalizeDelete(bool enforce, TER res, beast::Journal const&) const; + finalizeDelete(bool enforce, bool enforceAMMDelete, TER res, beast::Journal const&) const; [[nodiscard]] bool finalizeDeposit(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const; // Includes clawback [[nodiscard]] bool - finalizeWithdraw(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const; + finalizeWithdraw( + STTx const&, + ReadView const&, + bool enforce, + bool enforceAMMDelete, + beast::Journal const&) const; [[nodiscard]] bool finalizeDEX(bool enforce, beast::Journal const&) const; [[nodiscard]] bool diff --git a/src/libxrpl/tx/invariants/AMMInvariant.cpp b/src/libxrpl/tx/invariants/AMMInvariant.cpp index ecd7bedf89..356c26d6b0 100644 --- a/src/libxrpl/tx/invariants/AMMInvariant.cpp +++ b/src/libxrpl/tx/invariants/AMMInvariant.cpp @@ -27,7 +27,14 @@ void ValidAMM::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { if (isDelete) + { + if (before && before->getType() == ltAMM) + { + ammDeleted_ = true; + lptAMMBalanceBeforeDeletion_ = before->getFieldAmount(sfLPTokenBalance); + } return; + } if (after) { @@ -166,18 +173,60 @@ ValidAMM::finalizeCreate( } bool -ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const +ValidAMM::finalizeDelete(bool enforce, bool enforceAMMDelete, TER res, beast::Journal const& j) + const { if (ammAccount_) { // LCOV_EXCL_START - std::string const msg = (isTesSuccess(res)) ? "AMM object is not deleted on tesSUCCESS" - : "AMM object is changed on tecINCOMPLETE"; + std::string const msg = (isTesSuccess(res)) ? "AMM object remained on tesSUCCESS" + : "AMM object changed on tecINCOMPLETE"; JLOG(j.error()) << "Invariant failed: AMMDelete failed, " << msg; if (enforce) return false; // LCOV_EXCL_STOP } + if (enforceAMMDelete) + { + if (isTesSuccess(res)) + { + if (!ammDeleted_) + { + // LCOV_EXCL_START + JLOG(j.error()) + << "Invariant failed: AMMDelete failed, AMM object remained on tesSUCCESS"; + return false; + // LCOV_EXCL_STOP + } + if (!lptAMMBalanceBeforeDeletion_) + { + // LCOV_EXCL_START + JLOG(j.error()) + << "Invariant failed: AMMDelete failed, AMM object deleted without LP balance"; + return false; + // LCOV_EXCL_STOP + } + if (*lptAMMBalanceBeforeDeletion_ != beast::kZero) + { + // LCOV_EXCL_START + JLOG(j.error()) + << "Invariant failed: AMMDelete failed, AMM object deleted with non-zero LP " + "balance: " + << *lptAMMBalanceBeforeDeletion_; + return false; + // LCOV_EXCL_STOP + } + } + else if (ammDeleted_) + { + // AMM should only be fully deleted when AMMDelete returns tesSUCCESS. + // LCOV_EXCL_START + JLOG(j.error()) << "Invariant failed: AMMDelete failed, AMM object deleted when result " + "is not tesSUCCESS"; + return false; + // LCOV_EXCL_STOP + } + } return true; } @@ -271,16 +320,20 @@ ValidAMM::finalizeWithdraw( xrpl::STTx const& tx, xrpl::ReadView const& view, bool enforce, + bool enforceAMMDelete, beast::Journal const& j) const { - if (!ammAccount_) + if (enforceAMMDelete && ammDeleted_) { - // Last Withdraw or Clawback deleted AMM + // Last Withdraw or Clawback can delete the AMM. We don't have to check + // the LPToken balance because a final AMMWithdraw or AMMClawback can + // redeem the remaining LP tokens and delete the AMM entry in the same + // transaction. + return true; } - else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j)) + if (ammAccount_ && !generalInvariant(tx, view, ZeroAllowed::Yes, j) && enforce) { - if (enforce) - return false; + return false; } return true; @@ -300,6 +353,25 @@ ValidAMM::finalize( return true; bool const enforce = view.rules().enabled(fixAMMv1_3); + bool const enforceAMMDelete = view.rules().enabled(fixCleanup3_3_0); + + // AMM can only be deleted by AMMWithdraw, AMMClawback, and AMMDelete + if (enforceAMMDelete && ammDeleted_) + { + switch (tx.getTxnType()) + { + case ttAMM_WITHDRAW: + case ttAMM_CLAWBACK: + case ttAMM_DELETE: + break; + default: + // LCOV_EXCL_START + JLOG(j.error()) << "Invariant failed: AMM failed, unexpected AMM deletion by " + << tx.getTxnType(); + return false; + // LCOV_EXCL_STOP + } + } switch (tx.getTxnType()) { @@ -309,13 +381,13 @@ ValidAMM::finalize( return finalizeDeposit(tx, view, enforce, j); case ttAMM_CLAWBACK: case ttAMM_WITHDRAW: - return finalizeWithdraw(tx, view, enforce, j); + return finalizeWithdraw(tx, view, enforce, enforceAMMDelete, j); case ttAMM_BID: return finalizeBid(enforce, j); case ttAMM_VOTE: return finalizeVote(enforce, j); case ttAMM_DELETE: - return finalizeDelete(enforce, result, j); + return finalizeDelete(enforce, enforceAMMDelete, result, j); case ttCHECK_CASH: case ttOFFER_CREATE: case ttPAYMENT: diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 6d53d25661..e0c29ea72a 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include @@ -1277,6 +1278,87 @@ class Invariants_test : public beast::unit_test::Suite }); } + void + testAMMDeleteInvariants(FeatureBitset features) + { + using namespace test::jtx; + + bool const enforceAMMDelete = features[fixCleanup3_3_0]; + testcase << "AMM delete invariants" + std::string(enforceAMMDelete ? " fix" : ""); + + Env env(*this, features); + Account const issuer{"issuer"}; + Issue const lptIssue{Currency(0x4c50540000000000), issuer.id()}; + STAmount const zeroLP{lptIssue, 0}; + STAmount const nonZeroLP{lptIssue, 1}; + + auto const makeAMM = [](STAmount const& lptBalance) { + auto sleAMM = std::make_shared(keylet::amm(uint256(1))); + sleAMM->setFieldAmount(sfLPTokenBalance, lptBalance); + return sleAMM; + }; + + auto const checkInvariant = [&](TxType txType, + TER result, + std::optional const& deletedLPBalance, + bool expected, + std::string const& expectedLog) { + test::StreamSink sink{beast::Severity::Warning}; + beast::Journal const jlog{sink}; + ValidAMM invariant; + + if (deletedLPBalance) + invariant.visitEntry(true, makeAMM(*deletedLPBalance), nullptr); + + bool const actual = invariant.finalize( + STTx{txType, [](STObject&) {}}, result, XRPAmount{}, *env.current(), jlog); + + BEAST_EXPECTS(actual == expected, "unexpected AMM delete invariant result"); + auto const messages = sink.messages().str(); + auto const expectedLogWhenEnforced = enforceAMMDelete ? expectedLog : ""; + if (!expectedLogWhenEnforced.empty()) + { + BEAST_EXPECTS(messages.contains(expectedLogWhenEnforced), expectedLogWhenEnforced); + } + else + { + BEAST_EXPECTS(messages.empty(), messages); + } + }; + + checkInvariant( + ttPAYMENT, + tesSUCCESS, + nonZeroLP, + !enforceAMMDelete, + "Invariant failed: AMM failed, unexpected AMM deletion by"); + checkInvariant( + ttAMM_DELETE, + tesSUCCESS, + std::nullopt, + !enforceAMMDelete, + "Invariant failed: AMMDelete failed, AMM object remained on tesSUCCESS"); + checkInvariant( + ttAMM_DELETE, + tesSUCCESS, + nonZeroLP, + !enforceAMMDelete, + "Invariant failed: AMMDelete failed, AMM object deleted with non-zero LP balance"); + checkInvariant( + ttAMM_DELETE, + tecINCOMPLETE, + zeroLP, + !enforceAMMDelete, + "Invariant failed: AMMDelete failed, AMM object deleted when result is not tesSUCCESS"); + + checkInvariant(ttAMM_WITHDRAW, tesSUCCESS, nonZeroLP, true, ""); + checkInvariant(ttAMM_CLAWBACK, tesSUCCESS, nonZeroLP, true, ""); + + checkInvariant(ttAMM_DELETE, tesSUCCESS, zeroLP, true, ""); + checkInvariant(ttAMM_WITHDRAW, tesSUCCESS, zeroLP, true, ""); + checkInvariant(ttAMM_CLAWBACK, tesSUCCESS, zeroLP, true, ""); + } + static SLE::pointer createPermissionedDomain( ApplyContext& ac, @@ -4900,6 +4982,8 @@ public: testNoZeroEscrow(); testValidNewAccountRoot(); testNFTokenPageInvariants(); + testAMMDeleteInvariants(defaultAmendments()); + testAMMDeleteInvariants(defaultAmendments() - fixCleanup3_3_0); testPermissionedDomainInvariants(defaultAmendments() | fixCleanup3_1_3); testPermissionedDomainInvariants(defaultAmendments() - fixCleanup3_1_3); testPermissionedDEX(defaultAmendments() | fixCleanup3_1_3); From 19a9ed776761ee738da4ee2ffde88aa349a9ae31 Mon Sep 17 00:00:00 2001 From: Zhiyuan Wang <96991820+Kassaking7@users.noreply.github.com> Date: Mon, 22 Jun 2026 14:42:57 -0400 Subject: [PATCH 132/135] fix: Move AMMInvariant weakInvariantCheck logic into the transaction (#7032) --- include/xrpl/ledger/helpers/AMMHelpers.h | 26 +++++++++++++ src/libxrpl/ledger/helpers/AMMHelpers.cpp | 37 +++++++++++++++++++ src/libxrpl/tx/invariants/AMMInvariant.cpp | 9 +---- .../tx/transactors/dex/AMMClawback.cpp | 10 +++++ src/libxrpl/tx/transactors/dex/AMMDeposit.cpp | 13 +++++++ .../tx/transactors/dex/AMMWithdraw.cpp | 10 +++++ src/test/app/AMMClawback_test.cpp | 18 ++++++++- src/test/app/AMM_test.cpp | 16 ++++++-- 8 files changed, 127 insertions(+), 12 deletions(-) diff --git a/include/xrpl/ledger/helpers/AMMHelpers.h b/include/xrpl/ledger/helpers/AMMHelpers.h index d21e50e7cb..de8bb9d3f7 100644 --- a/include/xrpl/ledger/helpers/AMMHelpers.h +++ b/include/xrpl/ledger/helpers/AMMHelpers.h @@ -37,6 +37,8 @@ reduceOffer(auto const& amount) enum class IsDeposit : bool { No = false, Yes = true }; +inline Number const kAMMInvariantRelativeTolerance{1, -11}; + /** Calculate LP Tokens given AMM pool reserves. * @param asset1 AMM one side of the pool reserve * @param asset2 AMM another side of the pool reserve @@ -738,6 +740,30 @@ ammPoolHolds( AuthHandling authHandling, beast::Journal const j); +/** Check AMM pool product invariant after an AMM operation that changes LP tokens + * (deposit/withdraw/clawback) from an already calculated pool product mean. + * Returns tecPRECISION_LOSS if poolProductMean < newLPTokenBalance beyond the + * invariant tolerance, + * tesSUCCESS otherwise. Skips check when newLPTokenBalance is zero (last withdrawal). + */ +TER +checkAMMPrecisionLoss(Number const& poolProductMean, STAmount const& newLPTokenBalance); + +/** Check AMM pool product invariant after an AMM operation that changes LP tokens + * (deposit/withdraw/clawback). + * Returns tecPRECISION_LOSS if sqrt(asset1 * asset2) < newLPTokenBalance beyond + * the invariant tolerance, + * tesSUCCESS otherwise. Skips check when newLPTokenBalance is zero (last withdrawal). + */ +TER +checkAMMPrecisionLoss( + ReadView const& view, + AccountID const& ammAccountID, + Asset const& asset1, + Asset const& asset2, + STAmount const& newLPTokenBalance, + beast::Journal const j); + /** Get AMM pool and LP token balances. If both optIssue are * provided then they are used as the AMM token pair issues. * Otherwise the missing issues are fetched from ammSle. diff --git a/src/libxrpl/ledger/helpers/AMMHelpers.cpp b/src/libxrpl/ledger/helpers/AMMHelpers.cpp index a59b8e4436..cacbfc9d58 100644 --- a/src/libxrpl/ledger/helpers/AMMHelpers.cpp +++ b/src/libxrpl/ledger/helpers/AMMHelpers.cpp @@ -433,6 +433,43 @@ ammPoolHolds( return std::make_pair(assetInBalance, assetOutBalance); } +TER +checkAMMPrecisionLoss(Number const& poolProductMean, STAmount const& newLPTokenBalance) +{ + if (newLPTokenBalance <= beast::kZero) + return tesSUCCESS; + if (poolProductMean >= newLPTokenBalance) + return tesSUCCESS; + // Strong check failed. Allow the same relative tolerance as the invariant + // checker's weak check. Only return tecPRECISION_LOSS when both fail. + if (withinRelativeDistance( + poolProductMean, Number{newLPTokenBalance}, kAMMInvariantRelativeTolerance)) + return tesSUCCESS; + return tecPRECISION_LOSS; +} + +TER +checkAMMPrecisionLoss( + ReadView const& view, + AccountID const& ammAccountID, + Asset const& asset1, + Asset const& asset2, + STAmount const& newLPTokenBalance, + beast::Journal const j) +{ + if (newLPTokenBalance <= beast::kZero) + return tesSUCCESS; + auto const [amount, amount2] = ammPoolHolds( + view, + ammAccountID, + asset1, + asset2, + FreezeHandling::IgnoreFreeze, + AuthHandling::IgnoreAuth, + j); + return checkAMMPrecisionLoss(root2(amount * amount2), newLPTokenBalance); +} + std::expected, TER> ammHolds( ReadView const& view, diff --git a/src/libxrpl/tx/invariants/AMMInvariant.cpp b/src/libxrpl/tx/invariants/AMMInvariant.cpp index 356c26d6b0..cca0ce149c 100644 --- a/src/libxrpl/tx/invariants/AMMInvariant.cpp +++ b/src/libxrpl/tx/invariants/AMMInvariant.cpp @@ -270,13 +270,8 @@ ValidAMM::generalInvariant( auto const poolProductMean = root2(amount * amount2); bool const nonNegativeBalances = validBalances(amount, amount2, *lptAMMBalanceAfter_, zeroAllowed); - bool const strongInvariantCheck = poolProductMean >= *lptAMMBalanceAfter_; - // Allow for a small relative error if strongInvariantCheck fails - auto weakInvariantCheck = [&]() { - return *lptAMMBalanceAfter_ != beast::kZero && - withinRelativeDistance(poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11}); - }; - if (!nonNegativeBalances || (!strongInvariantCheck && !weakInvariantCheck())) + auto const precisionLoss = checkAMMPrecisionLoss(poolProductMean, *lptAMMBalanceAfter_); + if (!nonNegativeBalances || !isTesSuccess(precisionLoss)) { JLOG(j.error()) << "Invariant failed: AMM " << tx.getTxnType() << " " << tx.getHash(HashPrefix::TransactionId) << " " << ammPoolChanged_ << " " diff --git a/src/libxrpl/tx/transactors/dex/AMMClawback.cpp b/src/libxrpl/tx/transactors/dex/AMMClawback.cpp index b94e97e931..0cc2be381f 100644 --- a/src/libxrpl/tx/transactors/dex/AMMClawback.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMClawback.cpp @@ -258,6 +258,16 @@ AMMClawback::applyGuts(Sandbox& sb) if (!isTesSuccess(result)) return result; // LCOV_EXCL_LINE + if (sb.rules().enabled(fixCleanup3_3_0) && sb.rules().enabled(fixAMMv1_3)) + { + if (auto const ter = + checkAMMPrecisionLoss(sb, ammAccount, asset, asset2, newLPTokenBalance, j_); + !isTesSuccess(ter)) + { + return ter; + } + } + auto const res = AMMWithdraw::deleteAMMAccountIfEmpty(sb, ammSle, newLPTokenBalance, asset, asset2, j_); if (!res.second) diff --git a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp index 91858e3cd7..653e8c6961 100644 --- a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp @@ -470,6 +470,19 @@ AMMDeposit::applyGuts(Sandbox& sb) XRPL_ASSERT( newLPTokenBalance > beast::kZero, "xrpl::AMMDeposit::applyGuts : valid new LP token balance"); + // Defensive check: deposit formulas with fixAMMv1_3 round LP tokens + // down and asset amounts up, so sqrt(pool1*pool2) >= newLPTokenBalance + // is guaranteed to hold. A precision loss failure is not expected. + if (sb.rules().enabled(fixCleanup3_3_0) && sb.rules().enabled(fixAMMv1_3)) + { + if (auto const ter = checkAMMPrecisionLoss( + sb, ammAccountID, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], newLPTokenBalance, j_); + !isTesSuccess(ter)) + { + UNREACHABLE("xrpl::AMMDeposit::applyGuts : AMM precision loss"); + return {ter, false}; // LCOV_EXCL_LINE + } + } ammSle->setFieldAmount(sfLPTokenBalance, newLPTokenBalance); // LP depositing into AMM empty state gets the auction slot // and the voting diff --git a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp index d3a6c9c74c..17ce1a6b83 100644 --- a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp @@ -406,6 +406,16 @@ AMMWithdraw::applyGuts(Sandbox& sb) if (!isTesSuccess(result)) return {result, false}; + if (sb.rules().enabled(fixCleanup3_3_0) && sb.rules().enabled(fixAMMv1_3)) + { + if (auto const ter = checkAMMPrecisionLoss( + sb, ammAccountID, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], newLPTokenBalance, j_); + !isTesSuccess(ter)) + { + return {ter, false}; + } + } + auto const res = deleteAMMAccountIfEmpty( sb, ammSle, newLPTokenBalance, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_); // LCOV_EXCL_START diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp index 9683e8ac17..ba416d8192 100644 --- a/src/test/app/AMMClawback_test.cpp +++ b/src/test/app/AMMClawback_test.cpp @@ -2486,8 +2486,17 @@ class AMMClawback_test : public beast::unit_test::Suite else if (!features[fixAMMClawbackRounding]) { // sqrt(amount * amount2) >= LPTokens and exceeds the allowed - // tolerance - env(amm::ammClawback(gw, alice, usd, eur, usd(1)), Ter(tecINVARIANT_FAILED)); + // tolerance. + // With fixCleanup3_3_0 this is caught in the transaction layer; + // without it the invariant checker fires instead. + if (features[fixCleanup3_3_0]) + { + env(amm::ammClawback(gw, alice, usd, eur, usd(1)), Ter(tecPRECISION_LOSS)); + } + else + { + env(amm::ammClawback(gw, alice, usd, eur, usd(1)), Ter(tecINVARIANT_FAILED)); + } BEAST_EXPECT(amm.ammExists()); } else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) @@ -2514,6 +2523,11 @@ class AMMClawback_test : public beast::unit_test::Suite testFeatureDisabled(all - featureAMMClawback); for (auto const& features : {all - fixAMMv1_3 - fixAMMClawbackRounding - featureMPTokensV2, + // fixAMMv1_3 on, fixAMMClawbackRounding off, fixCleanup3_3_0 off: + // precision loss caught by invariant checker -> tecINVARIANT_FAILED + all - fixAMMClawbackRounding - fixCleanup3_3_0 - featureMPTokensV2, + // fixAMMv1_3 on, fixAMMClawbackRounding off, fixCleanup3_3_0 on: + // precision loss caught in transaction layer -> tecPRECISION_LOSS all - fixAMMClawbackRounding - featureMPTokensV2, all - featureMPTokensV2, all}) diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index 1b54c2aab9..b01d58ddff 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -1842,8 +1842,18 @@ private: // are rounded to all LP tokens. testAMM( [&](AMM& ammAlice, Env& env) { - auto const err = - env.enabled(fixAMMv1_3) ? Ter(tecINVARIANT_FAILED) : Ter(tecAMM_BALANCE); + // Without fixAMMv1_3: sub-method returns tecAMM_BALANCE early. + // With fixAMMv1_3 but without fixCleanup3_3_0: sub-method succeeds + // but invariant check catches the precision violation. + // With fixCleanup3_3_0: caught in the transaction layer before + // the invariant checker runs. + auto const err = [&] { + if (!env.enabled(fixAMMv1_3)) + return Ter(tecAMM_BALANCE); + if (env.enabled(fixCleanup3_3_0)) + return Ter(tecPRECISION_LOSS); + return Ter(tecINVARIANT_FAILED); + }(); ammAlice.withdraw( alice_, STAmount{USD, UINT64_C(9'999'999999999999), -12}, @@ -1851,7 +1861,7 @@ private: std::nullopt, err); }, - {.features = {all, all - fixAMMv1_3}, .noLog = true}); + {.features = {all, all - fixAMMv1_3, all - fixCleanup3_3_0}, .noLog = true}); // Tiny withdraw testAMM([&](AMM& ammAlice, Env&) { From dd7401fde21c6ddd5d5e1ef4761a3de6630b40fa Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Mon, 22 Jun 2026 14:44:42 -0400 Subject: [PATCH 133/135] refactor: Clean up tec object deletion logic (#6588) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- include/xrpl/tx/Transactor.h | 7 + src/libxrpl/tx/Transactor.cpp | 238 ++++++++++++++-------------- src/test/app/NFToken_test.cpp | 2 + src/test/app/Offer_test.cpp | 25 +++ src/test/app/SetRegularKey_test.cpp | 22 +++ 5 files changed, 172 insertions(+), 122 deletions(-) diff --git a/include/xrpl/tx/Transactor.h b/include/xrpl/tx/Transactor.h index a27d638107..470571eb48 100644 --- a/include/xrpl/tx/Transactor.h +++ b/include/xrpl/tx/Transactor.h @@ -7,6 +7,7 @@ #include #include +#include #include namespace xrpl { @@ -419,8 +420,13 @@ private: TER consumeSeqProxy(SLE::pointer const& sleAccount); + TER payFee(); + + std::tuple + processPersistentChanges(TER result, XRPAmount fee); + static NotTEC checkSingleSign( ReadView const& view, @@ -428,6 +434,7 @@ private: AccountID const& idAccount, SLE::const_pointer sleAccount, beast::Journal const j); + static NotTEC checkMultiSign( ReadView const& view, diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index b57d30d2b3..2ff24d92b5 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -45,8 +45,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -1078,26 +1080,6 @@ removeDeletedTrustLines( } } -static void -removeDeletedMPTs(ApplyView& view, std::vector const& mpts, beast::Journal viewJ) -{ - // There could be at most two MPTs - one for each side of AMM pool - if (mpts.size() > 2) - { - JLOG(viewJ.error()) << "removeDeletedMPTs: deleted mpts exceed 2 " << mpts.size(); - return; - } - - for (auto const& index : mpts) - { - if (auto const sleState = view.peek({ltMPTOKEN, index}); sleState && - deleteAMMMPToken(view, sleState, (*sleState)[sfIssuer], viewJ) != tesSUCCESS) - { - JLOG(viewJ.error()) << "removeDeletedMPTs: failed to delete AMM MPT"; - } - } -} - /** Reset the context, discarding any changes made and adjust the fee. @param fee The transaction fee to be charged. @@ -1160,6 +1142,118 @@ Transactor::trapTransaction(uint256 txHash) const JLOG(j_.debug()) << "Transaction trapped: " << txHash; } +std::tuple +Transactor::processPersistentChanges(TER result, XRPAmount fee) +{ + JLOG(j_.trace()) << "reapplying because of " << transToken(result); + + // FIXME: This mechanism for doing work while returning a `tec` is + // awkward and very limiting. A more general purpose approach + // should be used, making it possible to do more useful work + // when transactions fail with a `tec` code. + + auto typesForResult = [](TER const ter) { + std::unordered_set types; + if ((ter == tecOVERSIZE) || (ter == tecKILLED)) + { + types.insert(ltOFFER); + } + else if (ter == tecINCOMPLETE) + { + types.insert(ltRIPPLE_STATE); + } + else if (ter == tecEXPIRED) + { + types.insert(ltNFTOKEN_OFFER); + types.insert(ltCREDENTIAL); + } + return types; + }; + + // Build a list of ledger entry types to collect, based on the + // result code. Only deleted objects of these types will be + // re-applied after the context is reset. + auto const typesToCollect = typesForResult(result); + + std::map> deletedObjects; + if (!typesToCollect.empty()) + { + ctx_.visit( + [&typesToCollect, &deletedObjects]( + uint256 const& index, bool isDelete, SLE::const_ref before, SLE::const_ref after) { + if (isDelete) + { + XRPL_ASSERT( + before && after, + "xrpl::Transactor::processPersistentChanges : non-null " + "SLE inputs"); + if (before && after) + { + auto const type = before->getType(); + if (typesToCollect.contains(type)) + { + // For offers, only collect unfunded removals + // (where TakerPays is unchanged) + if (type == ltOFFER && + before->getFieldAmount(sfTakerPays) != + after->getFieldAmount(sfTakerPays)) + return; + + deletedObjects[type].push_back(index); + } + } + } + }); + } + + // Reset the context, potentially adjusting the fee. + { + auto const resetResult = reset(fee); + if (!isTesSuccess(resetResult.first)) + result = resetResult.first; + + fee = resetResult.second; + } + + // Re-apply the collected deletions, but only if the reset succeeded + // and the post-reset result still allows the same deletion type. + auto const typesToApply = typesForResult(result); + if (isTecClaim(result) && !typesToApply.empty()) + { + auto const viewJ = ctx_.registry.get().getJournal("View"); + for (auto const& [type, ids] : deletedObjects) + { + if (ids.empty() || !typesToApply.contains(type)) + continue; + + switch (type) + { + case ltOFFER: + removeUnfundedOffers(view(), ids, viewJ); + break; + case ltNFTOKEN_OFFER: + removeExpiredNFTokenOffers(view(), ids, viewJ); + break; + case ltRIPPLE_STATE: + removeDeletedTrustLines(view(), ids, viewJ); + break; + case ltCREDENTIAL: + removeExpiredCredentials(view(), ids, viewJ); + break; + // LCOV_EXCL_START + default: + UNREACHABLE( + "xrpl::Transactor::processPersistentChanges() : " + "unexpected type"); + break; + // LCOV_EXCL_STOP + } + } + } + + return {result, fee, isTecClaim(result)}; +} + [[nodiscard]] TER Transactor::checkTransactionInvariants(TER result, XRPAmount fee) { @@ -1209,6 +1303,7 @@ Transactor::checkInvariants(TER result, XRPAmount fee) */ return ctx_.checkInvariants(result, fee); } + //------------------------------------------------------------------------------ ApplyResult Transactor::operator()() @@ -1275,108 +1370,7 @@ Transactor::operator()() (result == tecOVERSIZE) || (result == tecKILLED) || (result == tecINCOMPLETE) || (result == tecEXPIRED) || (isTecClaimHardFail(result, view().flags()))) { - JLOG(j_.trace()) << "reapplying because of " << transToken(result); - - // FIXME: This mechanism for doing work while returning a `tec` is - // awkward and very limiting. A more general purpose approach - // should be used, making it possible to do more useful work - // when transactions fail with a `tec` code. - std::vector removedOffers; - std::vector removedTrustLines; - std::vector removedMPTs; - std::vector expiredNFTokenOffers; - std::vector expiredCredentials; - - bool const doOffers = ((result == tecOVERSIZE) || (result == tecKILLED)); - bool const doLinesOrMPTs = (result == tecINCOMPLETE); - bool const doNFTokenOffers = (result == tecEXPIRED); - bool const doCredentials = (result == tecEXPIRED); - if (doOffers || doLinesOrMPTs || doNFTokenOffers || doCredentials) - { - ctx_.visit([doOffers, - &removedOffers, - doLinesOrMPTs, - &removedTrustLines, - &removedMPTs, - doNFTokenOffers, - &expiredNFTokenOffers, - doCredentials, - &expiredCredentials]( - uint256 const& index, - bool isDelete, - SLE::const_ref before, - SLE::const_ref after) { - if (isDelete) - { - XRPL_ASSERT( - before && after, - "xrpl::Transactor::operator()::visit : non-null SLE " - "inputs"); - if (doOffers && before && after && (before->getType() == ltOFFER) && - (before->getFieldAmount(sfTakerPays) == after->getFieldAmount(sfTakerPays))) - { - // Removal of offer found or made unfunded - removedOffers.push_back(index); - } - - if (doLinesOrMPTs && before && after) - { - // Removal of obsolete AMM trust line - if (before->getType() == ltRIPPLE_STATE) - { - removedTrustLines.push_back(index); - } - else if (before->getType() == ltMPTOKEN) - { - removedMPTs.push_back(index); - } - } - - if (doNFTokenOffers && before && after && - (before->getType() == ltNFTOKEN_OFFER)) - expiredNFTokenOffers.push_back(index); - - if (doCredentials && before && after && (before->getType() == ltCREDENTIAL)) - expiredCredentials.push_back(index); - } - }); - } - - // Reset the context, potentially adjusting the fee. - { - auto const resetResult = reset(fee); - if (!isTesSuccess(resetResult.first)) - result = resetResult.first; - - fee = resetResult.second; - } - - // If necessary, remove any offers found unfunded during processing - if ((result == tecOVERSIZE) || (result == tecKILLED)) - { - removeUnfundedOffers(view(), removedOffers, ctx_.registry.get().getJournal("View")); - } - - if (result == tecEXPIRED) - { - removeExpiredNFTokenOffers( - view(), expiredNFTokenOffers, ctx_.registry.get().getJournal("View")); - } - - if (result == tecINCOMPLETE) - { - removeDeletedTrustLines( - view(), removedTrustLines, ctx_.registry.get().getJournal("View")); - removeDeletedMPTs(view(), removedMPTs, ctx_.registry.get().getJournal("View")); - } - - if (result == tecEXPIRED) - { - removeExpiredCredentials( - view(), expiredCredentials, ctx_.registry.get().getJournal("View")); - } - - applied = isTecClaim(result); + std::tie(result, fee, applied) = processPersistentChanges(result, fee); } if (applied) diff --git a/src/test/app/NFToken_test.cpp b/src/test/app/NFToken_test.cpp index ba8f09c449..cb92b23a4c 100644 --- a/src/test/app/NFToken_test.cpp +++ b/src/test/app/NFToken_test.cpp @@ -1120,6 +1120,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite if (features[fixCleanup3_1_3]) { buyerCount--; + BEAST_EXPECT(!env.closed()->exists(keylet::nftoffer(buyerExpOfferIndex))); } BEAST_EXPECT(ownerCount(env, buyer) == buyerCount); @@ -1143,6 +1144,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite if (features[fixCleanup3_1_3]) { aliceCount--; + BEAST_EXPECT(!env.closed()->exists(keylet::nftoffer(aliceExpOfferIndex))); } BEAST_EXPECT(ownerCount(env, alice) == aliceCount); BEAST_EXPECT(ownerCount(env, buyer) == buyerCount); diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index 7382f4f090..ea8b0a7c0e 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -797,11 +797,13 @@ public: // The offer expires (it's not removed yet). env.close(); env.require(Owners(bob, 1), offers(bob, 1)); + auto const expiredBobOffer = keylet::offer(bob, env.seq(bob) - 1); // bob creates the offer that will be crossed. env(offer(bob, usd(500), XRP(500)), Ter(tesSUCCESS)); env.close(); env.require(Owners(bob, 2), offers(bob, 2)); + auto const crossedBobOffer = keylet::offer(bob, env.seq(bob) - 1); env(trust(alice, usd(1000)), Ter(tesSUCCESS)); env(pay(gw, alice, usd(1000)), Ter(tesSUCCESS)); @@ -820,6 +822,8 @@ public: Balance(bob, usd(kNone)), Owners(bob, 1), offers(bob, 1)); + BEAST_EXPECT(!env.current()->exists(expiredBobOffer)); + BEAST_EXPECT(env.current()->exists(crossedBobOffer)); // Order that can be filled env(offer(alice, XRP(500), usd(500)), Txflags(tfFillOrKill), Ter(tesSUCCESS)); @@ -835,6 +839,27 @@ public: offers(bob, 0)); } + // A failed Fill-or-Kill may tentatively consume a funded offer before + // the transaction is reset. That offer must not be treated as an + // unfunded offer cleanup. + { + Env env{*this, features}; + + env.fund(startBalance, gw, alice, bob); + env.close(); + + env(offer(bob, usd(500), XRP(500)), Ter(tesSUCCESS)); + env.close(); + auto const bobOffer = keylet::offer(bob, env.seq(bob) - 1); + + env(trust(alice, usd(1000)), Ter(tesSUCCESS)); + env(pay(gw, alice, usd(1000)), Ter(tesSUCCESS)); + env(offer(alice, XRP(1000), usd(1000)), Txflags(tfFillOrKill), Ter(tecKILLED)); + + env.require(offers(alice, 0), offers(bob, 1), Balance(alice, usd(1000))); + BEAST_EXPECT(env.current()->exists(bobOffer)); + } + // Immediate or Cancel - cross as much as possible // and add nothing on the books: { diff --git a/src/test/app/SetRegularKey_test.cpp b/src/test/app/SetRegularKey_test.cpp index b5d1af9ef0..b512885606 100644 --- a/src/test/app/SetRegularKey_test.cpp +++ b/src/test/app/SetRegularKey_test.cpp @@ -72,6 +72,27 @@ public: env(regkey(alice, alice), Ter(temBAD_REGKEY)); } + void + testNoAlternativeKey() + { + using namespace test::jtx; + + testcase("Cannot remove last signing method"); + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + env.fund(XRP(10000), alice); + + env(regkey(alice, bob)); + env(fset(alice, asfDisableMaster), Sig(alice)); + + env(regkey(alice, kDisabled), Sig(bob), Ter(tecNO_ALTERNATIVE_KEY)); + + auto const sle = env.le(alice); + BEAST_EXPECT( + sle && sle->isFlag(lsfDisableMaster) && sle->getAccountID(sfRegularKey) == bob.id()); + } + void testPasswordSpent() { @@ -169,6 +190,7 @@ public: { testDisabledMasterKey(); testDisabledRegularKey(); + testNoAlternativeKey(); testPasswordSpent(); testUniversalMask(); testTicketRegularKey(); From df5fa5aadead93ef03ecd763910fb1b4d4bd31ce Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Mon, 22 Jun 2026 16:48:00 -0400 Subject: [PATCH 134/135] Correct a couple of comments --- src/test/app/SHAMapStore_test.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/app/SHAMapStore_test.cpp b/src/test/app/SHAMapStore_test.cpp index 8f6b4919b1..2ecb71796f 100644 --- a/src/test/app/SHAMapStore_test.cpp +++ b/src/test/app/SHAMapStore_test.cpp @@ -698,7 +698,8 @@ public: // Close another ledger, which will trigger a rotation, but the // rotation will be stuck until the missing ledger is filled in. env.close(); - // DO NOT CALL rendezvous()! You'll end up with a deadlock. + // DO NOT CALL rendezvous() without a timeout parameter! You'll end up with a + // deadlock. ++maxSeq; // Nothing has changed @@ -712,7 +713,7 @@ public: expectedRange(minSeq, deleteSeq, maxSeq), lm.getCompleteLedgers())); - // Close 5 more ledgers, waiting one second in between to + // Close 5 more ledgers, waiting a little bit in between to // simulate the ledger making progress while online delete waits // for the missing ledger to be filled in. // This ensures the healthWait check has time to run and From ff02269c0dd1707b38ae783fdf9ba05b07037af7 Mon Sep 17 00:00:00 2001 From: Bart Date: Mon, 22 Jun 2026 18:35:28 -0400 Subject: [PATCH 135/135] refactor: Use dispatch instead of post (#7438) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- src/xrpld/overlay/detail/PeerImp.cpp | 367 ++++++++++++--------------- 1 file changed, 167 insertions(+), 200 deletions(-) diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 21e84d6fc7..e1d7d23215 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -70,7 +70,6 @@ #include #include #include -#include #include #include #include @@ -198,78 +197,70 @@ stringIsUInt256Sized(std::string const& pBuffStr) void PeerImp::run() { - if (!strand_.running_in_this_thread()) - { - post(strand_, std::bind(&PeerImp::run, shared_from_this())); - return; - } + dispatch(strand_, [self = shared_from_this()]() { + auto parseLedgerHash = [](std::string_view value) -> std::optional { + if (uint256 ret; ret.parseHex(value)) + return ret; - auto parseLedgerHash = [](std::string_view value) -> std::optional { - if (uint256 ret; ret.parseHex(value)) - return ret; + if (auto const s = base64Decode(value); s.size() == uint256::size()) + return uint256::fromRaw(s); - if (auto const s = base64Decode(value); s.size() == uint256::size()) - return uint256::fromRaw(s); + return std::nullopt; + }; - return std::nullopt; - }; + std::optional closed; + std::optional previous; - std::optional closed; - std::optional previous; + if (auto const iter = self->headers_.find("Closed-Ledger"); iter != self->headers_.end()) + { + closed = parseLedgerHash(iter->value()); - if (auto const iter = headers_.find("Closed-Ledger"); iter != headers_.end()) - { - closed = parseLedgerHash(iter->value()); + if (!closed) + self->fail("Malformed handshake data (1)"); + } - if (!closed) - fail("Malformed handshake data (1)"); - } + if (auto const iter = self->headers_.find("Previous-Ledger"); iter != self->headers_.end()) + { + previous = parseLedgerHash(iter->value()); - if (auto const iter = headers_.find("Previous-Ledger"); iter != headers_.end()) - { - previous = parseLedgerHash(iter->value()); + if (!previous) + self->fail("Malformed handshake data (2)"); + } - if (!previous) - fail("Malformed handshake data (2)"); - } + if (previous && !closed) + self->fail("Malformed handshake data (3)"); - if (previous && !closed) - fail("Malformed handshake data (3)"); + { + std::scoped_lock const sl(self->recentLock_); + if (closed) + self->closedLedgerHash_ = *closed; + if (previous) + self->previousLedgerHash_ = *previous; + } - { - std::scoped_lock const sl(recentLock_); - if (closed) - closedLedgerHash_ = *closed; - if (previous) - previousLedgerHash_ = *previous; - } + if (self->inbound_) + { + self->doAccept(); + } + else + { + self->doProtocolStart(); + } - if (inbound_) - { - doAccept(); - } - else - { - doProtocolStart(); - } - - // Anything else that needs to be done with the connection should be - // done in doProtocolStart + // Anything else that needs to be done with the connection should be + // done in doProtocolStart + }); } void PeerImp::stop() { - if (!strand_.running_in_this_thread()) - { - post(strand_, std::bind(&PeerImp::stop, shared_from_this())); - return; - } + dispatch(strand_, [self = shared_from_this()]() { + if (!self->socket_.is_open()) + return; - if (!socket_.is_open()) - return; - - close(); + self->close(); + }); } //------------------------------------------------------------------------------ @@ -277,126 +268,111 @@ PeerImp::stop() void PeerImp::send(std::shared_ptr const& m) { - if (!strand_.running_in_this_thread()) - { - post(strand_, std::bind(&PeerImp::send, shared_from_this(), m)); - return; - } - if (gracefulClose_) - return; - if (detaching_) - return; - if (!socket_.is_open()) - return; + dispatch(strand_, [self = shared_from_this(), m]() { + if (self->gracefulClose_) + return; + if (self->detaching_) + return; + if (!self->socket_.is_open()) + return; - auto validator = m->getValidatorKey(); - if (validator && !squelch_.expireSquelch(*validator)) - { - overlay_.reportOutboundTraffic( - TrafficCount::Category::SquelchSuppressed, - static_cast(m->getBuffer(compressionEnabled_).size())); - return; - } + auto validator = m->getValidatorKey(); + if (validator && !self->squelch_.expireSquelch(*validator)) + { + self->overlay_.reportOutboundTraffic( + TrafficCount::Category::SquelchSuppressed, + static_cast(m->getBuffer(self->compressionEnabled_).size())); + return; + } - // report categorized outgoing traffic - overlay_.reportOutboundTraffic( - safeCast(m->getCategory()), - static_cast(m->getBuffer(compressionEnabled_).size())); + // report categorized outgoing traffic + self->overlay_.reportOutboundTraffic( + safeCast(m->getCategory()), + static_cast(m->getBuffer(self->compressionEnabled_).size())); - // report total outgoing traffic - overlay_.reportOutboundTraffic( - TrafficCount::Category::Total, static_cast(m->getBuffer(compressionEnabled_).size())); + // report total outgoing traffic + self->overlay_.reportOutboundTraffic( + TrafficCount::Category::Total, + static_cast(m->getBuffer(self->compressionEnabled_).size())); - auto sendqSize = sendQueue_.size(); + auto sendqSize = self->sendQueue_.size(); - if (sendqSize < Tuning::kTargetSendQueue) - { - // To detect a peer that does not read from their - // side of the connection, we expect a peer to have - // a small senq periodically - largeSendq_ = 0; - } - else if (auto sink = journal_.debug(); sink && (sendqSize % Tuning::kSendQueueLogFreq) == 0) - { - std::string const n = name(); - sink << n << " sendq: " << sendqSize; - } + if (sendqSize < Tuning::kTargetSendQueue) + { + // To detect a peer that does not read from their + // side of the connection, we expect a peer to have + // a small sendq periodically + self->largeSendq_ = 0; + } + else if ( + auto sink = self->journal_.debug(); + sink && (sendqSize % Tuning::kSendQueueLogFreq) == 0) + { + std::string const n = self->name(); + sink << n << " sendq: " << sendqSize; + } - sendQueue_.push(m); + self->sendQueue_.push(m); - if (sendqSize != 0) - return; + if (sendqSize != 0) + return; - boost::asio::async_write( - stream_, - boost::asio::buffer(sendQueue_.front()->getBuffer(compressionEnabled_)), - bind_executor( - strand_, - std::bind( - &PeerImp::onWriteMessage, - shared_from_this(), - std::placeholders::_1, - std::placeholders::_2))); + boost::asio::async_write( + self->stream_, + boost::asio::buffer(self->sendQueue_.front()->getBuffer(self->compressionEnabled_)), + bind_executor( + self->strand_, + std::bind( + &PeerImp::onWriteMessage, self, std::placeholders::_1, std::placeholders::_2))); + }); } void PeerImp::sendTxQueue() { - if (!strand_.running_in_this_thread()) - { - post(strand_, std::bind(&PeerImp::sendTxQueue, shared_from_this())); - return; - } - - if (!txQueue_.empty()) - { - protocol::TMHaveTransactions ht; - std::ranges::for_each( - txQueue_, [&](auto const& hash) { ht.add_hashes(hash.data(), hash.size()); }); - JLOG(pJournal_.trace()) << "sendTxQueue " << txQueue_.size(); - txQueue_.clear(); - send(std::make_shared(ht, protocol::mtHAVE_TRANSACTIONS)); - } + dispatch(strand_, [self = shared_from_this()]() { + if (!self->txQueue_.empty()) + { + protocol::TMHaveTransactions ht; + std::ranges::for_each( + self->txQueue_, [&](auto const& hash) { ht.add_hashes(hash.data(), hash.size()); }); + JLOG(self->pJournal_.trace()) << "sendTxQueue " << self->txQueue_.size(); + self->txQueue_.clear(); + self->send(std::make_shared(ht, protocol::mtHAVE_TRANSACTIONS)); + } + }); } void PeerImp::addTxQueue(uint256 const& hash) { - if (!strand_.running_in_this_thread()) - { - post(strand_, std::bind(&PeerImp::addTxQueue, shared_from_this(), hash)); - return; - } + dispatch(strand_, [self = shared_from_this(), hash]() { + if (self->txQueue_.size() == reduce_relay::kMaxTxQueueSize) + { + JLOG(self->pJournal_.warn()) << "addTxQueue exceeds the cap"; + self->sendTxQueue(); + } - if (txQueue_.size() == reduce_relay::kMaxTxQueueSize) - { - JLOG(pJournal_.warn()) << "addTxQueue exceeds the cap"; - sendTxQueue(); - } - - txQueue_.insert(hash); - JLOG(pJournal_.trace()) << "addTxQueue " << txQueue_.size(); + self->txQueue_.insert(hash); + JLOG(self->pJournal_.trace()) << "addTxQueue " << self->txQueue_.size(); + }); } void PeerImp::removeTxQueue(uint256 const& hash) { - if (!strand_.running_in_this_thread()) - { - post(strand_, std::bind(&PeerImp::removeTxQueue, shared_from_this(), hash)); - return; - } - - auto removed = txQueue_.erase(hash); - JLOG(pJournal_.trace()) << "removeTxQueue " << removed; + dispatch(strand_, [self = shared_from_this(), hash]() { + auto removed = self->txQueue_.erase(hash); + JLOG(self->pJournal_.trace()) << "removeTxQueue " << removed; + }); } void PeerImp::charge(Resource::Charge const& fee, std::string const& context) { - dispatch(strand_, [this, self = shared_from_this(), fee, context]() { - if ((usage_.charge(fee, context) == Resource::Disposition::Drop) && - usage_.disconnect(pJournal_)) + dispatch(strand_, [self = shared_from_this(), fee, context]() { + if ((self->usage_.charge(fee, context) == Resource::Disposition::Drop) && + self->usage_.disconnect(self->pJournal_)) { // Idempotent: only the first worker to observe Drop counts the // metric and posts fail(). Without the guard, several queued @@ -405,11 +381,11 @@ PeerImp::charge(Resource::Charge const& fee, std::string const& context) // shutdowns. fail(std::string const&) self-posts to strand_ // when invoked off-strand. bool expected = false; - if (chargeDisconnectFired_.compare_exchange_strong( + if (self->chargeDisconnectFired_.compare_exchange_strong( expected, true, std::memory_order_acq_rel)) { - overlay_.incPeerDisconnectCharges(); - fail("charge: Resources"); + self->overlay_.incPeerDisconnectCharges(); + self->fail("charge: Resources"); } } }); @@ -640,20 +616,14 @@ PeerImp::close() void PeerImp::fail(std::string const& reason) { - if (!strand_.running_in_this_thread()) - { - post( - strand_, - std::bind( - (void (Peer::*)(std::string const&))&PeerImp::fail, shared_from_this(), reason)); - return; - } - if (journal_.active(beast::Severity::Warning) && socket_.is_open()) - { - std::string const n = name(); - JLOG(journal_.warn()) << n << " failed: " << reason; - } - close(); + dispatch(strand_, [self = shared_from_this(), reason]() { + if (self->journal_.active(beast::Severity::Warning) && self->socket_.is_open()) + { + std::string const n = self->name(); + JLOG(self->journal_.warn()) << n << " failed: " << reason; + } + self->close(); + }); } void @@ -2752,45 +2722,42 @@ PeerImp::onMessage(std::shared_ptr const& m) void PeerImp::onMessage(std::shared_ptr const& m) { - using on_message_fn = void (PeerImp::*)(std::shared_ptr const&); - if (!strand_.running_in_this_thread()) - { - post(strand_, std::bind((on_message_fn)&PeerImp::onMessage, shared_from_this(), m)); - return; - } + dispatch(strand_, [self = shared_from_this(), m]() { + if (!m->has_validatorpubkey()) + { + self->fee_.update(Resource::kFeeInvalidData, "squelch no pubkey"); + return; + } + auto validator = m->validatorpubkey(); + auto const slice{makeSlice(validator)}; + if (!publicKeyType(slice)) + { + self->fee_.update(Resource::kFeeInvalidData, "squelch bad pubkey"); + return; + } + PublicKey const key(slice); - if (!m->has_validatorpubkey()) - { - fee_.update(Resource::kFeeInvalidData, "squelch no pubkey"); - return; - } - auto validator = m->validatorpubkey(); - auto const slice{makeSlice(validator)}; - if (!publicKeyType(slice)) - { - fee_.update(Resource::kFeeInvalidData, "squelch bad pubkey"); - return; - } - PublicKey const key(slice); + // Ignore the squelch for validator's own messages. + if (key == self->app_.getValidationPublicKey()) + { + JLOG(self->pJournal_.debug()) + << "onMessage: TMSquelch discarding validator's squelch " << slice; + return; + } - // Ignore the squelch for validator's own messages. - if (key == app_.getValidationPublicKey()) - { - JLOG(pJournal_.debug()) << "onMessage: TMSquelch discarding validator's squelch " << slice; - return; - } + std::uint32_t const duration = m->has_squelchduration() ? m->squelchduration() : 0; + if (!m->squelch()) + { + self->squelch_.removeSquelch(key); + } + else if (!self->squelch_.addSquelch(key, std::chrono::seconds{duration})) + { + self->fee_.update(Resource::kFeeInvalidData, "squelch duration"); + } - std::uint32_t const duration = m->has_squelchduration() ? m->squelchduration() : 0; - if (!m->squelch()) - { - squelch_.removeSquelch(key); - } - else if (!squelch_.addSquelch(key, std::chrono::seconds{duration})) - { - fee_.update(Resource::kFeeInvalidData, "squelch duration"); - } - - JLOG(pJournal_.debug()) << "onMessage: TMSquelch " << slice << " " << id() << " " << duration; + JLOG(self->pJournal_.debug()) + << "onMessage: TMSquelch " << slice << " " << self->id() << " " << duration; + }); } //--------------------------------------------------------------------------