rippled
Loading...
Searching...
No Matches
InvariantCheck.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012-2016 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/tx/detail/InvariantCheck.h>
21
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>
34
35namespace ripple {
36
37void
39 bool,
42{
43 // nothing to do
44}
45
46bool
48 STTx const& tx,
49 TER const,
50 XRPAmount const fee,
51 ReadView const&,
52 beast::Journal const& j)
53{
54 // We should never charge a negative fee
55 if (fee.drops() < 0)
56 {
57 JLOG(j.fatal()) << "Invariant failed: fee paid was negative: "
58 << fee.drops();
59 return false;
60 }
61
62 // We should never charge a fee that's greater than or equal to the
63 // entire XRP supply.
64 if (fee >= INITIAL_XRP)
65 {
66 JLOG(j.fatal()) << "Invariant failed: fee paid exceeds system limit: "
67 << fee.drops();
68 return false;
69 }
70
71 // We should never charge more for a transaction than the transaction
72 // authorizes. It's possible to charge less in some circumstances.
73 if (fee > tx.getFieldAmount(sfFee).xrp())
74 {
75 JLOG(j.fatal()) << "Invariant failed: fee paid is " << fee.drops()
76 << " exceeds fee specified in transaction.";
77 return false;
78 }
79
80 return true;
81}
82
83//------------------------------------------------------------------------------
84
85void
87 bool isDelete,
88 std::shared_ptr<SLE const> const& before,
90{
91 /* We go through all modified ledger entries, looking only at account roots,
92 * escrow payments, and payment channels. We remove from the total any
93 * previous XRP values and add to the total any new XRP values. The net
94 * balance of a payment channel is computed from two fields (amount and
95 * balance) and deletions are ignored for paychan and escrow because the
96 * amount fields have not been adjusted for those in the case of deletion.
97 */
98 if (before)
99 {
100 switch (before->getType())
101 {
102 case ltACCOUNT_ROOT:
103 drops_ -= (*before)[sfBalance].xrp().drops();
104 break;
105 case ltPAYCHAN:
106 drops_ -=
107 ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
108 break;
109 case ltESCROW:
110 drops_ -= (*before)[sfAmount].xrp().drops();
111 break;
112 default:
113 break;
114 }
115 }
116
117 if (after)
118 {
119 switch (after->getType())
120 {
121 case ltACCOUNT_ROOT:
122 drops_ += (*after)[sfBalance].xrp().drops();
123 break;
124 case ltPAYCHAN:
125 if (!isDelete)
126 drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
127 .xrp()
128 .drops();
129 break;
130 case ltESCROW:
131 if (!isDelete)
132 drops_ += (*after)[sfAmount].xrp().drops();
133 break;
134 default:
135 break;
136 }
137 }
138}
139
140bool
142 STTx const& tx,
143 TER const,
144 XRPAmount const fee,
145 ReadView const&,
146 beast::Journal const& j)
147{
148 // The net change should never be positive, as this would mean that the
149 // transaction created XRP out of thin air. That's not possible.
150 if (drops_ > 0)
151 {
152 JLOG(j.fatal()) << "Invariant failed: XRP net change was positive: "
153 << drops_;
154 return false;
155 }
156
157 // The negative of the net change should be equal to actual fee charged.
158 if (-drops_ != fee.drops())
159 {
160 JLOG(j.fatal()) << "Invariant failed: XRP net change of " << drops_
161 << " doesn't match fee " << fee.drops();
162 return false;
163 }
164
165 return true;
166}
167
168//------------------------------------------------------------------------------
169
170void
172 bool,
173 std::shared_ptr<SLE const> const& before,
175{
176 auto isBad = [](STAmount const& balance) {
177 if (!balance.native())
178 return true;
179
180 auto const drops = balance.xrp();
181
182 // Can't have more than the number of drops instantiated
183 // in the genesis ledger.
184 if (drops > INITIAL_XRP)
185 return true;
186
187 // Can't have a negative balance (0 is OK)
188 if (drops < XRPAmount{0})
189 return true;
190
191 return false;
192 };
193
194 if (before && before->getType() == ltACCOUNT_ROOT)
195 bad_ |= isBad((*before)[sfBalance]);
196
197 if (after && after->getType() == ltACCOUNT_ROOT)
198 bad_ |= isBad((*after)[sfBalance]);
199}
200
201bool
203 STTx const&,
204 TER const,
205 XRPAmount const,
206 ReadView const&,
207 beast::Journal const& j)
208{
209 if (bad_)
210 {
211 JLOG(j.fatal()) << "Invariant failed: incorrect account XRP balance";
212 return false;
213 }
214
215 return true;
216}
217
218//------------------------------------------------------------------------------
219
220void
222 bool isDelete,
223 std::shared_ptr<SLE const> const& before,
225{
226 auto isBad = [](STAmount const& pays, STAmount const& gets) {
227 // An offer should never be negative
228 if (pays < beast::zero)
229 return true;
230
231 if (gets < beast::zero)
232 return true;
233
234 // Can't have an XRP to XRP offer:
235 return pays.native() && gets.native();
236 };
237
238 if (before && before->getType() == ltOFFER)
239 bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
240
241 if (after && after->getType() == ltOFFER)
242 bad_ |= isBad((*after)[sfTakerPays], (*after)[sfTakerGets]);
243}
244
245bool
247 STTx const&,
248 TER const,
249 XRPAmount const,
250 ReadView const&,
251 beast::Journal const& j)
252{
253 if (bad_)
254 {
255 JLOG(j.fatal()) << "Invariant failed: offer with a bad amount";
256 return false;
257 }
258
259 return true;
260}
261
262//------------------------------------------------------------------------------
263
264void
266 bool isDelete,
267 std::shared_ptr<SLE const> const& before,
269{
270 auto isBad = [](STAmount const& amount) {
271 if (!amount.native())
272 return true;
273
274 if (amount.xrp() <= XRPAmount{0})
275 return true;
276
277 if (amount.xrp() >= INITIAL_XRP)
278 return true;
279
280 return false;
281 };
282
283 if (before && before->getType() == ltESCROW)
284 bad_ |= isBad((*before)[sfAmount]);
285
286 if (after && after->getType() == ltESCROW)
287 bad_ |= isBad((*after)[sfAmount]);
288}
289
290bool
292 STTx const&,
293 TER const,
294 XRPAmount const,
295 ReadView const&,
296 beast::Journal const& j)
297{
298 if (bad_)
299 {
300 JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount";
301 return false;
302 }
303
304 return true;
305}
306
307//------------------------------------------------------------------------------
308
309void
311 bool isDelete,
312 std::shared_ptr<SLE const> const& before,
314{
315 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
317}
318
319bool
321 STTx const& tx,
322 TER const result,
323 XRPAmount const,
324 ReadView const&,
325 beast::Journal const& j)
326{
327 // AMM account root can be deleted as the result of AMM withdraw/delete
328 // transaction when the total AMM LP Tokens balance goes to 0.
329 // A successful AccountDelete or AMMDelete MUST delete exactly
330 // one account root.
331 if ((tx.getTxnType() == ttACCOUNT_DELETE ||
332 tx.getTxnType() == ttAMM_DELETE) &&
333 result == tesSUCCESS)
334 {
335 if (accountsDeleted_ == 1)
336 return true;
337
338 if (accountsDeleted_ == 0)
339 JLOG(j.fatal()) << "Invariant failed: account deletion "
340 "succeeded without deleting an account";
341 else
342 JLOG(j.fatal()) << "Invariant failed: account deletion "
343 "succeeded but deleted multiple accounts!";
344 return false;
345 }
346
347 // A successful AMMWithdraw/AMMClawback MAY delete one account root
348 // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw
349 // deletes the AMM account, accountsDeleted_ is set if it is deleted.
350 if ((tx.getTxnType() == ttAMM_WITHDRAW ||
351 tx.getTxnType() == ttAMM_CLAWBACK) &&
352 result == tesSUCCESS && accountsDeleted_ == 1)
353 return true;
354
355 if (accountsDeleted_ == 0)
356 return true;
357
358 JLOG(j.fatal()) << "Invariant failed: an account root was deleted";
359 return false;
360}
361
362//------------------------------------------------------------------------------
363
364void
366 bool isDelete,
367 std::shared_ptr<SLE const> const& before,
369{
370 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
371 accountsDeleted_.emplace_back(before);
372}
373
374bool
376 STTx const& tx,
377 TER const result,
378 XRPAmount const,
379 ReadView const& view,
380 beast::Journal const& j)
381{
382 // Always check for objects in the ledger, but to prevent differing
383 // transaction processing results, however unlikely, only fail if the
384 // feature is enabled. Enabled, or not, though, a fatal-level message will
385 // be logged
386 [[maybe_unused]] bool const enforce =
387 view.rules().enabled(featureInvariantsV1_1);
388
389 auto const objectExists = [&view, enforce, &j](auto const& keylet) {
390 if (auto const sle = view.read(keylet))
391 {
392 // Finding the object is bad
393 auto const typeName = [&sle]() {
394 auto item =
395 LedgerFormats::getInstance().findByType(sle->getType());
396
397 if (item != nullptr)
398 return item->getName();
399 return std::to_string(sle->getType());
400 }();
401
402 JLOG(j.fatal())
403 << "Invariant failed: account deletion left behind a "
404 << typeName << " object";
405 XRPL_ASSERT(
406 enforce,
407 "ripple::AccountRootsDeletedClean::finalize::objectExists : "
408 "account deletion left no objects behind");
409 return true;
410 }
411 return false;
412 };
413
414 for (auto const& accountSLE : accountsDeleted_)
415 {
416 auto const accountID = accountSLE->getAccountID(sfAccount);
417 // Simple types
418 for (auto const& [keyletfunc, _, __] : directAccountKeylets)
419 {
420 if (objectExists(std::invoke(keyletfunc, accountID)) && enforce)
421 return false;
422 }
423
424 {
425 // NFT pages. ntfpage_min and nftpage_max were already explicitly
426 // checked above as entries in directAccountKeylets. This uses
427 // view.succ() to check for any NFT pages in between the two
428 // endpoints.
429 Keylet const first = keylet::nftpage_min(accountID);
430 Keylet const last = keylet::nftpage_max(accountID);
431
432 std::optional<uint256> key = view.succ(first.key, last.key.next());
433
434 // current page
435 if (key && objectExists(Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
436 return false;
437 }
438
439 // Keys directly stored in the AccountRoot object
440 if (auto const ammKey = accountSLE->at(~sfAMMID))
441 {
442 if (objectExists(keylet::amm(*ammKey)) && enforce)
443 return false;
444 }
445 }
446
447 return true;
448}
449
450//------------------------------------------------------------------------------
451
452void
454 bool,
455 std::shared_ptr<SLE const> const& before,
457{
458 if (before && after && before->getType() != after->getType())
459 typeMismatch_ = true;
460
461 if (after)
462 {
463 switch (after->getType())
464 {
465 case ltACCOUNT_ROOT:
466 case ltDIR_NODE:
467 case ltRIPPLE_STATE:
468 case ltTICKET:
469 case ltSIGNER_LIST:
470 case ltOFFER:
471 case ltLEDGER_HASHES:
472 case ltAMENDMENTS:
473 case ltFEE_SETTINGS:
474 case ltESCROW:
475 case ltPAYCHAN:
476 case ltCHECK:
477 case ltDEPOSIT_PREAUTH:
478 case ltNEGATIVE_UNL:
479 case ltNFTOKEN_PAGE:
480 case ltNFTOKEN_OFFER:
481 case ltAMM:
482 case ltBRIDGE:
483 case ltXCHAIN_OWNED_CLAIM_ID:
484 case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID:
485 case ltDID:
486 case ltORACLE:
487 case ltMPTOKEN_ISSUANCE:
488 case ltMPTOKEN:
489 case ltCREDENTIAL:
490 case ltPERMISSIONED_DOMAIN:
491 break;
492 default:
493 invalidTypeAdded_ = true;
494 break;
495 }
496 }
497}
498
499bool
501 STTx const&,
502 TER const,
503 XRPAmount const,
504 ReadView const&,
505 beast::Journal const& j)
506{
507 if ((!typeMismatch_) && (!invalidTypeAdded_))
508 return true;
509
510 if (typeMismatch_)
511 {
512 JLOG(j.fatal()) << "Invariant failed: ledger entry type mismatch";
513 }
514
516 {
517 JLOG(j.fatal()) << "Invariant failed: invalid ledger entry type added";
518 }
519
520 return false;
521}
522
523//------------------------------------------------------------------------------
524
525void
527 bool,
530{
531 if (after && after->getType() == ltRIPPLE_STATE)
532 {
533 // checking the issue directly here instead of
534 // relying on .native() just in case native somehow
535 // were systematically incorrect
537 after->getFieldAmount(sfLowLimit).issue() == xrpIssue() ||
538 after->getFieldAmount(sfHighLimit).issue() == xrpIssue();
539 }
540}
541
542bool
544 STTx const&,
545 TER const,
546 XRPAmount const,
547 ReadView const&,
548 beast::Journal const& j)
549{
550 if (!xrpTrustLine_)
551 return true;
552
553 JLOG(j.fatal()) << "Invariant failed: an XRP trust line was created";
554 return false;
555}
556
557//------------------------------------------------------------------------------
558
559void
561 bool,
564{
565 if (after && after->getType() == ltRIPPLE_STATE)
566 {
567 std::uint32_t const uFlags = after->getFieldU32(sfFlags);
568 bool const lowFreeze = uFlags & lsfLowFreeze;
569 bool const lowDeepFreeze = uFlags & lsfLowDeepFreeze;
570
571 bool const highFreeze = uFlags & lsfHighFreeze;
572 bool const highDeepFreeze = uFlags & lsfHighDeepFreeze;
573
575 (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze);
576 }
577}
578
579bool
581 STTx const&,
582 TER const,
583 XRPAmount const,
584 ReadView const&,
585 beast::Journal const& j)
586{
588 return true;
589
590 JLOG(j.fatal()) << "Invariant failed: a trust line with deep freeze flag "
591 "without normal freeze was created";
592 return false;
593}
594
595//------------------------------------------------------------------------------
596
597void
599 bool isDelete,
600 std::shared_ptr<SLE const> const& before,
602{
603 /*
604 * A trust line freeze state alone doesn't determine if a transfer is
605 * frozen. The transfer must be examined "end-to-end" because both sides of
606 * the transfer may have different freeze states and freeze impact depends
607 * on the transfer direction. This is why first we need to track the
608 * transfers using IssuerChanges senders/receivers.
609 *
610 * Only in validateIssuerChanges, after we collected all changes can we
611 * determine if the transfer is valid.
612 */
613 if (!isValidEntry(before, after))
614 {
615 return;
616 }
617
618 auto const balanceChange = calculateBalanceChange(before, after, isDelete);
619 if (balanceChange.signum() == 0)
620 {
621 return;
622 }
623
624 recordBalanceChanges(after, balanceChange);
625}
626
627bool
629 STTx const& tx,
630 TER const ter,
631 XRPAmount const fee,
632 ReadView const& view,
633 beast::Journal const& j)
634{
635 /*
636 * We check this invariant regardless of deep freeze amendment status,
637 * allowing for detection and logging of potential issues even when the
638 * amendment is disabled.
639 *
640 * If an exploit that allows moving frozen assets is discovered,
641 * we can alert operators who monitor fatal messages and trigger assert in
642 * debug builds for an early warning.
643 *
644 * In an unlikely event that an exploit is found, this early detection
645 * enables encouraging the UNL to expedite deep freeze amendment activation
646 * or deploy hotfixes via new amendments. In case of a new amendment, we'd
647 * only have to change this line setting 'enforce' variable.
648 * enforce = view.rules().enabled(featureDeepFreeze) ||
649 * view.rules().enabled(fixFreezeExploit);
650 */
651 [[maybe_unused]] bool const enforce =
652 view.rules().enabled(featureDeepFreeze);
653
654 for (auto const& [issue, changes] : balanceChanges_)
655 {
656 auto const issuerSle = findIssuer(issue.account, view);
657 // It should be impossible for the issuer to not be found, but check
658 // just in case so rippled doesn't crash in release.
659 if (!issuerSle)
660 {
661 XRPL_ASSERT(
662 enforce,
663 "ripple::TransfersNotFrozen::finalize : enforce "
664 "invariant.");
665 if (enforce)
666 {
667 return false;
668 }
669 continue;
670 }
671
672 if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce))
673 {
674 return false;
675 }
676 }
677
678 return true;
679}
680
681bool
683 std::shared_ptr<SLE const> const& before,
685{
686 // `after` can never be null, even if the trust line is deleted.
687 XRPL_ASSERT(
688 after, "ripple::TransfersNotFrozen::isValidEntry : valid after.");
689 if (!after)
690 {
691 return false;
692 }
693
694 if (after->getType() == ltACCOUNT_ROOT)
695 {
696 possibleIssuers_.emplace(after->at(sfAccount), after);
697 return false;
698 }
699
700 /* While LedgerEntryTypesMatch invariant also checks types, all invariants
701 * are processed regardless of previous failures.
702 *
703 * This type check is still necessary here because it prevents potential
704 * issues in subsequent processing.
705 */
706 return after->getType() == ltRIPPLE_STATE &&
707 (!before || before->getType() == ltRIPPLE_STATE);
708}
709
712 std::shared_ptr<SLE const> const& before,
714 bool isDelete)
715{
716 auto const getBalance = [](auto const& line, auto const& other, bool zero) {
717 STAmount amt =
718 line ? line->at(sfBalance) : other->at(sfBalance).zeroed();
719 return zero ? amt.zeroed() : amt;
720 };
721
722 /* Trust lines can be created dynamically by other transactions such as
723 * Payment and OfferCreate that cross offers. Such trust line won't be
724 * created frozen, but the sender might be, so the starting balance must be
725 * treated as zero.
726 */
727 auto const balanceBefore = getBalance(before, after, false);
728
729 /* Same as above, trust lines can be dynamically deleted, and for frozen
730 * trust lines, payments not involving the issuer must be blocked. This is
731 * achieved by treating the final balance as zero when isDelete=true to
732 * ensure frozen line restrictions are enforced even during deletion.
733 */
734 auto const balanceAfter = getBalance(after, before, isDelete);
735
736 return balanceAfter - balanceBefore;
737}
738
739void
741{
742 XRPL_ASSERT(
743 change.balanceChangeSign,
744 "ripple::TransfersNotFrozen::recordBalance : valid trustline "
745 "balance sign.");
746 auto& changes = balanceChanges_[issue];
747 if (change.balanceChangeSign < 0)
748 changes.senders.emplace_back(std::move(change));
749 else
750 changes.receivers.emplace_back(std::move(change));
751}
752
753void
756 STAmount const& balanceChange)
757{
758 auto const balanceChangeSign = balanceChange.signum();
759 auto const currency = after->at(sfBalance).getCurrency();
760
761 // Change from low account's perspective, which is trust line default
763 {currency, after->at(sfHighLimit).getIssuer()},
764 {after, balanceChangeSign});
765
766 // Change from high account's perspective, which reverses the sign.
768 {currency, after->at(sfLowLimit).getIssuer()},
769 {after, -balanceChangeSign});
770}
771
774{
775 if (auto it = possibleIssuers_.find(issuerID); it != possibleIssuers_.end())
776 {
777 return it->second;
778 }
779
780 return view.read(keylet::account(issuerID));
781}
782
783bool
785 std::shared_ptr<SLE const> const& issuer,
786 IssuerChanges const& changes,
787 STTx const& tx,
788 beast::Journal const& j,
789 bool enforce)
790{
791 if (!issuer)
792 {
793 return false;
794 }
795
796 bool const globalFreeze = issuer->isFlag(lsfGlobalFreeze);
797 if (changes.receivers.empty() || changes.senders.empty())
798 {
799 /* If there are no receivers, then the holder(s) are returning
800 * their tokens to the issuer. Likewise, if there are no
801 * senders, then the issuer is issuing tokens to the holder(s).
802 * This is allowed regardless of the issuer's freeze flags. (The
803 * holder may have contradicting freeze flags, but that will be
804 * checked when the holder is treated as issuer.)
805 */
806 return true;
807 }
808
809 for (auto const& actors : {changes.senders, changes.receivers})
810 {
811 for (auto const& change : actors)
812 {
813 bool const high = change.line->at(sfLowLimit).getIssuer() ==
814 issuer->at(sfAccount);
815
817 change, high, tx, j, enforce, globalFreeze))
818 {
819 return false;
820 }
821 }
822 }
823 return true;
824}
825
826bool
828 BalanceChange const& change,
829 bool high,
830 STTx const& tx,
831 beast::Journal const& j,
832 bool enforce,
833 bool globalFreeze)
834{
835 bool const freeze = change.balanceChangeSign < 0 &&
836 change.line->isFlag(high ? lsfLowFreeze : lsfHighFreeze);
837 bool const deepFreeze =
838 change.line->isFlag(high ? lsfLowDeepFreeze : lsfHighDeepFreeze);
839 bool const frozen = globalFreeze || deepFreeze || freeze;
840
841 bool const isAMMLine = change.line->isFlag(lsfAMMNode);
842
843 if (!frozen)
844 {
845 return true;
846 }
847
848 // AMMClawbacks are allowed to override some freeze rules
849 if ((!isAMMLine || globalFreeze) && tx.getTxnType() == ttAMM_CLAWBACK)
850 {
851 JLOG(j.debug()) << "Invariant check allowing funds to be moved "
852 << (change.balanceChangeSign > 0 ? "to" : "from")
853 << " a frozen trustline for AMMClawback "
854 << tx.getTransactionID();
855 return true;
856 }
857
858 JLOG(j.fatal()) << "Invariant failed: Attempting to move frozen funds for "
859 << tx.getTransactionID();
860 XRPL_ASSERT(
861 enforce,
862 "ripple::TransfersNotFrozen::validateFrozenState : enforce "
863 "invariant.");
864
865 if (enforce)
866 {
867 return false;
868 }
869
870 return true;
871}
872
873//------------------------------------------------------------------------------
874
875void
877 bool,
878 std::shared_ptr<SLE const> const& before,
880{
881 if (!before && after->getType() == ltACCOUNT_ROOT)
882 {
884 accountSeq_ = (*after)[sfSequence];
885 }
886}
887
888bool
890 STTx const& tx,
891 TER const result,
892 XRPAmount const,
893 ReadView const& view,
894 beast::Journal const& j)
895{
896 if (accountsCreated_ == 0)
897 return true;
898
899 if (accountsCreated_ > 1)
900 {
901 JLOG(j.fatal()) << "Invariant failed: multiple accounts "
902 "created in a single transaction";
903 return false;
904 }
905
906 // From this point on we know exactly one account was created.
907 if ((tx.getTxnType() == ttPAYMENT || tx.getTxnType() == ttAMM_CREATE ||
908 tx.getTxnType() == ttXCHAIN_ADD_CLAIM_ATTESTATION ||
909 tx.getTxnType() == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) &&
910 result == tesSUCCESS)
911 {
912 std::uint32_t const startingSeq{
913 view.rules().enabled(featureDeletableAccounts) ? view.seq() : 1};
914
915 if (accountSeq_ != startingSeq)
916 {
917 JLOG(j.fatal()) << "Invariant failed: account created with "
918 "wrong starting sequence number";
919 return false;
920 }
921 return true;
922 }
923
924 JLOG(j.fatal()) << "Invariant failed: account root created "
925 "by a non-Payment, by an unsuccessful transaction, "
926 "or by AMM";
927 return false;
928}
929
930//------------------------------------------------------------------------------
931
932void
934 bool isDelete,
935 std::shared_ptr<SLE const> const& before,
937{
938 static constexpr uint256 const& pageBits = nft::pageMask;
939 static constexpr uint256 const accountBits = ~pageBits;
940
941 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
942 (after && after->getType() != ltNFTOKEN_PAGE))
943 return;
944
945 auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
946 uint256 const account = sle->key() & accountBits;
947 uint256 const hiLimit = sle->key() & pageBits;
948 std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
949
950 // Make sure that any page links...
951 // 1. Are properly associated with the owning account and
952 // 2. The page is correctly ordered between links.
953 if (prev)
954 {
955 if (account != (*prev & accountBits))
956 badLink_ = true;
957
958 if (hiLimit <= (*prev & pageBits))
959 badLink_ = true;
960 }
961
962 if (auto const next = (*sle)[~sfNextPageMin])
963 {
964 if (account != (*next & accountBits))
965 badLink_ = true;
966
967 if (hiLimit >= (*next & pageBits))
968 badLink_ = true;
969 }
970
971 {
972 auto const& nftokens = sle->getFieldArray(sfNFTokens);
973
974 // An NFTokenPage should never contain too many tokens or be empty.
975 if (std::size_t const nftokenCount = nftokens.size();
976 (!isDelete && nftokenCount == 0) ||
977 nftokenCount > dirMaxTokensPerPage)
978 invalidSize_ = true;
979
980 // If prev is valid, use it to establish a lower bound for
981 // page entries. If prev is not valid the lower bound is zero.
982 uint256 const loLimit =
983 prev ? *prev & pageBits : uint256(beast::zero);
984
985 // Also verify that all NFTokenIDs in the page are sorted.
986 uint256 loCmp = loLimit;
987 for (auto const& obj : nftokens)
988 {
989 uint256 const tokenID = obj[sfNFTokenID];
990 if (!nft::compareTokens(loCmp, tokenID))
991 badSort_ = true;
992 loCmp = tokenID;
993
994 // None of the NFTs on this page should belong on lower or
995 // higher pages.
996 if (uint256 const tokenPageBits = tokenID & pageBits;
997 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
998 badEntry_ = true;
999
1000 if (auto uri = obj[~sfURI]; uri && uri->empty())
1001 badURI_ = true;
1002 }
1003 }
1004 };
1005
1006 if (before)
1007 {
1008 check(before);
1009
1010 // While an account's NFToken directory contains any NFTokens, the last
1011 // NFTokenPage (with 96 bits of 1 in the low part of the index) should
1012 // never be deleted.
1013 if (isDelete && (before->key() & nft::pageMask) == nft::pageMask &&
1014 before->isFieldPresent(sfPreviousPageMin))
1015 {
1016 deletedFinalPage_ = true;
1017 }
1018 }
1019
1020 if (after)
1021 check(after);
1022
1023 if (!isDelete && before && after)
1024 {
1025 // If the NFTokenPage
1026 // 1. Has a NextMinPage field in before, but loses it in after, and
1027 // 2. This is not the last page in the directory
1028 // Then we have identified a corruption in the links between the
1029 // NFToken pages in the NFToken directory.
1030 if ((before->key() & nft::pageMask) != nft::pageMask &&
1031 before->isFieldPresent(sfNextPageMin) &&
1032 !after->isFieldPresent(sfNextPageMin))
1033 {
1034 deletedLink_ = true;
1035 }
1036 }
1037}
1038
1039bool
1041 STTx const& tx,
1042 TER const result,
1043 XRPAmount const,
1044 ReadView const& view,
1045 beast::Journal const& j)
1046{
1047 if (badLink_)
1048 {
1049 JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
1050 return false;
1051 }
1052
1053 if (badEntry_)
1054 {
1055 JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
1056 return false;
1057 }
1058
1059 if (badSort_)
1060 {
1061 JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
1062 return false;
1063 }
1064
1065 if (badURI_)
1066 {
1067 JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
1068 return false;
1069 }
1070
1071 if (invalidSize_)
1072 {
1073 JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
1074 return false;
1075 }
1076
1077 if (view.rules().enabled(fixNFTokenPageLinks))
1078 {
1080 {
1081 JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with "
1082 "non-empty directory.";
1083 return false;
1084 }
1085 if (deletedLink_)
1086 {
1087 JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link.";
1088 return false;
1089 }
1090 }
1091
1092 return true;
1093}
1094
1095//------------------------------------------------------------------------------
1096void
1098 bool,
1099 std::shared_ptr<SLE const> const& before,
1101{
1102 if (before && before->getType() == ltACCOUNT_ROOT)
1103 {
1104 beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0);
1105 beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
1106 }
1107
1108 if (after && after->getType() == ltACCOUNT_ROOT)
1109 {
1110 afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
1111 afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
1112 }
1113}
1114
1115bool
1117 STTx const& tx,
1118 TER const result,
1119 XRPAmount const,
1120 ReadView const& view,
1121 beast::Journal const& j)
1122{
1123 if (TxType const txType = tx.getTxnType();
1124 txType != ttNFTOKEN_MINT && txType != ttNFTOKEN_BURN)
1125 {
1127 {
1128 JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
1129 "changed without a mint transaction!";
1130 return false;
1131 }
1132
1134 {
1135 JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
1136 "changed without a burn transaction!";
1137 return false;
1138 }
1139
1140 return true;
1141 }
1142
1143 if (tx.getTxnType() == ttNFTOKEN_MINT)
1144 {
1145 if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal)
1146 {
1147 JLOG(j.fatal())
1148 << "Invariant failed: successful minting didn't increase "
1149 "the number of minted tokens.";
1150 return false;
1151 }
1152
1153 if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal)
1154 {
1155 JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
1156 "number of minted tokens.";
1157 return false;
1158 }
1159
1161 {
1162 JLOG(j.fatal())
1163 << "Invariant failed: minting changed the number of "
1164 "burned tokens.";
1165 return false;
1166 }
1167 }
1168
1169 if (tx.getTxnType() == ttNFTOKEN_BURN)
1170 {
1171 if (result == tesSUCCESS)
1172 {
1174 {
1175 JLOG(j.fatal())
1176 << "Invariant failed: successful burning didn't increase "
1177 "the number of burned tokens.";
1178 return false;
1179 }
1180 }
1181
1182 if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal)
1183 {
1184 JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
1185 "number of burned tokens.";
1186 return false;
1187 }
1188
1190 {
1191 JLOG(j.fatal())
1192 << "Invariant failed: burning changed the number of "
1193 "minted tokens.";
1194 return false;
1195 }
1196 }
1197
1198 return true;
1199}
1200
1201//------------------------------------------------------------------------------
1202
1203void
1205 bool,
1206 std::shared_ptr<SLE const> const& before,
1208{
1209 if (before && before->getType() == ltRIPPLE_STATE)
1211
1212 if (before && before->getType() == ltMPTOKEN)
1214}
1215
1216bool
1218 STTx const& tx,
1219 TER const result,
1220 XRPAmount const,
1221 ReadView const& view,
1222 beast::Journal const& j)
1223{
1224 if (tx.getTxnType() != ttCLAWBACK)
1225 return true;
1226
1227 if (result == tesSUCCESS)
1228 {
1229 if (trustlinesChanged > 1)
1230 {
1231 JLOG(j.fatal())
1232 << "Invariant failed: more than one trustline changed.";
1233 return false;
1234 }
1235
1236 if (mptokensChanged > 1)
1237 {
1238 JLOG(j.fatal())
1239 << "Invariant failed: more than one mptokens changed.";
1240 return false;
1241 }
1242
1243 if (trustlinesChanged == 1)
1244 {
1245 AccountID const issuer = tx.getAccountID(sfAccount);
1246 STAmount const& amount = tx.getFieldAmount(sfAmount);
1247 AccountID const& holder = amount.getIssuer();
1248 STAmount const holderBalance = accountHolds(
1249 view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
1250
1251 if (holderBalance.signum() < 0)
1252 {
1253 JLOG(j.fatal())
1254 << "Invariant failed: trustline balance is negative";
1255 return false;
1256 }
1257 }
1258 }
1259 else
1260 {
1261 if (trustlinesChanged != 0)
1262 {
1263 JLOG(j.fatal()) << "Invariant failed: some trustlines were changed "
1264 "despite failure of the transaction.";
1265 return false;
1266 }
1267
1268 if (mptokensChanged != 0)
1269 {
1270 JLOG(j.fatal()) << "Invariant failed: some mptokens were changed "
1271 "despite failure of the transaction.";
1272 return false;
1273 }
1274 }
1275
1276 return true;
1277}
1278
1279//------------------------------------------------------------------------------
1280
1281void
1283 bool isDelete,
1284 std::shared_ptr<SLE const> const& before,
1286{
1287 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
1288 {
1289 if (isDelete)
1291 else if (!before)
1293 }
1294
1295 if (after && after->getType() == ltMPTOKEN)
1296 {
1297 if (isDelete)
1299 else if (!before)
1301 }
1302}
1303
1304bool
1306 STTx const& tx,
1307 TER const result,
1308 XRPAmount const _fee,
1309 ReadView const& _view,
1310 beast::Journal const& j)
1311{
1312 if (result == tesSUCCESS)
1313 {
1314 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_CREATE)
1315 {
1316 if (mptIssuancesCreated_ == 0)
1317 {
1318 JLOG(j.fatal()) << "Invariant failed: MPT issuance creation "
1319 "succeeded without creating a MPT issuance";
1320 }
1321 else if (mptIssuancesDeleted_ != 0)
1322 {
1323 JLOG(j.fatal()) << "Invariant failed: MPT issuance creation "
1324 "succeeded while removing MPT issuances";
1325 }
1326 else if (mptIssuancesCreated_ > 1)
1327 {
1328 JLOG(j.fatal()) << "Invariant failed: MPT issuance creation "
1329 "succeeded but created multiple issuances";
1330 }
1331
1332 return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0;
1333 }
1334
1335 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_DESTROY)
1336 {
1337 if (mptIssuancesDeleted_ == 0)
1338 {
1339 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1340 "succeeded without removing a MPT issuance";
1341 }
1342 else if (mptIssuancesCreated_ > 0)
1343 {
1344 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1345 "succeeded while creating MPT issuances";
1346 }
1347 else if (mptIssuancesDeleted_ > 1)
1348 {
1349 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1350 "succeeded but deleted multiple issuances";
1351 }
1352
1353 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1;
1354 }
1355
1356 if (tx.getTxnType() == ttMPTOKEN_AUTHORIZE)
1357 {
1358 bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
1359
1360 if (mptIssuancesCreated_ > 0)
1361 {
1362 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1363 "succeeded but created MPT issuances";
1364 return false;
1365 }
1366 else if (mptIssuancesDeleted_ > 0)
1367 {
1368 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1369 "succeeded but deleted issuances";
1370 return false;
1371 }
1372 else if (
1373 submittedByIssuer &&
1375 {
1376 JLOG(j.fatal())
1377 << "Invariant failed: MPT authorize submitted by issuer "
1378 "succeeded but created/deleted mptokens";
1379 return false;
1380 }
1381 else if (
1382 !submittedByIssuer &&
1384 {
1385 // if the holder submitted this tx, then a mptoken must be
1386 // either created or deleted.
1387 JLOG(j.fatal())
1388 << "Invariant failed: MPT authorize submitted by holder "
1389 "succeeded but created/deleted bad number of mptokens";
1390 return false;
1391 }
1392
1393 return true;
1394 }
1395
1396 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_SET)
1397 {
1398 if (mptIssuancesDeleted_ > 0)
1399 {
1400 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1401 "succeeded while removing MPT issuances";
1402 }
1403 else if (mptIssuancesCreated_ > 0)
1404 {
1405 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1406 "succeeded while creating MPT issuances";
1407 }
1408 else if (mptokensDeleted_ > 0)
1409 {
1410 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1411 "succeeded while removing MPTokens";
1412 }
1413 else if (mptokensCreated_ > 0)
1414 {
1415 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1416 "succeeded while creating MPTokens";
1417 }
1418
1419 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
1421 }
1422 }
1423
1424 if (mptIssuancesCreated_ != 0)
1425 {
1426 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created";
1427 }
1428 else if (mptIssuancesDeleted_ != 0)
1429 {
1430 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted";
1431 }
1432 else if (mptokensCreated_ != 0)
1433 {
1434 JLOG(j.fatal()) << "Invariant failed: a MPToken was created";
1435 }
1436 else if (mptokensDeleted_ != 0)
1437 {
1438 JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted";
1439 }
1440
1441 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
1443}
1444
1445//------------------------------------------------------------------------------
1446
1447void
1449 bool,
1450 std::shared_ptr<SLE const> const& before,
1452{
1453 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
1454 return;
1455 if (after && after->getType() != ltPERMISSIONED_DOMAIN)
1456 return;
1457
1458 auto check = [](SleStatus& sleStatus,
1459 std::shared_ptr<SLE const> const& sle) {
1460 auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
1461 sleStatus.credentialsSize_ = credentials.size();
1462 auto const sorted = credentials::makeSorted(credentials);
1463 sleStatus.isUnique_ = !sorted.empty();
1464
1465 // If array have duplicates then all the other checks are invalid
1466 sleStatus.isSorted_ = false;
1467
1468 if (sleStatus.isUnique_)
1469 {
1470 unsigned i = 0;
1471 for (auto const& cred : sorted)
1472 {
1473 auto const& credTx = credentials[i++];
1474 sleStatus.isSorted_ = (cred.first == credTx[sfIssuer]) &&
1475 (cred.second == credTx[sfCredentialType]);
1476 if (!sleStatus.isSorted_)
1477 break;
1478 }
1479 }
1480 };
1481
1482 if (before)
1483 {
1484 sleStatus_[0] = SleStatus();
1485 check(*sleStatus_[0], after);
1486 }
1487
1488 if (after)
1489 {
1490 sleStatus_[1] = SleStatus();
1491 check(*sleStatus_[1], after);
1492 }
1493}
1494
1495bool
1497 STTx const& tx,
1498 TER const result,
1499 XRPAmount const,
1500 ReadView const& view,
1501 beast::Journal const& j)
1502{
1503 if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS)
1504 return true;
1505
1506 auto check = [](SleStatus const& sleStatus, beast::Journal const& j) {
1507 if (!sleStatus.credentialsSize_)
1508 {
1509 JLOG(j.fatal()) << "Invariant failed: permissioned domain with "
1510 "no rules.";
1511 return false;
1512 }
1513
1514 if (sleStatus.credentialsSize_ >
1516 {
1517 JLOG(j.fatal()) << "Invariant failed: permissioned domain bad "
1518 "credentials size "
1519 << sleStatus.credentialsSize_;
1520 return false;
1521 }
1522
1523 if (!sleStatus.isUnique_)
1524 {
1525 JLOG(j.fatal())
1526 << "Invariant failed: permissioned domain credentials "
1527 "aren't unique";
1528 return false;
1529 }
1530
1531 if (!sleStatus.isSorted_)
1532 {
1533 JLOG(j.fatal())
1534 << "Invariant failed: permissioned domain credentials "
1535 "aren't sorted";
1536 return false;
1537 }
1538
1539 return true;
1540 };
1541
1542 return (sleStatus_[0] ? check(*sleStatus_[0], j) : true) &&
1543 (sleStatus_[1] ? check(*sleStatus_[1], j) : true);
1544}
1545
1546} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:59
Stream fatal() const
Definition: Journal.h:341
Stream debug() const
Definition: Journal.h:317
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_
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 &)
A currency issued by an account.
Definition: Issue.h:36
Item const * findByType(KeyType type) const
Retrieve a format based on its type.
Definition: KnownFormats.h:127
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 &)
static LedgerFormats const & getInstance()
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 &)
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 &)
A view into a ledger.
Definition: ReadView.h:55
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.
Definition: ReadView.h:122
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:122
Currency const & getCurrency() const
Definition: STAmount.h:493
XRPAmount xrp() const
Definition: STAmount.cpp:273
int signum() const noexcept
Definition: STAmount.h:505
AccountID const & getIssuer() const
Definition: STAmount.h:499
bool native() const noexcept
Definition: STAmount.h:449
STAmount zeroed() const
Returns a zero value with the same issuer and currency.
Definition: STAmount.h:511
AccountID getAccountID(SField const &field) const
Definition: STObject.cpp:621
STAmount const & getFieldAmount(SField const &field) const
Definition: STObject.cpp:635
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:454
TxType getTxnType() const
Definition: STTx.h:181
uint256 getTransactionID() const
Definition: STTx.h:193
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::map< AccountID, std::shared_ptr< SLE const > const > possibleIssuers_
bool isValidEntry(std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after)
void recordBalance(Issue const &issue, BalanceChange change)
std::shared_ptr< SLE const > findIssuer(AccountID const &issuerID, ReadView const &view)
bool validateIssuerChanges(std::shared_ptr< SLE const > const &issuer, IssuerChanges const &changes, STTx const &tx, beast::Journal const &j, bool enforce)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
STAmount calculateBalanceChange(std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after, bool isDelete)
void recordBalanceChanges(std::shared_ptr< SLE const > const &after, STAmount const &balanceChange)
bool validateFrozenState(BalanceChange const &change, bool high, STTx const &tx, beast::Journal const &j, bool enforce, bool globalFreeze)
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::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 &)
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 &)
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.
Definition: XRPAmount.h:177
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 &)
base_uint next() const
Definition: base_uint.h:454
T invoke(T... args)
std::set< std::pair< AccountID, Slice > > makeSorted(STArray const &credentials)
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition: Indexes.cpp:422
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:160
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition: Indexes.cpp:379
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition: Indexes.cpp:387
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.
Definition: algorithm.h:26
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:118
@ fhIGNORE_FREEZE
Definition: View.h:80
TxType
Transaction type identifiers.
Definition: TxFormats.h:57
base_uint< 256 > uint256
Definition: base_uint.h:557
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition: Protocol.h:110
@ lsfHighDeepFreeze
@ lsfHighFreeze
@ lsfGlobalFreeze
@ lsfLowFreeze
@ lsfLowDeepFreeze
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
std::size_t constexpr dirMaxTokensPerPage
The maximum number of items in an NFT page.
Definition: Protocol.h:62
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
Definition: Indexes.h:368
@ tesSUCCESS
Definition: TER.h:242
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition: View.cpp:308
static bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: Escrow.cpp:89
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:39
uint256 key
Definition: Keylet.h:40
std::shared_ptr< SLE const > const line
std::vector< BalanceChange > receivers
std::vector< BalanceChange > senders
T to_string(T... args)