1#include <xrpl/tx/invariants/InvariantCheck.h>
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/utility/instrumentation.h>
5#include <xrpl/ledger/View.h>
6#include <xrpl/ledger/helpers/AccountRootHelpers.h>
7#include <xrpl/ledger/helpers/RippleStateHelpers.h>
8#include <xrpl/protocol/Feature.h>
9#include <xrpl/protocol/Indexes.h>
10#include <xrpl/protocol/LedgerFormats.h>
11#include <xrpl/protocol/SField.h>
12#include <xrpl/protocol/STAmount.h>
13#include <xrpl/protocol/STNumber.h>
14#include <xrpl/protocol/SystemParameters.h>
15#include <xrpl/protocol/TxFormats.h>
16#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
23#pragma push_macro("TRANSACTION")
26#define TRANSACTION(tag, value, name, delegable, amendment, privileges, ...) \
28 return (privileges) & priv; \
36#include <xrpl/protocol/detail/transactions.macro>
45#pragma pop_macro("TRANSACTION")
67 JLOG(j.
fatal()) <<
"Invariant failed: fee paid was negative: " << fee.
drops();
75 JLOG(j.
fatal()) <<
"Invariant failed: fee paid exceeds system limit: " << fee.
drops();
83 JLOG(j.
fatal()) <<
"Invariant failed: fee paid is " << fee.
drops()
84 <<
" exceeds fee specified in transaction.";
108 switch (before->getType())
111 drops_ -= (*before)[sfBalance].xrp().drops();
114 drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
117 if (
isXRP((*before)[sfAmount]))
118 drops_ -= (*before)[sfAmount].xrp().drops();
127 switch (
after->getType())
130 drops_ += (*after)[sfBalance].xrp().drops();
134 drops_ += ((*after)[sfAmount] - (*after)[sfBalance]).xrp().drops();
138 drops_ += (*after)[sfAmount].xrp().drops();
158 JLOG(j.
fatal()) <<
"Invariant failed: XRP net change was positive: " <<
drops_;
165 JLOG(j.
fatal()) <<
"Invariant failed: XRP net change of " <<
drops_ <<
" doesn't match fee "
181 auto isBad = [](
STAmount const& balance) {
182 if (!balance.native())
185 auto const drops = balance.xrp();
199 if (before && before->getType() == ltACCOUNT_ROOT)
200 bad_ |= isBad((*before)[sfBalance]);
202 if (
after &&
after->getType() == ltACCOUNT_ROOT)
216 JLOG(j.
fatal()) <<
"Invariant failed: incorrect account XRP balance";
233 if (pays < beast::zero)
236 if (gets < beast::zero)
240 return pays.
native() && gets.native();
243 if (before && before->getType() == ltOFFER)
244 bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
247 bad_ |= isBad((*
after)[sfTakerPays], (*after)[sfTakerGets]);
260 JLOG(j.
fatal()) <<
"Invariant failed: offer with a bad amount";
275 auto isBad = [](
STAmount const& amount) {
288 if (amount.holds<
Issue>())
290 if (amount <= beast::zero)
300 if (amount <= beast::zero)
310 if (before && before->getType() == ltESCROW)
311 bad_ |= isBad((*before)[sfAmount]);
321 if (
after &&
after->getType() == ltMPTOKEN_ISSUANCE)
323 auto const outstanding = (*after)[sfOutstandingAmount];
324 checkAmount(outstanding);
325 if (
auto const locked = (*
after)[~sfLockedAmount])
327 checkAmount(*locked);
328 bad_ = outstanding < *locked;
334 auto const mptAmount = (*after)[sfMPTAmount];
335 checkAmount(mptAmount);
336 if (
auto const locked = (*
after)[~sfLockedAmount])
338 checkAmount(*locked);
353 JLOG(j.
fatal()) <<
"Invariant failed: escrow specifies invalid amount";
368 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
391 JLOG(j.
fatal()) <<
"Invariant failed: account deletion "
392 "succeeded without deleting an account";
395 JLOG(j.
fatal()) <<
"Invariant failed: account deletion "
396 "succeeded but deleted multiple accounts!";
409 JLOG(j.
fatal()) <<
"Invariant failed: an account root was deleted";
421 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
437 [[maybe_unused]]
bool const enforce = view.
rules().
enabled(featureInvariantsV1_1) ||
441 auto const objectExists = [&view, enforce, &j](
auto const& keylet) {
443 if (
auto const sle = view.
read(keylet))
446 auto const typeName = [&sle]() {
450 return item->getName();
454 JLOG(j.
fatal()) <<
"Invariant failed: account deletion left behind a " << typeName
460 "xrpl::AccountRootsDeletedClean::finalize::objectExists : "
461 "account deletion left no objects behind");
469 auto const accountID = before->getAccountID(sfAccount);
471 if (
after->at(sfBalance) != beast::zero)
473 JLOG(j.
fatal()) <<
"Invariant failed: account deletion left "
474 "behind a non-zero balance";
477 "xrpl::AccountRootsDeletedClean::finalize : "
478 "deleted account has zero balance");
483 if (
after->at(sfOwnerCount) != 0)
485 JLOG(j.
fatal()) <<
"Invariant failed: account deletion left "
486 "behind a non-zero owner count";
489 "xrpl::AccountRootsDeletedClean::finalize : "
490 "deleted account has zero owner count");
498 if (objectExists(
std::invoke(keyletfunc, accountID)) && enforce)
513 if (key && objectExists(
Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
521 if (before->isFieldPresent(*field))
523 auto const key = before->getFieldH256(*field);
541 if (before &&
after && before->getType() !=
after->getType())
546#pragma push_macro("LEDGER_ENTRY")
549#define LEDGER_ENTRY(tag, ...) case tag:
551 switch (
after->getType())
553#include <xrpl/protocol/detail/ledger_entries.macro>
562#pragma pop_macro("LEDGER_ENTRY")
579 JLOG(j.
fatal()) <<
"Invariant failed: ledger entry type mismatch";
584 JLOG(j.
fatal()) <<
"Invariant failed: invalid ledger entry type added";
598 if (
after &&
after->getType() == ltRIPPLE_STATE)
619 JLOG(j.
fatal()) <<
"Invariant failed: an XRP trust line was created";
631 if (
after &&
after->getType() == ltRIPPLE_STATE)
634 bool const lowFreeze = (uFlags & lsfLowFreeze) != 0u;
635 bool const lowDeepFreeze = (uFlags & lsfLowDeepFreeze) != 0u;
637 bool const highFreeze = (uFlags & lsfHighFreeze) != 0u;
638 bool const highDeepFreeze = (uFlags & lsfHighDeepFreeze) != 0u;
655 JLOG(j.
fatal()) <<
"Invariant failed: a trust line with deep freeze flag "
656 "without normal freeze was created";
668 if (!before &&
after->getType() == ltACCOUNT_ROOT)
690 JLOG(j.
fatal()) <<
"Invariant failed: multiple accounts "
691 "created in a single transaction";
698 bool const pseudoAccount =
705 JLOG(j.
fatal()) <<
"Invariant failed: pseudo-account created by a "
706 "wrong transaction type";
714 JLOG(j.
fatal()) <<
"Invariant failed: account created with "
715 "wrong starting sequence number";
721 std::uint32_t const expected = (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
724 JLOG(j.
fatal()) <<
"Invariant failed: pseudo-account created with "
733 JLOG(j.
fatal()) <<
"Invariant failed: account root created illegally";
745 if (before && before->getType() == ltRIPPLE_STATE)
748 if (before && before->getType() == ltMPTOKEN)
767 JLOG(j.
fatal()) <<
"Invariant failed: more than one trustline changed.";
773 JLOG(j.
fatal()) <<
"Invariant failed: more than one mptokens changed.";
781 AccountID const& holder = amount.getIssuer();
785 if (holderBalance.
signum() < 0)
787 JLOG(j.
fatal()) <<
"Invariant failed: trustline balance is negative";
796 JLOG(j.
fatal()) <<
"Invariant failed: some trustlines were changed "
797 "despite failure of the transaction.";
803 JLOG(j.
fatal()) <<
"Invariant failed: some mptokens were changed "
804 "despite failure of the transaction.";
826 if (
after &&
after->getType() == ltACCOUNT_ROOT)
828 bool const isPseudo = [&]() {
835 if (
after->at(sfSequence) == 0)
851 auto const numFields =
853 return after->isFieldPresent(*sf);
858 error <<
"pseudo-account has " << numFields <<
" pseudo-account fields set";
862 if (before && before->at(sfSequence) !=
after->at(sfSequence))
866 if (!
after->isFlag(lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth))
870 if (
after->isFieldPresent(sfRegularKey))
886 bool const enforce = view.
rules().
enabled(featureSingleAssetVault);
889 "xrpl::ValidPseudoAccounts::finalize : no bad "
890 "changes or enforce invariant");
893 for (
auto const& error :
errors_)
895 JLOG(j.
fatal()) <<
"Invariant failed: " << error;
911 if (isDelete || !before)
928 static auto const fieldChanged = [](
auto const& before,
auto const&
after,
auto const& field) {
929 bool const beforeField = before->isFieldPresent(field);
930 bool const afterField =
after->isFieldPresent(field);
931 return beforeField != afterField || (afterField && before->at(field) !=
after->at(field));
935 auto const& before = slePair.first;
936 auto const&
after = slePair.second;
937 auto const type =
after->getType();
939 [[maybe_unused]]
bool enforce =
false;
949 bad = fieldChanged(before,
after, sfLedgerEntryType) ||
950 fieldChanged(before,
after, sfLedgerIndex) ||
951 fieldChanged(before,
after, sfSequence) ||
952 fieldChanged(before,
after, sfOwnerNode) ||
953 fieldChanged(before,
after, sfVaultNode) ||
954 fieldChanged(before,
after, sfVaultID) ||
955 fieldChanged(before,
after, sfAccount) ||
956 fieldChanged(before,
after, sfOwner) ||
957 fieldChanged(before,
after, sfManagementFeeRate) ||
958 fieldChanged(before,
after, sfCoverRateMinimum) ||
959 fieldChanged(before,
after, sfCoverRateLiquidation);
968 bad = fieldChanged(before,
after, sfLedgerEntryType) ||
969 fieldChanged(before,
after, sfLedgerIndex) ||
970 fieldChanged(before,
after, sfSequence) ||
971 fieldChanged(before,
after, sfOwnerNode) ||
972 fieldChanged(before,
after, sfLoanBrokerNode) ||
973 fieldChanged(before,
after, sfLoanBrokerID) ||
974 fieldChanged(before,
after, sfBorrower) ||
975 fieldChanged(before,
after, sfLoanOriginationFee) ||
976 fieldChanged(before,
after, sfLoanServiceFee) ||
977 fieldChanged(before,
after, sfLatePaymentFee) ||
978 fieldChanged(before,
after, sfClosePaymentFee) ||
979 fieldChanged(before,
after, sfOverpaymentFee) ||
980 fieldChanged(before,
after, sfInterestRate) ||
981 fieldChanged(before,
after, sfLateInterestRate) ||
982 fieldChanged(before,
after, sfCloseInterestRate) ||
983 fieldChanged(before,
after, sfOverpaymentInterestRate) ||
984 fieldChanged(before,
after, sfStartDate) ||
985 fieldChanged(before,
after, sfPaymentInterval) ||
986 fieldChanged(before,
after, sfGracePeriod) ||
987 fieldChanged(before,
after, sfLoanScale);
1000 bad = fieldChanged(before,
after, sfLedgerEntryType) ||
1001 fieldChanged(before,
after, sfLedgerIndex);
1005 "xrpl::NoModifiedUnmodifiableFields::finalize : no bad "
1006 "changes or enforce invariant");
1009 JLOG(j.
fatal()) <<
"Invariant failed: changed an unchangeable field for "
A generic endpoint for log messages.
std::vector< std::pair< std::shared_ptr< SLE const >, std::shared_ptr< SLE const > > > accountsDeleted_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
std::uint32_t accountsDeleted_
A currency issued by an account.
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
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, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool deepFreezeWithoutFreeze_
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::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, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
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, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
virtual Rules const & rules() const =0
Returns the tx processing rules.
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.
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
int signum() const noexcept
bool native() const noexcept
AccountID getAccountID(SField const &field) const
STAmount const & getFieldAmount(SField const &field) const
TxType getTxnType() const
uint256 getTransactionID() const
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
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::uint32_t trustlinesChanged
std::uint32_t mptokensChanged
std::uint32_t accountsCreated_
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::uint32_t accountSeq_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::vector< std::string > errors_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
constexpr value_type drops() const
Returns the number of drops.
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
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, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
T emplace_back(T... args)
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
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 isXRP(AccountID const &c)
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const ¤cy, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
bool isPseudoAccount(std::shared_ptr< SLE const > sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Returns true if and only if sleAcct is a pseudo-account or specific pseudo-accounts in pseudoFieldFil...
bool hasPrivilege(STTx const &tx, Privilege priv)
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
bool isTesSuccess(TER x) noexcept
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
A pair of SHAMap key and LedgerEntryType.