20#include <xrpld/app/tx/detail/InvariantCheck.h>
22#include <xrpld/app/misc/CredentialHelpers.h>
23#include <xrpld/app/tx/detail/NFTokenUtils.h>
24#include <xrpld/app/tx/detail/PermissionedDomainSet.h>
25#include <xrpld/ledger/ReadView.h>
26#include <xrpld/ledger/View.h>
27#include <xrpl/basics/Log.h>
28#include <xrpl/protocol/Feature.h>
29#include <xrpl/protocol/FeeUnits.h>
30#include <xrpl/protocol/STArray.h>
31#include <xrpl/protocol/SystemParameters.h>
32#include <xrpl/protocol/TxFormats.h>
33#include <xrpl/protocol/nftPageMask.h>
57 JLOG(j.
fatal()) <<
"Invariant failed: fee paid was negative: "
66 JLOG(j.
fatal()) <<
"Invariant failed: fee paid exceeds system limit: "
75 JLOG(j.
fatal()) <<
"Invariant failed: fee paid is " << fee.
drops()
76 <<
" exceeds fee specified in transaction.";
100 switch (before->getType())
103 drops_ -= (*before)[sfBalance].xrp().drops();
107 ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
110 drops_ -= (*before)[sfAmount].xrp().drops();
119 switch (
after->getType())
122 drops_ += (*after)[sfBalance].xrp().drops();
126 drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
132 drops_ += (*after)[sfAmount].xrp().drops();
152 JLOG(j.
fatal()) <<
"Invariant failed: XRP net change was positive: "
160 JLOG(j.
fatal()) <<
"Invariant failed: XRP net change of " <<
drops_
161 <<
" doesn't match fee " << fee.
drops();
176 auto isBad = [](
STAmount const& balance) {
177 if (!balance.native())
180 auto const drops = balance.xrp();
194 if (before && before->getType() == ltACCOUNT_ROOT)
195 bad_ |= isBad((*before)[sfBalance]);
197 if (
after &&
after->getType() == ltACCOUNT_ROOT)
211 JLOG(j.
fatal()) <<
"Invariant failed: incorrect account XRP balance";
228 if (pays < beast::zero)
231 if (gets < beast::zero)
235 return pays.
native() && gets.native();
238 if (before && before->getType() == ltOFFER)
239 bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
242 bad_ |= isBad((*
after)[sfTakerPays], (*after)[sfTakerGets]);
255 JLOG(j.
fatal()) <<
"Invariant failed: offer with a bad amount";
270 auto isBad = [](
STAmount const& amount) {
271 if (!amount.native())
283 if (before && before->getType() == ltESCROW)
284 bad_ |= isBad((*before)[sfAmount]);
300 JLOG(j.
fatal()) <<
"Invariant failed: escrow specifies invalid amount";
315 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
339 JLOG(j.
fatal()) <<
"Invariant failed: account deletion "
340 "succeeded without deleting an account";
342 JLOG(j.
fatal()) <<
"Invariant failed: account deletion "
343 "succeeded but deleted multiple accounts!";
358 JLOG(j.
fatal()) <<
"Invariant failed: an account root was deleted";
370 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
386 [[maybe_unused]]
bool const enforce =
389 auto const objectExists = [&view, enforce, &j](
auto const& keylet) {
390 if (
auto const sle = view.
read(keylet))
393 auto const typeName = [&sle]() {
398 return item->getName();
403 <<
"Invariant failed: account deletion left behind a "
404 << typeName <<
" object";
407 "ripple::AccountRootsDeletedClean::finalize::objectExists : "
408 "account deletion left no objects behind");
416 auto const accountID = accountSLE->getAccountID(sfAccount);
420 if (objectExists(
std::invoke(keyletfunc, accountID)) && enforce)
435 if (key && objectExists(
Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
440 if (
auto const ammKey = accountSLE->at(~sfAMMID))
458 if (before &&
after && before->getType() !=
after->getType())
463 switch (
after->getType())
471 case ltLEDGER_HASHES:
477 case ltDEPOSIT_PREAUTH:
480 case ltNFTOKEN_OFFER:
483 case ltXCHAIN_OWNED_CLAIM_ID:
484 case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID:
487 case ltMPTOKEN_ISSUANCE:
490 case ltPERMISSIONED_DOMAIN:
512 JLOG(j.
fatal()) <<
"Invariant failed: ledger entry type mismatch";
517 JLOG(j.
fatal()) <<
"Invariant failed: invalid ledger entry type added";
531 if (
after &&
after->getType() == ltRIPPLE_STATE)
553 JLOG(j.
fatal()) <<
"Invariant failed: an XRP trust line was created";
565 if (!before &&
after->getType() == ltACCOUNT_ROOT)
585 JLOG(j.
fatal()) <<
"Invariant failed: multiple accounts "
586 "created in a single transaction";
592 tx.
getTxnType() == ttXCHAIN_ADD_CLAIM_ATTESTATION ||
593 tx.
getTxnType() == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) &&
601 JLOG(j.
fatal()) <<
"Invariant failed: account created with "
602 "wrong starting sequence number";
608 JLOG(j.
fatal()) <<
"Invariant failed: account root created "
609 "by a non-Payment, by an unsuccessful transaction, "
623 static constexpr uint256 const accountBits = ~pageBits;
625 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
626 (
after &&
after->getType() != ltNFTOKEN_PAGE))
630 uint256 const account = sle->key() & accountBits;
631 uint256 const hiLimit = sle->key() & pageBits;
639 if (account != (*prev & accountBits))
642 if (hiLimit <= (*prev & pageBits))
646 if (
auto const next = (*sle)[~sfNextPageMin])
648 if (account != (*next & accountBits))
651 if (hiLimit >= (*next & pageBits))
656 auto const& nftokens = sle->getFieldArray(sfNFTokens);
659 if (
std::size_t const nftokenCount = nftokens.size();
660 (!isDelete && nftokenCount == 0) ||
667 prev ? *prev & pageBits :
uint256(beast::zero);
671 for (
auto const& obj : nftokens)
673 uint256 const tokenID = obj[sfNFTokenID];
680 if (
uint256 const tokenPageBits = tokenID & pageBits;
681 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
684 if (
auto uri = obj[~sfURI]; uri && uri->empty())
698 before->isFieldPresent(sfPreviousPageMin))
707 if (!isDelete && before &&
after)
715 before->isFieldPresent(sfNextPageMin) &&
716 !
after->isFieldPresent(sfNextPageMin))
733 JLOG(j.
fatal()) <<
"Invariant failed: NFT page is improperly linked.";
739 JLOG(j.
fatal()) <<
"Invariant failed: NFT found in incorrect page.";
745 JLOG(j.
fatal()) <<
"Invariant failed: NFTs on page are not sorted.";
751 JLOG(j.
fatal()) <<
"Invariant failed: NFT contains empty URI.";
757 JLOG(j.
fatal()) <<
"Invariant failed: NFT page has invalid size.";
765 JLOG(j.
fatal()) <<
"Invariant failed: Last NFT page deleted with "
766 "non-empty directory.";
771 JLOG(j.
fatal()) <<
"Invariant failed: Lost NextMinPage link.";
786 if (before && before->getType() == ltACCOUNT_ROOT)
792 if (
after &&
after->getType() == ltACCOUNT_ROOT)
808 txType != ttNFTOKEN_MINT && txType != ttNFTOKEN_BURN)
812 JLOG(j.
fatal()) <<
"Invariant failed: the number of minted tokens "
813 "changed without a mint transaction!";
819 JLOG(j.
fatal()) <<
"Invariant failed: the number of burned tokens "
820 "changed without a burn transaction!";
832 <<
"Invariant failed: successful minting didn't increase "
833 "the number of minted tokens.";
839 JLOG(j.
fatal()) <<
"Invariant failed: failed minting changed the "
840 "number of minted tokens.";
847 <<
"Invariant failed: minting changed the number of "
860 <<
"Invariant failed: successful burning didn't increase "
861 "the number of burned tokens.";
868 JLOG(j.
fatal()) <<
"Invariant failed: failed burning changed the "
869 "number of burned tokens.";
876 <<
"Invariant failed: burning changed the number of "
893 if (before && before->getType() == ltRIPPLE_STATE)
896 if (before && before->getType() == ltMPTOKEN)
916 <<
"Invariant failed: more than one trustline changed.";
923 <<
"Invariant failed: more than one mptokens changed.";
935 if (holderBalance.
signum() < 0)
938 <<
"Invariant failed: trustline balance is negative";
947 JLOG(j.
fatal()) <<
"Invariant failed: some trustlines were changed "
948 "despite failure of the transaction.";
954 JLOG(j.
fatal()) <<
"Invariant failed: some mptokens were changed "
955 "despite failure of the transaction.";
971 if (
after &&
after->getType() == ltMPTOKEN_ISSUANCE)
998 if (tx.
getTxnType() == ttMPTOKEN_ISSUANCE_CREATE)
1002 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance creation "
1003 "succeeded without creating a MPT issuance";
1007 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance creation "
1008 "succeeded while removing MPT issuances";
1012 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance creation "
1013 "succeeded but created multiple issuances";
1019 if (tx.
getTxnType() == ttMPTOKEN_ISSUANCE_DESTROY)
1023 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance deletion "
1024 "succeeded without removing a MPT issuance";
1028 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance deletion "
1029 "succeeded while creating MPT issuances";
1033 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance deletion "
1034 "succeeded but deleted multiple issuances";
1046 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize "
1047 "succeeded but created MPT issuances";
1052 JLOG(j.
fatal()) <<
"Invariant failed: MPT authorize "
1053 "succeeded but deleted issuances";
1057 submittedByIssuer &&
1061 <<
"Invariant failed: MPT authorize submitted by issuer "
1062 "succeeded but created/deleted mptokens";
1066 !submittedByIssuer &&
1072 <<
"Invariant failed: MPT authorize submitted by holder "
1073 "succeeded but created/deleted bad number of mptokens";
1080 if (tx.
getTxnType() == ttMPTOKEN_ISSUANCE_SET)
1084 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance set "
1085 "succeeded while removing MPT issuances";
1089 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance set "
1090 "succeeded while creating MPT issuances";
1094 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance set "
1095 "succeeded while removing MPTokens";
1099 JLOG(j.
fatal()) <<
"Invariant failed: MPT issuance set "
1100 "succeeded while creating MPTokens";
1110 JLOG(j.
fatal()) <<
"Invariant failed: a MPT issuance was created";
1114 JLOG(j.
fatal()) <<
"Invariant failed: a MPT issuance was deleted";
1118 JLOG(j.
fatal()) <<
"Invariant failed: a MPToken was created";
1122 JLOG(j.
fatal()) <<
"Invariant failed: a MPToken was deleted";
1137 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
1139 if (
after &&
after->getType() != ltPERMISSIONED_DOMAIN)
1144 auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
1155 for (
auto const& cred : sorted)
1157 auto const& credTx = credentials[i++];
1158 sleStatus.
isSorted_ = (cred.first == credTx[sfIssuer]) &&
1159 (cred.second == credTx[sfCredentialType]);
1193 JLOG(j.
fatal()) <<
"Invariant failed: permissioned domain with "
1201 JLOG(j.
fatal()) <<
"Invariant failed: permissioned domain bad "
1210 <<
"Invariant failed: permissioned domain credentials "
1218 <<
"Invariant failed: permissioned domain credentials "
A generic endpoint for log messages.
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 &)
std::vector< std::shared_ptr< SLE const > > accountsDeleted_
std::uint32_t accountsDeleted_
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 &)
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 &)
std::uint32_t afterMintedTotal
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t afterBurnedTotal
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::uint32_t beforeBurnedTotal
std::uint32_t beforeMintedTotal
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 &)
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 &)
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
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.
LedgerIndex seq() const
Returns the sequence number of the base ledger.
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Currency const & getCurrency() const
int signum() const noexcept
AccountID const & getIssuer() const
bool native() const noexcept
AccountID getAccountID(SField const &field) const
STAmount const & getFieldAmount(SField const &field) const
bool isFieldPresent(SField const &field) const
TxType getTxnType() 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 &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::uint32_t trustlinesChanged
std::uint32_t mptokensChanged
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t mptIssuancesCreated_
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::uint32_t mptokensCreated_
std::uint32_t mptIssuancesDeleted_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t mptokensDeleted_
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 &)
std::uint32_t accountsCreated_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t accountSeq_
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 &)
std::optional< SleStatus > sleStatus_[2]
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 &)
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 &)
std::set< std::pair< AccountID, Slice > > makeSorted(STArray const &credentials)
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
bool compareTokens(uint256 const &a, uint256 const &b)
uint256 constexpr pageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
TxType
Transaction type identifiers.
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
std::size_t constexpr dirMaxTokensPerPage
The maximum number of items in an NFT page.
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const ¤cy, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
static bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
A pair of SHAMap key and LedgerEntryType.
std::size_t credentialsSize_