#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { #if GENERATING_DOCS /** * @brief Prototype for invariant check implementations. * * __THIS CLASS DOES NOT EXIST__ - or rather it exists in documentation only to * communicate the interface required of any invariant checker. Any invariant * check implementation should implement the public methods documented here. * * ## Rules for implementing `finalize` * * ### Invariants must run regardless of transaction result * * An invariant's `finalize` method MUST perform meaningful checks even when * the transaction has failed (i.e., `!isTesSuccess(tec)`). The following * pattern is almost certainly wrong and must never be used: * * @code * // WRONG: skipping all checks on failure defeats the purpose of invariants * if (!isTesSuccess(tec)) * return true; * @endcode * * The entire purpose of invariants is to detect and prevent the impossible. * A bug or exploit could cause a failed transaction to mutate ledger state in * unexpected ways. Invariants are the last line of defense against such * scenarios. * * In general: an invariant that expects a domain-specific state change to * occur (e.g., a new object being created) should only expect that change * when the transaction succeeded. A failed VaultCreate must not have created * a Vault. A failed LoanSet must not have created a Loan. * * Also be aware that failed transactions, regardless of type, carry no * Privileges. Any privilege-gated checks must therefore also be applied to * failed transactions. */ class InvariantChecker_PROTOTYPE { public: explicit InvariantChecker_PROTOTYPE() = default; /** * @brief called for each ledger entry in the current transaction. * * @param isDelete true if the SLE is being deleted * @param before ledger entry before modification by the transaction * @param after ledger entry after modification by the transaction */ void visitEntry( bool isDelete, std::shared_ptr const& before, std::shared_ptr const& after); /** * @brief called after all ledger entries have been visited to determine * the final status of the check. * * This method MUST perform meaningful checks even when `tec` indicates a * failed transaction. See the class-level documentation for the rules * governing how failed transactions must be handled. * * @param tx the transaction being applied * @param tec the current TER result of the transaction * @param fee the fee actually charged for this transaction * @param view a ReadView of the ledger being modified * @param j journal for logging * * @return true if check passes, false if it fails */ bool finalize( STTx const& tx, TER const tec, XRPAmount const fee, ReadView const& view, beast::Journal const& j); }; #endif /** * @brief Invariant: We should never charge a transaction a negative fee or a * fee that is larger than what the transaction itself specifies. * * We can, in some circumstances, charge less. */ class TransactionFeeCheck { public: void visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); static bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); }; /** * @brief Invariant: A transaction must not create XRP and should only destroy * the XRP fee. * * We iterate through all account roots, payment channels and escrow entries * that were modified and calculate the net change in XRP caused by the * transactions. */ class XRPNotCreated { std::int64_t drops_ = 0; 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; }; /** * @brief Invariant: we cannot remove an account ledger entry * * We iterate all account roots that were modified, and ensure that any that * were present before the transaction was applied continue to be present * afterwards unless they were explicitly deleted by a successful * AccountDelete transaction. */ class AccountRootsNotDeleted { std::uint32_t accountsDeleted_ = 0; 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; }; /** * @brief Invariant: a deleted account must not have any objects left * * We iterate all deleted account roots, and ensure that there are no * objects left that are directly accessible with that account's ID. * * There should only be one deleted account, but that's checked by * AccountRootsNotDeleted. This invariant will handle multiple deleted account * roots without a problem. */ class AccountRootsDeletedClean { // Pair is . Before is used for most of the checks, so that // if, for example, an object ID field is cleared, but the object is not // 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_; 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&); }; /** * @brief Invariant: An account XRP balance must be in XRP and take a value * between 0 and INITIAL_XRP drops, inclusive. * * We iterate all account roots modified by the transaction and ensure that * their XRP balances are reasonable. */ class XRPBalanceChecks { bool bad_ = false; 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; }; /** * @brief Invariant: corresponding modified ledger entries should match in type * and added entries should be a valid type. */ class LedgerEntryTypesMatch { bool typeMismatch_ = false; bool invalidTypeAdded_ = false; 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; }; /** * @brief Invariant: Trust lines using XRP are not allowed. * * We iterate all the trust lines created by this transaction and ensure * that they are against a valid issuer. */ class NoXRPTrustLines { bool xrpTrustLine_ = false; 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; }; /** * @brief Invariant: Trust lines with deep freeze flag are not allowed if normal * freeze flag is not set. * * We iterate all the trust lines created by this transaction and ensure * that they don't have deep freeze flag set without normal freeze flag set. */ class NoDeepFreezeTrustLinesWithoutFreeze { bool deepFreezeWithoutFreeze_ = false; 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; }; /** * @brief Invariant: offers should be for non-negative amounts and must not * be XRP to XRP. * * Examine all offers modified by the transaction and ensure that there are * no offers which contain negative amounts or which exchange XRP for XRP. */ class NoBadOffers { bool bad_ = false; 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; }; /** * @brief Invariant: an escrow entry must take a value between 0 and * INITIAL_XRP drops exclusive. */ class NoZeroEscrow { bool bad_ = false; 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; }; /** * @brief Invariant: a new account root must be the consequence of a payment, * must have the right starting sequence, and the payment * may not create more than one new account root. */ class ValidNewAccountRoot { std::uint32_t accountsCreated_ = 0; std::uint32_t accountSeq_ = 0; bool pseudoAccount_ = false; std::uint32_t flags_ = 0; 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; }; /** * @brief Invariant: Token holder's trustline balance cannot be negative after * Clawback. * * We iterate all the trust lines affected by this transaction and ensure * that no more than one trustline is modified, and also holder's balance is * non-negative. */ class ValidClawback { std::uint32_t trustlinesChanged = 0; std::uint32_t mptokensChanged = 0; 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; }; /** * @brief Invariants: Pseudo-accounts have valid and consistent properties * * Pseudo-accounts have certain properties, and some of those properties are * unique to pseudo-accounts. Check that all pseudo-accounts are following the * rules, and that only pseudo-accounts look like pseudo-accounts. * */ class ValidPseudoAccounts { std::vector errors_; public: void visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); }; /** * @brief Invariants: Some fields are unmodifiable * * Check that any fields specified as unmodifiable are not modified when the * object is modified. Creation and deletion are ignored. * */ class NoModifiedUnmodifiableFields { // Pair is . std::set> changedEntries_; 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&); }; // additional invariant checks can be declared above and then added to this // tuple using InvariantChecks = std::tuple< TransactionFeeCheck, AccountRootsNotDeleted, AccountRootsDeletedClean, LedgerEntryTypesMatch, XRPBalanceChecks, XRPNotCreated, NoXRPTrustLines, NoDeepFreezeTrustLinesWithoutFreeze, TransfersNotFrozen, NoBadOffers, NoZeroEscrow, ValidNewAccountRoot, ValidNFTokenPage, NFTokenCountTracking, ValidClawback, ValidMPTIssuance, ValidPermissionedDomain, ValidPermissionedDEX, ValidAMM, NoModifiedUnmodifiableFields, ValidPseudoAccounts, ValidLoanBroker, ValidLoan, ValidVault, ValidMPTPayment>; /** * @brief get a tuple of all invariant checks * * @return std::tuple of instances that implement the required invariant check * methods * * @see xrpl::InvariantChecker_PROTOTYPE */ inline InvariantChecks getInvariantChecks() { return InvariantChecks{}; } } // namespace xrpl