1#include <xrpl/tx/invariants/InvariantCheck.h>
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/base_uint.h>
5#include <xrpl/beast/utility/Journal.h>
6#include <xrpl/beast/utility/Zero.h>
7#include <xrpl/beast/utility/instrumentation.h>
8#include <xrpl/ledger/ReadView.h>
9#include <xrpl/ledger/helpers/AccountRootHelpers.h>
10#include <xrpl/ledger/helpers/TokenHelpers.h>
11#include <xrpl/protocol/AccountID.h>
12#include <xrpl/protocol/Feature.h>
13#include <xrpl/protocol/Indexes.h>
14#include <xrpl/protocol/Issue.h>
15#include <xrpl/protocol/Keylet.h>
16#include <xrpl/protocol/LedgerFormats.h>
17#include <xrpl/protocol/MPTIssue.h>
18#include <xrpl/protocol/Protocol.h>
19#include <xrpl/protocol/Rules.h>
20#include <xrpl/protocol/SField.h>
21#include <xrpl/protocol/STAmount.h>
22#include <xrpl/protocol/STLedgerEntry.h>
23#include <xrpl/protocol/STNumber.h>
24#include <xrpl/protocol/STTx.h>
25#include <xrpl/protocol/SystemParameters.h>
26#include <xrpl/protocol/TER.h>
27#include <xrpl/protocol/TxFormats.h>
28#include <xrpl/protocol/UintTypes.h>
29#include <xrpl/protocol/XRPAmount.h>
30#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
42#pragma push_macro("TRANSACTION")
45#define TRANSACTION(tag, value, name, delegable, amendment, privileges, ...) \
47 return (privileges) & priv; \
55#include <xrpl/protocol/detail/transactions.macro>
64#pragma pop_macro("TRANSACTION")
83 JLOG(j.
fatal()) <<
"Invariant failed: fee paid was negative: " << fee.
drops();
91 JLOG(j.
fatal()) <<
"Invariant failed: fee paid exceeds system limit: " << fee.
drops();
99 JLOG(j.
fatal()) <<
"Invariant failed: fee paid is " << fee.
drops()
100 <<
" exceeds fee specified in transaction.";
121 switch (before->getType())
124 drops_ -= (*before)[sfBalance].xrp().drops();
127 drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
130 if (
isXRP((*before)[sfAmount]))
131 drops_ -= (*before)[sfAmount].xrp().drops();
140 switch (
after->getType())
143 drops_ += (*after)[sfBalance].xrp().drops();
147 drops_ += ((*after)[sfAmount] - (*after)[sfBalance]).xrp().drops();
151 drops_ += (*after)[sfAmount].xrp().drops();
171 JLOG(j.
fatal()) <<
"Invariant failed: XRP net change was positive: " <<
drops_;
178 JLOG(j.
fatal()) <<
"Invariant failed: XRP net change of " <<
drops_ <<
" doesn't match fee "
191 auto isBad = [](
STAmount const& balance) {
192 if (!balance.native())
195 auto const drops = balance.xrp();
209 if (before && before->getType() == ltACCOUNT_ROOT)
210 bad_ |= isBad((*before)[sfBalance]);
212 if (
after &&
after->getType() == ltACCOUNT_ROOT)
226 JLOG(j.
fatal()) <<
"Invariant failed: incorrect account XRP balance";
240 if (pays < beast::kZero)
243 if (gets < beast::kZero)
247 return pays.
native() && gets.native();
250 if (before && before->getType() == ltOFFER)
251 bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
267 JLOG(j.
fatal()) <<
"Invariant failed: offer with a bad amount";
279 auto isBad = [](
STAmount const& amount) {
291 return amount.asset().visit(
292 [&](
Issue const& issue) {
294 if (amount <= beast::kZero)
306 if (amount <= beast::kZero)
309 if (amount.mpt() >
MPTAmount{kMaxMpTokenAmount})
318 if (before && before->getType() == ltESCROW)
319 bad_ |= isBad((*before)[sfAmount]);
331 if (
after &&
after->getType() == ltMPTOKEN_ISSUANCE)
333 auto const outstanding = (*after)[sfOutstandingAmount];
334 checkAmount(outstanding);
335 if (
auto const locked = (*
after)[~sfLockedAmount])
337 checkAmount(*locked);
338 bool const isBad = outstanding < *locked;
339 if (overwriteFixEnabled)
352 auto const mptAmount = (*after)[sfMPTAmount];
353 checkAmount(mptAmount);
354 if (
auto const locked = (*
after)[~sfLockedAmount])
356 checkAmount(*locked);
371 JLOG(j.
fatal()) <<
"Invariant failed: escrow specifies invalid amount";
383 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
406 JLOG(j.
fatal()) <<
"Invariant failed: account deletion "
407 "succeeded without deleting an account";
411 JLOG(j.
fatal()) <<
"Invariant failed: account deletion "
412 "succeeded but deleted multiple accounts!";
426 JLOG(j.
fatal()) <<
"Invariant failed: an account root was deleted";
435 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
451 [[maybe_unused]]
bool const enforce = view.
rules().
enabled(fixCleanup3_2_0) ||
455 auto const objectExists = [&view, enforce, &j](
auto const&
keylet) {
460 auto const typeName = [&sle]() {
464 return item->getName();
468 JLOG(j.
fatal()) <<
"Invariant failed: account deletion left behind a " << typeName
474 "xrpl::AccountRootsDeletedClean::finalize::objectExists : "
475 "account deletion left no objects behind");
483 auto const accountID = before->getAccountID(sfAccount);
485 if (
after->at(sfBalance) != beast::kZero)
487 JLOG(j.
fatal()) <<
"Invariant failed: account deletion left "
488 "behind a non-zero balance";
491 "xrpl::AccountRootsDeletedClean::finalize : "
492 "deleted account has zero balance");
497 if (
after->at(sfOwnerCount) != 0)
499 JLOG(j.
fatal()) <<
"Invariant failed: account deletion left "
500 "behind a non-zero owner count";
503 "xrpl::AccountRootsDeletedClean::finalize : "
504 "deleted account has zero owner count");
512 if (objectExists(
std::invoke(keyletfunc, accountID)) && enforce)
527 if (key && objectExists(
Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
535 if (before->isFieldPresent(*field))
537 auto const key = before->getFieldH256(*field);
552 if (before &&
after && before->getType() !=
after->getType())
557#pragma push_macro("LEDGER_ENTRY")
560#define LEDGER_ENTRY(tag, ...) case tag:
562 switch (
after->getType())
564#include <xrpl/protocol/detail/ledger_entries.macro>
573#pragma pop_macro("LEDGER_ENTRY")
590 JLOG(j.
fatal()) <<
"Invariant failed: ledger entry type mismatch";
595 JLOG(j.
fatal()) <<
"Invariant failed: invalid ledger entry type added";
608 if (
after &&
after->getType() == ltRIPPLE_STATE)
613 bool const isXrp =
after->getFieldAmount(sfLowLimit).asset() ==
xrpIssue() ||
615 if (overwriteFixEnabled)
637 JLOG(j.
fatal()) <<
"Invariant failed: an XRP trust line was created";
646 if (
after &&
after->getType() == ltRIPPLE_STATE)
650 bool const lowFreeze =
after->isFlag(lsfLowFreeze);
651 bool const lowDeepFreeze =
after->isFlag(lsfLowDeepFreeze);
653 bool const highFreeze =
after->isFlag(lsfHighFreeze);
654 bool const highDeepFreeze =
after->isFlag(lsfHighDeepFreeze);
656 bool const bad = (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze);
657 if (overwriteFixEnabled)
679 JLOG(j.
fatal()) <<
"Invariant failed: a trust line with deep freeze flag "
680 "without normal freeze was created";
689 if (!before &&
after->getType() == ltACCOUNT_ROOT)
711 JLOG(j.
fatal()) <<
"Invariant failed: multiple accounts "
712 "created in a single transaction";
719 bool const pseudoAccount =
721 (view.
rules().enabled(featureSingleAssetVault) ||
726 JLOG(j.
fatal()) <<
"Invariant failed: pseudo-account created by a "
727 "wrong transaction type";
735 JLOG(j.
fatal()) <<
"Invariant failed: account created with "
736 "wrong starting sequence number";
742 std::uint32_t const expected = (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
745 JLOG(j.
fatal()) <<
"Invariant failed: pseudo-account created with "
754 JLOG(j.
fatal()) <<
"Invariant failed: account root created illegally";
763 if (before && before->getType() == ltRIPPLE_STATE)
766 if (before && before->getType() == ltMPTOKEN)
785 JLOG(j.
fatal()) <<
"Invariant failed: more than one trustline changed.";
791 JLOG(j.
fatal()) <<
"Invariant failed: more than one mptokens changed.";
795 bool const mptV2Enabled = view.
rules().
enabled(featureMPTokensV2);
802 [&](
Issue const& issue) {
816 if (holderBalance.
signum() < 0)
818 JLOG(j.
fatal()) <<
"Invariant failed: trustline or MPT balance is negative";
827 JLOG(j.
fatal()) <<
"Invariant failed: some trustlines were changed "
828 "despite failure of the transaction.";
834 JLOG(j.
fatal()) <<
"Invariant failed: some mptokens were changed "
835 "despite failure of the transaction.";
854 if (
after &&
after->getType() == ltACCOUNT_ROOT)
856 bool const isPseudo = [&]() {
863 if (
after->at(sfSequence) == 0)
885 error <<
"pseudo-account has " << numFields <<
" pseudo-account fields set";
889 if (before && before->at(sfSequence) !=
after->at(sfSequence))
891 errors_.emplace_back(
"pseudo-account sequence changed");
893 if (!
after->isFlag(lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth))
895 errors_.emplace_back(
"pseudo-account flags are not set");
897 if (
after->isFieldPresent(sfRegularKey))
899 errors_.emplace_back(
"pseudo-account has a regular key");
913 bool const enforce = view.
rules().
enabled(featureSingleAssetVault);
916 "xrpl::ValidPseudoAccounts::finalize : no bad "
917 "changes or enforce invariant");
920 for (
auto const& error :
errors_)
922 JLOG(j.
fatal()) <<
"Invariant failed: " << error;
935 if (isDelete || !before)
952 static auto const kFieldChanged = [](
auto const& before,
auto const&
after,
auto const& field) {
953 bool const beforeField = before->isFieldPresent(field);
954 bool const afterField =
after->isFieldPresent(field);
955 return beforeField != afterField || (afterField && before->at(field) !=
after->at(field));
959 auto const& before = slePair.first;
960 auto const&
after = slePair.second;
961 auto const type =
after->getType();
963 [[maybe_unused]]
bool enforce =
false;
973 bad = kFieldChanged(before,
after, sfLedgerEntryType) ||
974 kFieldChanged(before,
after, sfLedgerIndex) ||
975 kFieldChanged(before,
after, sfSequence) ||
976 kFieldChanged(before,
after, sfOwnerNode) ||
977 kFieldChanged(before,
after, sfVaultNode) ||
978 kFieldChanged(before,
after, sfVaultID) ||
979 kFieldChanged(before,
after, sfAccount) ||
980 kFieldChanged(before,
after, sfOwner) ||
981 kFieldChanged(before,
after, sfManagementFeeRate) ||
982 kFieldChanged(before,
after, sfCoverRateMinimum) ||
983 kFieldChanged(before,
after, sfCoverRateLiquidation);
992 bad = kFieldChanged(before,
after, sfLedgerEntryType) ||
993 kFieldChanged(before,
after, sfLedgerIndex) ||
994 kFieldChanged(before,
after, sfSequence) ||
995 kFieldChanged(before,
after, sfOwnerNode) ||
996 kFieldChanged(before,
after, sfLoanBrokerNode) ||
997 kFieldChanged(before,
after, sfLoanBrokerID) ||
998 kFieldChanged(before,
after, sfBorrower) ||
999 kFieldChanged(before,
after, sfLoanOriginationFee) ||
1000 kFieldChanged(before,
after, sfLoanServiceFee) ||
1001 kFieldChanged(before,
after, sfLatePaymentFee) ||
1002 kFieldChanged(before,
after, sfClosePaymentFee) ||
1003 kFieldChanged(before,
after, sfOverpaymentFee) ||
1004 kFieldChanged(before,
after, sfInterestRate) ||
1005 kFieldChanged(before,
after, sfLateInterestRate) ||
1006 kFieldChanged(before,
after, sfCloseInterestRate) ||
1007 kFieldChanged(before,
after, sfOverpaymentInterestRate) ||
1008 kFieldChanged(before,
after, sfStartDate) ||
1009 kFieldChanged(before,
after, sfPaymentInterval) ||
1010 kFieldChanged(before,
after, sfGracePeriod) ||
1011 kFieldChanged(before,
after, sfLoanScale);
1023 enforce = view.
rules().
enabled(featureLendingProtocol);
1024 bad = kFieldChanged(before,
after, sfLedgerEntryType) ||
1025 kFieldChanged(before,
after, sfLedgerIndex);
1029 "xrpl::NoModifiedUnmodifiableFields::finalize : no bad "
1030 "changes or enforce invariant");
1033 JLOG(j.
fatal()) <<
"Invariant failed: changed an unchangeable field for "
1048 if (!isDelete &&
after)
1066 <<
"Invariant failed: ledger entry contains non-canonical MPT or XRP amount";
A generic endpoint for log messages.
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< std::pair< SLE::const_pointer, SLE::const_pointer > > accountsDeleted_
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
std::uint32_t accountsDeleted_
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
A currency issued by an account.
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool deepFreezeWithoutFreeze_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
std::set< std::pair< SLE::const_pointer, SLE::const_pointer > > changedEntries_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
LedgerIndex seq() const
Returns the sequence number of the base ledger.
virtual std::optional< key_type > succ(key_type const &key, std::optional< key_type > const &last=std::nullopt) const =0
Return the key of the next state item.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
int signum() const noexcept
bool native() const noexcept
Asset const & asset() const
AccountID const & getIssuer() const
std::shared_ptr< STLedgerEntry const > const & const_ref
AccountID getAccountID(SField const &field) const
STAmount const & getFieldAmount(SField const &field) const
TxType getTxnType() const
uint256 getTransactionID() const
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
static bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::vector< std::shared_ptr< SLE const > > afterEntries_
std::uint32_t mptokensChanged_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
std::uint32_t trustlinesChanged_
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
std::uint32_t accountsCreated_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
std::uint32_t accountSeq_
std::vector< std::string > errors_
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
constexpr value_type drops() const
Returns the number of drops.
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
Keylet computation functions.
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Keylet nftokenPageMin(AccountID const &owner)
NFT page keylets.
Keylet nftokenPageMax(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
std::vector< SField const * > const & getPseudoAccountFields()
Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account if set.
bool isFeatureEnabled(uint256 const &feature, bool resultIfNoRules)
Check whether a feature is enabled in the current ledger rules.
bool isXRP(AccountID const &c)
std::array< KeyletDesc< AccountID const & >, 6 > const kDirectAccountKeylets
bool hasPrivilege(STTx const &tx, Privilege priv)
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
bool hasInvalidAmount(STBase const &field, beast::Journal j)
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
bool isTesSuccess(TER x) noexcept
TERSubset< CanCvtToTER > TER
bool isPseudoAccount(SLE::const_pointer sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Returns true if and only if sleAcct is a pseudo-account or specific pseudo-accounts in pseudoFieldFil...
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
constexpr std::uint64_t kMaxMpTokenAmount
The maximum amount of MPTokenIssuance.
constexpr XRPAmount kInitialXrp
Configure the native currency.
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const ¤cy, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=SpendableHandling::SimpleBalance)
A pair of SHAMap key and LedgerEntryType.