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,
562 std::shared_ptr<SLE const> const& before,
564{
565 if (!before && after->getType() == ltACCOUNT_ROOT)
566 {
568 accountSeq_ = (*after)[sfSequence];
569 }
570}
571
572bool
574 STTx const& tx,
575 TER const result,
576 XRPAmount const,
577 ReadView const& view,
578 beast::Journal const& j)
579{
580 if (accountsCreated_ == 0)
581 return true;
582
583 if (accountsCreated_ > 1)
584 {
585 JLOG(j.fatal()) << "Invariant failed: multiple accounts "
586 "created in a single transaction";
587 return false;
588 }
589
590 // From this point on we know exactly one account was created.
591 if ((tx.getTxnType() == ttPAYMENT || tx.getTxnType() == ttAMM_CREATE ||
592 tx.getTxnType() == ttXCHAIN_ADD_CLAIM_ATTESTATION ||
593 tx.getTxnType() == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) &&
594 result == tesSUCCESS)
595 {
596 std::uint32_t const startingSeq{
597 view.rules().enabled(featureDeletableAccounts) ? view.seq() : 1};
598
599 if (accountSeq_ != startingSeq)
600 {
601 JLOG(j.fatal()) << "Invariant failed: account created with "
602 "wrong starting sequence number";
603 return false;
604 }
605 return true;
606 }
607
608 JLOG(j.fatal()) << "Invariant failed: account root created "
609 "by a non-Payment, by an unsuccessful transaction, "
610 "or by AMM";
611 return false;
612}
613
614//------------------------------------------------------------------------------
615
616void
618 bool isDelete,
619 std::shared_ptr<SLE const> const& before,
621{
622 static constexpr uint256 const& pageBits = nft::pageMask;
623 static constexpr uint256 const accountBits = ~pageBits;
624
625 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
626 (after && after->getType() != ltNFTOKEN_PAGE))
627 return;
628
629 auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
630 uint256 const account = sle->key() & accountBits;
631 uint256 const hiLimit = sle->key() & pageBits;
632 std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
633
634 // Make sure that any page links...
635 // 1. Are properly associated with the owning account and
636 // 2. The page is correctly ordered between links.
637 if (prev)
638 {
639 if (account != (*prev & accountBits))
640 badLink_ = true;
641
642 if (hiLimit <= (*prev & pageBits))
643 badLink_ = true;
644 }
645
646 if (auto const next = (*sle)[~sfNextPageMin])
647 {
648 if (account != (*next & accountBits))
649 badLink_ = true;
650
651 if (hiLimit >= (*next & pageBits))
652 badLink_ = true;
653 }
654
655 {
656 auto const& nftokens = sle->getFieldArray(sfNFTokens);
657
658 // An NFTokenPage should never contain too many tokens or be empty.
659 if (std::size_t const nftokenCount = nftokens.size();
660 (!isDelete && nftokenCount == 0) ||
661 nftokenCount > dirMaxTokensPerPage)
662 invalidSize_ = true;
663
664 // If prev is valid, use it to establish a lower bound for
665 // page entries. If prev is not valid the lower bound is zero.
666 uint256 const loLimit =
667 prev ? *prev & pageBits : uint256(beast::zero);
668
669 // Also verify that all NFTokenIDs in the page are sorted.
670 uint256 loCmp = loLimit;
671 for (auto const& obj : nftokens)
672 {
673 uint256 const tokenID = obj[sfNFTokenID];
674 if (!nft::compareTokens(loCmp, tokenID))
675 badSort_ = true;
676 loCmp = tokenID;
677
678 // None of the NFTs on this page should belong on lower or
679 // higher pages.
680 if (uint256 const tokenPageBits = tokenID & pageBits;
681 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
682 badEntry_ = true;
683
684 if (auto uri = obj[~sfURI]; uri && uri->empty())
685 badURI_ = true;
686 }
687 }
688 };
689
690 if (before)
691 {
692 check(before);
693
694 // While an account's NFToken directory contains any NFTokens, the last
695 // NFTokenPage (with 96 bits of 1 in the low part of the index) should
696 // never be deleted.
697 if (isDelete && (before->key() & nft::pageMask) == nft::pageMask &&
698 before->isFieldPresent(sfPreviousPageMin))
699 {
700 deletedFinalPage_ = true;
701 }
702 }
703
704 if (after)
705 check(after);
706
707 if (!isDelete && before && after)
708 {
709 // If the NFTokenPage
710 // 1. Has a NextMinPage field in before, but loses it in after, and
711 // 2. This is not the last page in the directory
712 // Then we have identified a corruption in the links between the
713 // NFToken pages in the NFToken directory.
714 if ((before->key() & nft::pageMask) != nft::pageMask &&
715 before->isFieldPresent(sfNextPageMin) &&
716 !after->isFieldPresent(sfNextPageMin))
717 {
718 deletedLink_ = true;
719 }
720 }
721}
722
723bool
725 STTx const& tx,
726 TER const result,
727 XRPAmount const,
728 ReadView const& view,
729 beast::Journal const& j)
730{
731 if (badLink_)
732 {
733 JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
734 return false;
735 }
736
737 if (badEntry_)
738 {
739 JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
740 return false;
741 }
742
743 if (badSort_)
744 {
745 JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
746 return false;
747 }
748
749 if (badURI_)
750 {
751 JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
752 return false;
753 }
754
755 if (invalidSize_)
756 {
757 JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
758 return false;
759 }
760
761 if (view.rules().enabled(fixNFTokenPageLinks))
762 {
764 {
765 JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with "
766 "non-empty directory.";
767 return false;
768 }
769 if (deletedLink_)
770 {
771 JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link.";
772 return false;
773 }
774 }
775
776 return true;
777}
778
779//------------------------------------------------------------------------------
780void
782 bool,
783 std::shared_ptr<SLE const> const& before,
785{
786 if (before && before->getType() == ltACCOUNT_ROOT)
787 {
788 beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0);
789 beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
790 }
791
792 if (after && after->getType() == ltACCOUNT_ROOT)
793 {
794 afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
795 afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
796 }
797}
798
799bool
801 STTx const& tx,
802 TER const result,
803 XRPAmount const,
804 ReadView const& view,
805 beast::Journal const& j)
806{
807 if (TxType const txType = tx.getTxnType();
808 txType != ttNFTOKEN_MINT && txType != ttNFTOKEN_BURN)
809 {
811 {
812 JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
813 "changed without a mint transaction!";
814 return false;
815 }
816
818 {
819 JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
820 "changed without a burn transaction!";
821 return false;
822 }
823
824 return true;
825 }
826
827 if (tx.getTxnType() == ttNFTOKEN_MINT)
828 {
830 {
831 JLOG(j.fatal())
832 << "Invariant failed: successful minting didn't increase "
833 "the number of minted tokens.";
834 return false;
835 }
836
838 {
839 JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
840 "number of minted tokens.";
841 return false;
842 }
843
845 {
846 JLOG(j.fatal())
847 << "Invariant failed: minting changed the number of "
848 "burned tokens.";
849 return false;
850 }
851 }
852
853 if (tx.getTxnType() == ttNFTOKEN_BURN)
854 {
855 if (result == tesSUCCESS)
856 {
858 {
859 JLOG(j.fatal())
860 << "Invariant failed: successful burning didn't increase "
861 "the number of burned tokens.";
862 return false;
863 }
864 }
865
867 {
868 JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
869 "number of burned tokens.";
870 return false;
871 }
872
874 {
875 JLOG(j.fatal())
876 << "Invariant failed: burning changed the number of "
877 "minted tokens.";
878 return false;
879 }
880 }
881
882 return true;
883}
884
885//------------------------------------------------------------------------------
886
887void
889 bool,
890 std::shared_ptr<SLE const> const& before,
892{
893 if (before && before->getType() == ltRIPPLE_STATE)
895
896 if (before && before->getType() == ltMPTOKEN)
898}
899
900bool
902 STTx const& tx,
903 TER const result,
904 XRPAmount const,
905 ReadView const& view,
906 beast::Journal const& j)
907{
908 if (tx.getTxnType() != ttCLAWBACK)
909 return true;
910
911 if (result == tesSUCCESS)
912 {
913 if (trustlinesChanged > 1)
914 {
915 JLOG(j.fatal())
916 << "Invariant failed: more than one trustline changed.";
917 return false;
918 }
919
920 if (mptokensChanged > 1)
921 {
922 JLOG(j.fatal())
923 << "Invariant failed: more than one mptokens changed.";
924 return false;
925 }
926
927 if (trustlinesChanged == 1)
928 {
929 AccountID const issuer = tx.getAccountID(sfAccount);
930 STAmount const& amount = tx.getFieldAmount(sfAmount);
931 AccountID const& holder = amount.getIssuer();
932 STAmount const holderBalance = accountHolds(
933 view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
934
935 if (holderBalance.signum() < 0)
936 {
937 JLOG(j.fatal())
938 << "Invariant failed: trustline balance is negative";
939 return false;
940 }
941 }
942 }
943 else
944 {
945 if (trustlinesChanged != 0)
946 {
947 JLOG(j.fatal()) << "Invariant failed: some trustlines were changed "
948 "despite failure of the transaction.";
949 return false;
950 }
951
952 if (mptokensChanged != 0)
953 {
954 JLOG(j.fatal()) << "Invariant failed: some mptokens were changed "
955 "despite failure of the transaction.";
956 return false;
957 }
958 }
959
960 return true;
961}
962
963//------------------------------------------------------------------------------
964
965void
967 bool isDelete,
968 std::shared_ptr<SLE const> const& before,
970{
971 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
972 {
973 if (isDelete)
975 else if (!before)
977 }
978
979 if (after && after->getType() == ltMPTOKEN)
980 {
981 if (isDelete)
983 else if (!before)
985 }
986}
987
988bool
990 STTx const& tx,
991 TER const result,
992 XRPAmount const _fee,
993 ReadView const& _view,
994 beast::Journal const& j)
995{
996 if (result == tesSUCCESS)
997 {
998 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_CREATE)
999 {
1000 if (mptIssuancesCreated_ == 0)
1001 {
1002 JLOG(j.fatal()) << "Invariant failed: MPT issuance creation "
1003 "succeeded without creating a MPT issuance";
1004 }
1005 else if (mptIssuancesDeleted_ != 0)
1006 {
1007 JLOG(j.fatal()) << "Invariant failed: MPT issuance creation "
1008 "succeeded while removing MPT issuances";
1009 }
1010 else if (mptIssuancesCreated_ > 1)
1011 {
1012 JLOG(j.fatal()) << "Invariant failed: MPT issuance creation "
1013 "succeeded but created multiple issuances";
1014 }
1015
1016 return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0;
1017 }
1018
1019 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_DESTROY)
1020 {
1021 if (mptIssuancesDeleted_ == 0)
1022 {
1023 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1024 "succeeded without removing a MPT issuance";
1025 }
1026 else if (mptIssuancesCreated_ > 0)
1027 {
1028 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1029 "succeeded while creating MPT issuances";
1030 }
1031 else if (mptIssuancesDeleted_ > 1)
1032 {
1033 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
1034 "succeeded but deleted multiple issuances";
1035 }
1036
1037 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1;
1038 }
1039
1040 if (tx.getTxnType() == ttMPTOKEN_AUTHORIZE)
1041 {
1042 bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
1043
1044 if (mptIssuancesCreated_ > 0)
1045 {
1046 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1047 "succeeded but created MPT issuances";
1048 return false;
1049 }
1050 else if (mptIssuancesDeleted_ > 0)
1051 {
1052 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
1053 "succeeded but deleted issuances";
1054 return false;
1055 }
1056 else if (
1057 submittedByIssuer &&
1059 {
1060 JLOG(j.fatal())
1061 << "Invariant failed: MPT authorize submitted by issuer "
1062 "succeeded but created/deleted mptokens";
1063 return false;
1064 }
1065 else if (
1066 !submittedByIssuer &&
1068 {
1069 // if the holder submitted this tx, then a mptoken must be
1070 // either created or deleted.
1071 JLOG(j.fatal())
1072 << "Invariant failed: MPT authorize submitted by holder "
1073 "succeeded but created/deleted bad number of mptokens";
1074 return false;
1075 }
1076
1077 return true;
1078 }
1079
1080 if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_SET)
1081 {
1082 if (mptIssuancesDeleted_ > 0)
1083 {
1084 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1085 "succeeded while removing MPT issuances";
1086 }
1087 else if (mptIssuancesCreated_ > 0)
1088 {
1089 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1090 "succeeded while creating MPT issuances";
1091 }
1092 else if (mptokensDeleted_ > 0)
1093 {
1094 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1095 "succeeded while removing MPTokens";
1096 }
1097 else if (mptokensCreated_ > 0)
1098 {
1099 JLOG(j.fatal()) << "Invariant failed: MPT issuance set "
1100 "succeeded while creating MPTokens";
1101 }
1102
1103 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
1105 }
1106 }
1107
1108 if (mptIssuancesCreated_ != 0)
1109 {
1110 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created";
1111 }
1112 else if (mptIssuancesDeleted_ != 0)
1113 {
1114 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted";
1115 }
1116 else if (mptokensCreated_ != 0)
1117 {
1118 JLOG(j.fatal()) << "Invariant failed: a MPToken was created";
1119 }
1120 else if (mptokensDeleted_ != 0)
1121 {
1122 JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted";
1123 }
1124
1125 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
1127}
1128
1129//------------------------------------------------------------------------------
1130
1131void
1133 bool,
1134 std::shared_ptr<SLE const> const& before,
1136{
1137 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
1138 return;
1139 if (after && after->getType() != ltPERMISSIONED_DOMAIN)
1140 return;
1141
1142 auto check = [](SleStatus& sleStatus,
1143 std::shared_ptr<SLE const> const& sle) {
1144 auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
1145 sleStatus.credentialsSize_ = credentials.size();
1146 auto const sorted = credentials::makeSorted(credentials);
1147 sleStatus.isUnique_ = !sorted.empty();
1148
1149 // If array have duplicates then all the other checks are invalid
1150 sleStatus.isSorted_ = false;
1151
1152 if (sleStatus.isUnique_)
1153 {
1154 unsigned i = 0;
1155 for (auto const& cred : sorted)
1156 {
1157 auto const& credTx = credentials[i++];
1158 sleStatus.isSorted_ = (cred.first == credTx[sfIssuer]) &&
1159 (cred.second == credTx[sfCredentialType]);
1160 if (!sleStatus.isSorted_)
1161 break;
1162 }
1163 }
1164 };
1165
1166 if (before)
1167 {
1168 sleStatus_[0] = SleStatus();
1169 check(*sleStatus_[0], after);
1170 }
1171
1172 if (after)
1173 {
1174 sleStatus_[1] = SleStatus();
1175 check(*sleStatus_[1], after);
1176 }
1177}
1178
1179bool
1181 STTx const& tx,
1182 TER const result,
1183 XRPAmount const,
1184 ReadView const& view,
1185 beast::Journal const& j)
1186{
1187 if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS)
1188 return true;
1189
1190 auto check = [](SleStatus const& sleStatus, beast::Journal const& j) {
1191 if (!sleStatus.credentialsSize_)
1192 {
1193 JLOG(j.fatal()) << "Invariant failed: permissioned domain with "
1194 "no rules.";
1195 return false;
1196 }
1197
1198 if (sleStatus.credentialsSize_ >
1200 {
1201 JLOG(j.fatal()) << "Invariant failed: permissioned domain bad "
1202 "credentials size "
1203 << sleStatus.credentialsSize_;
1204 return false;
1205 }
1206
1207 if (!sleStatus.isUnique_)
1208 {
1209 JLOG(j.fatal())
1210 << "Invariant failed: permissioned domain credentials "
1211 "aren't unique";
1212 return false;
1213 }
1214
1215 if (!sleStatus.isSorted_)
1216 {
1217 JLOG(j.fatal())
1218 << "Invariant failed: permissioned domain credentials "
1219 "aren't sorted";
1220 return false;
1221 }
1222
1223 return true;
1224 };
1225
1226 return (sleStatus_[0] ? check(*sleStatus_[0], j) : true) &&
1227 (sleStatus_[1] ? check(*sleStatus_[1], j) : true);
1228}
1229
1230} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:59
Stream fatal() const
Definition: Journal.h:341
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 &)
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 &)
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
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
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 &)
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 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
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:271
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
T to_string(T... args)