xrpld
Loading...
Searching...
No Matches
InvariantCheck.cpp
1#include <xrpl/tx/invariants/InvariantCheck.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/base_uint.h>
5#include <xrpl/beast/utility/Journal.h>
6#include <xrpl/beast/utility/Zero.h>
7#include <xrpl/beast/utility/instrumentation.h>
8#include <xrpl/ledger/ReadView.h>
9#include <xrpl/ledger/helpers/AccountRootHelpers.h>
10#include <xrpl/ledger/helpers/TokenHelpers.h>
11#include <xrpl/protocol/AccountID.h>
12#include <xrpl/protocol/Feature.h>
13#include <xrpl/protocol/Indexes.h>
14#include <xrpl/protocol/Issue.h>
15#include <xrpl/protocol/Keylet.h>
16#include <xrpl/protocol/LedgerFormats.h>
17#include <xrpl/protocol/MPTIssue.h>
18#include <xrpl/protocol/Protocol.h>
19#include <xrpl/protocol/Rules.h>
20#include <xrpl/protocol/SField.h>
21#include <xrpl/protocol/STAmount.h>
22#include <xrpl/protocol/STLedgerEntry.h>
23#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
24#include <xrpl/protocol/STTx.h>
25#include <xrpl/protocol/SystemParameters.h>
26#include <xrpl/protocol/TER.h>
27#include <xrpl/protocol/TxFormats.h>
28#include <xrpl/protocol/UintTypes.h>
29#include <xrpl/protocol/XRPAmount.h>
30#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
31
32#include <algorithm>
33#include <cstdint>
34#include <functional>
35#include <memory>
36#include <optional>
37#include <sstream>
38#include <vector>
39
40namespace xrpl {
41
42#pragma push_macro("TRANSACTION")
43#undef TRANSACTION
44
45#define TRANSACTION(tag, value, name, delegable, amendment, privileges, ...) \
46 case tag: { \
47 return (privileges) & priv; \
48 }
49
50bool
51hasPrivilege(STTx const& tx, Privilege priv)
52{
53 switch (tx.getTxnType())
54 {
55#include <xrpl/protocol/detail/transactions.macro>
56
57 // Deprecated types
58 default:
59 return false;
60 }
61};
62
63#undef TRANSACTION
64#pragma pop_macro("TRANSACTION")
65
66void
71
72bool
74 STTx const& tx,
75 TER const,
76 XRPAmount const fee,
77 ReadView const&,
78 beast::Journal const& j)
79{
80 // We should never charge a negative fee
81 if (fee.drops() < 0)
82 {
83 JLOG(j.fatal()) << "Invariant failed: fee paid was negative: " << fee.drops();
84 return false;
85 }
86
87 // We should never charge a fee that's greater than or equal to the
88 // entire XRP supply.
89 if (fee >= kInitialXrp)
90 {
91 JLOG(j.fatal()) << "Invariant failed: fee paid exceeds system limit: " << fee.drops();
92 return false;
93 }
94
95 // We should never charge more for a transaction than the transaction
96 // authorizes. It's possible to charge less in some circumstances.
97 if (fee > tx.getFieldAmount(sfFee).xrp())
98 {
99 JLOG(j.fatal()) << "Invariant failed: fee paid is " << fee.drops()
100 << " exceeds fee specified in transaction.";
101 return false;
102 }
103
104 return true;
105}
106
107//------------------------------------------------------------------------------
108
109void
111{
112 /* We go through all modified ledger entries, looking only at account roots,
113 * escrow payments, and payment channels. We remove from the total any
114 * previous XRP values and add to the total any new XRP values. The net
115 * balance of a payment channel is computed from two fields (amount and
116 * balance) and deletions are ignored for paychan and escrow because the
117 * amount fields have not been adjusted for those in the case of deletion.
118 */
119 if (before)
120 {
121 switch (before->getType())
122 {
123 case ltACCOUNT_ROOT:
124 drops_ -= (*before)[sfBalance].xrp().drops();
125 break;
126 case ltPAYCHAN:
127 drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
128 break;
129 case ltESCROW:
130 if (isXRP((*before)[sfAmount]))
131 drops_ -= (*before)[sfAmount].xrp().drops();
132 break;
133 default:
134 break;
135 }
136 }
137
138 if (after)
139 {
140 switch (after->getType())
141 {
142 case ltACCOUNT_ROOT:
143 drops_ += (*after)[sfBalance].xrp().drops();
144 break;
145 case ltPAYCHAN:
146 if (!isDelete)
147 drops_ += ((*after)[sfAmount] - (*after)[sfBalance]).xrp().drops();
148 break;
149 case ltESCROW:
150 if (!isDelete && isXRP((*after)[sfAmount]))
151 drops_ += (*after)[sfAmount].xrp().drops();
152 break;
153 default:
154 break;
155 }
156 }
157}
158
159bool
161 STTx const& tx,
162 TER const,
163 XRPAmount const fee,
164 ReadView const&,
165 beast::Journal const& j) const
166{
167 // The net change should never be positive, as this would mean that the
168 // transaction created XRP out of thin air. That's not possible.
169 if (drops_ > 0)
170 {
171 JLOG(j.fatal()) << "Invariant failed: XRP net change was positive: " << drops_;
172 return false;
173 }
174
175 // The negative of the net change should be equal to actual fee charged.
176 if (-drops_ != fee.drops())
177 {
178 JLOG(j.fatal()) << "Invariant failed: XRP net change of " << drops_ << " doesn't match fee "
179 << fee.drops();
180 return false;
181 }
182
183 return true;
184}
185
186//------------------------------------------------------------------------------
187
188void
190{
191 auto isBad = [](STAmount const& balance) {
192 if (!balance.native())
193 return true;
194
195 auto const drops = balance.xrp();
196
197 // Can't have more than the number of drops instantiated
198 // in the genesis ledger.
199 if (drops > kInitialXrp)
200 return true;
201
202 // Can't have a negative balance (0 is OK)
203 if (drops < XRPAmount{0})
204 return true;
205
206 return false;
207 };
208
209 if (before && before->getType() == ltACCOUNT_ROOT)
210 bad_ |= isBad((*before)[sfBalance]);
211
212 if (after && after->getType() == ltACCOUNT_ROOT)
213 bad_ |= isBad((*after)[sfBalance]);
214}
215
216bool
218 STTx const&,
219 TER const,
220 XRPAmount const,
221 ReadView const&,
222 beast::Journal const& j) const
223{
224 if (bad_)
225 {
226 JLOG(j.fatal()) << "Invariant failed: incorrect account XRP balance";
227 return false;
228 }
229
230 return true;
231}
232
233//------------------------------------------------------------------------------
234
235void
237{
238 auto isBad = [](STAmount const& pays, STAmount const& gets) {
239 // An offer should never be negative
240 if (pays < beast::kZero)
241 return true;
242
243 if (gets < beast::kZero)
244 return true;
245
246 // Can't have an XRP to XRP offer:
247 return pays.native() && gets.native();
248 };
249
250 if (before && before->getType() == ltOFFER)
251 bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
252
253 if (after && after->getType() == ltOFFER)
254 bad_ |= isBad((*after)[sfTakerPays], (*after)[sfTakerGets]);
255}
256
257bool
259 STTx const&,
260 TER const,
261 XRPAmount const,
262 ReadView const&,
263 beast::Journal const& j) const
264{
265 if (bad_)
266 {
267 JLOG(j.fatal()) << "Invariant failed: offer with a bad amount";
268 return false;
269 }
270
271 return true;
272}
273
274//------------------------------------------------------------------------------
275
276void
278{
279 auto isBad = [](STAmount const& amount) {
280 // XRP case
281 if (amount.native())
282 {
283 if (amount.xrp() <= XRPAmount{0})
284 return true;
285
286 if (amount.xrp() >= kInitialXrp)
287 return true;
288 }
289 else
290 {
291 return amount.asset().visit(
292 [&](Issue const& issue) {
293 // IOU case
294 if (amount <= beast::kZero)
295 return true;
296
297 if (badCurrency() == issue.currency)
298 return true;
299
300 return false;
301 }
302
303 // MPT case
304 ,
305 [&](MPTIssue const&) {
306 if (amount <= beast::kZero)
307 return true;
308
309 if (amount.mpt() > MPTAmount{kMaxMpTokenAmount})
310 return true; // LCOV_EXCL_LINE
311
312 return false;
313 });
314 }
315 return false;
316 };
317
318 if (before && before->getType() == ltESCROW)
319 bad_ |= isBad((*before)[sfAmount]);
320
321 if (after && after->getType() == ltESCROW)
322 bad_ |= isBad((*after)[sfAmount]);
323
324 auto checkAmount = [this](std::int64_t amount) {
325 if (amount > kMaxMpTokenAmount || amount < 0)
326 bad_ |= true;
327 };
328
329 bool const overwriteFixEnabled = isFeatureEnabled(fixCleanup3_1_3, true);
330
331 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
332 {
333 auto const outstanding = (*after)[sfOutstandingAmount];
334 checkAmount(outstanding);
335 if (auto const locked = (*after)[~sfLockedAmount])
336 {
337 checkAmount(*locked);
338 bool const isBad = outstanding < *locked;
339 if (overwriteFixEnabled)
340 {
341 bad_ |= isBad;
342 }
343 else
344 {
345 bad_ = isBad;
346 }
347 }
348 }
349
350 if (after && after->getType() == ltMPTOKEN)
351 {
352 auto const mptAmount = (*after)[sfMPTAmount];
353 checkAmount(mptAmount);
354 if (auto const locked = (*after)[~sfLockedAmount])
355 {
356 checkAmount(*locked);
357 }
358 }
359}
360
361bool
363 STTx const& txn,
364 TER const,
365 XRPAmount const,
366 ReadView const&,
367 beast::Journal const& j) const
368{
369 if (bad_)
370 {
371 JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount";
372 return false;
373 }
374
375 return true;
376}
377
378//------------------------------------------------------------------------------
379
380void
382{
383 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
385}
386
387bool
389 STTx const& tx,
390 TER const result,
391 XRPAmount const,
392 ReadView const&,
393 beast::Journal const& j) const
394{
395 // AMM account root can be deleted as the result of AMM withdraw/delete
396 // transaction when the total AMM LP Tokens balance goes to 0.
397 // A successful AccountDelete or AMMDelete MUST delete exactly
398 // one account root.
399 if (hasPrivilege(tx, MustDeleteAcct) && isTesSuccess(result))
400 {
401 if (accountsDeleted_ == 1)
402 return true;
403
404 if (accountsDeleted_ == 0)
405 {
406 JLOG(j.fatal()) << "Invariant failed: account deletion "
407 "succeeded without deleting an account";
408 }
409 else
410 {
411 JLOG(j.fatal()) << "Invariant failed: account deletion "
412 "succeeded but deleted multiple accounts!";
413 }
414 return false;
415 }
416
417 // A successful AMMWithdraw/AMMClawback MAY delete one account root
418 // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw
419 // deletes the AMM account, accountsDeleted_ is set if it is deleted.
420 if (hasPrivilege(tx, MayDeleteAcct) && isTesSuccess(result) && accountsDeleted_ == 1)
421 return true;
422
423 if (accountsDeleted_ == 0)
424 return true;
425
426 JLOG(j.fatal()) << "Invariant failed: an account root was deleted";
427 return false;
428}
429
430//------------------------------------------------------------------------------
431
432void
434{
435 if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
436 accountsDeleted_.emplace_back(before, after);
437}
438
439bool
441 STTx const& tx,
442 TER const result,
443 XRPAmount const,
444 ReadView const& view,
445 beast::Journal const& j)
446{
447 // Always check for objects in the ledger, but to prevent differing
448 // transaction processing results, however unlikely, only fail if the
449 // feature is enabled. Enabled, or not, though, a fatal-level message will
450 // be logged
451 [[maybe_unused]] bool const enforce = view.rules().enabled(fixCleanup3_2_0) ||
452 view.rules().enabled(featureSingleAssetVault) ||
453 view.rules().enabled(featureLendingProtocol);
454
455 auto const objectExists = [&view, enforce, &j](auto const& keylet) {
456 (void)enforce;
457 if (auto const sle = view.read(keylet))
458 {
459 // Finding the object is bad
460 auto const typeName = [&sle]() {
461 auto item = LedgerFormats::getInstance().findByType(sle->getType());
462
463 if (item != nullptr)
464 return item->getName();
465 return std::to_string(sle->getType());
466 }();
467
468 JLOG(j.fatal()) << "Invariant failed: account deletion left behind a " << typeName
469 << " object";
470 // The comment above starting with "assert(enforce)" explains this
471 // assert.
472 XRPL_ASSERT(
473 enforce,
474 "xrpl::AccountRootsDeletedClean::finalize::objectExists : "
475 "account deletion left no objects behind");
476 return true;
477 }
478 return false;
479 };
480
481 for (auto const& [before, after] : accountsDeleted_)
482 {
483 auto const accountID = before->getAccountID(sfAccount);
484 // An account should not be deleted with a balance
485 if (after->at(sfBalance) != beast::kZero)
486 {
487 JLOG(j.fatal()) << "Invariant failed: account deletion left "
488 "behind a non-zero balance";
489 XRPL_ASSERT(
490 enforce,
491 "xrpl::AccountRootsDeletedClean::finalize : "
492 "deleted account has zero balance");
493 if (enforce)
494 return false;
495 }
496 // An account should not be deleted with a non-zero owner count
497 if (after->at(sfOwnerCount) != 0)
498 {
499 JLOG(j.fatal()) << "Invariant failed: account deletion left "
500 "behind a non-zero owner count";
501 XRPL_ASSERT(
502 enforce,
503 "xrpl::AccountRootsDeletedClean::finalize : "
504 "deleted account has zero owner count");
505 if (enforce)
506 return false;
507 }
508 // Simple types
509 for (auto const& [keyletfunc, _1, _2] : kDirectAccountKeylets)
510 {
511 // TODO: use '_' for both unused variables above once we are in C++26
512 if (objectExists(std::invoke(keyletfunc, accountID)) && enforce)
513 return false;
514 }
515
516 {
517 // NFT pages. nftpage_min and nftpage_max were already explicitly
518 // checked above as entries in directAccountKeylets. This uses
519 // view.succ() to check for any NFT pages in between the two
520 // endpoints.
521 Keylet const first = keylet::nftokenPageMin(accountID);
522 Keylet const last = keylet::nftokenPageMax(accountID);
523
524 std::optional<uint256> key = view.succ(first.key, last.key.next());
525
526 // current page
527 if (key && objectExists(Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
528 return false;
529 }
530
531 // If the account is a pseudo account, then the linked object must
532 // also be deleted. e.g. AMM, Vault, etc.
533 for (auto const& field : getPseudoAccountFields())
534 {
535 if (before->isFieldPresent(*field))
536 {
537 auto const key = before->getFieldH256(*field);
538 if (objectExists(keylet::unchecked(key)) && enforce)
539 return false;
540 }
541 }
542 }
543
544 return true;
545}
546
547//------------------------------------------------------------------------------
548
549void
551{
552 if (before && after && before->getType() != after->getType())
553 typeMismatch_ = true;
554
555 if (after)
556 {
557#pragma push_macro("LEDGER_ENTRY")
558#undef LEDGER_ENTRY
559
560#define LEDGER_ENTRY(tag, ...) case tag:
561
562 switch (after->getType())
563 {
564#include <xrpl/protocol/detail/ledger_entries.macro>
565
566 break;
567 default:
568 invalidTypeAdded_ = true;
569 break;
570 }
571
572#undef LEDGER_ENTRY
573#pragma pop_macro("LEDGER_ENTRY")
574 }
575}
576
577bool
579 STTx const&,
580 TER const,
581 XRPAmount const,
582 ReadView const&,
583 beast::Journal const& j) const
584{
585 if ((!typeMismatch_) && (!invalidTypeAdded_))
586 return true;
587
588 if (typeMismatch_)
589 {
590 JLOG(j.fatal()) << "Invariant failed: ledger entry type mismatch";
591 }
592
594 {
595 JLOG(j.fatal()) << "Invariant failed: invalid ledger entry type added";
596 }
597
598 return false;
599}
600
601//------------------------------------------------------------------------------
602
603void
605{
606 bool const overwriteFixEnabled = isFeatureEnabled(fixCleanup3_1_3, true);
607
608 if (after && after->getType() == ltRIPPLE_STATE)
609 {
610 // checking the issue directly here instead of
611 // relying on .native() just in case native somehow
612 // were systematically incorrect
613 bool const isXrp = after->getFieldAmount(sfLowLimit).asset() == xrpIssue() ||
614 after->getFieldAmount(sfHighLimit).asset() == xrpIssue();
615 if (overwriteFixEnabled)
616 {
617 xrpTrustLine_ |= isXrp;
618 }
619 else
620 {
621 xrpTrustLine_ = isXrp;
622 }
623 }
624}
625
626bool
628 STTx const&,
629 TER const,
630 XRPAmount const,
631 ReadView const&,
632 beast::Journal const& j) const
633{
634 if (!xrpTrustLine_)
635 return true;
636
637 JLOG(j.fatal()) << "Invariant failed: an XRP trust line was created";
638 return false;
639}
640
641//------------------------------------------------------------------------------
642
643void
645{
646 if (after && after->getType() == ltRIPPLE_STATE)
647 {
648 bool const overwriteFixEnabled = isFeatureEnabled(fixCleanup3_1_3, true);
649
650 bool const lowFreeze = after->isFlag(lsfLowFreeze);
651 bool const lowDeepFreeze = after->isFlag(lsfLowDeepFreeze);
652
653 bool const highFreeze = after->isFlag(lsfHighFreeze);
654 bool const highDeepFreeze = after->isFlag(lsfHighDeepFreeze);
655
656 bool const bad = (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze);
657 if (overwriteFixEnabled)
658 {
660 }
661 else
662 {
664 }
665 }
666}
667
668bool
670 STTx const&,
671 TER const,
672 XRPAmount const,
673 ReadView const&,
674 beast::Journal const& j) const
675{
677 return true;
678
679 JLOG(j.fatal()) << "Invariant failed: a trust line with deep freeze flag "
680 "without normal freeze was created";
681 return false;
682}
683
684//------------------------------------------------------------------------------
685
686void
688{
689 if (!before && after->getType() == ltACCOUNT_ROOT)
690 {
692 accountSeq_ = (*after)[sfSequence];
694 flags_ = after->getFlags();
695 }
696}
697
698bool
700 STTx const& tx,
701 TER const result,
702 XRPAmount const,
703 ReadView const& view,
704 beast::Journal const& j) const
705{
706 if (accountsCreated_ == 0)
707 return true;
708
709 if (accountsCreated_ > 1)
710 {
711 JLOG(j.fatal()) << "Invariant failed: multiple accounts "
712 "created in a single transaction";
713 return false;
714 }
715
716 // From this point on we know exactly one account was created.
718 {
719 bool const pseudoAccount =
721 (view.rules().enabled(featureSingleAssetVault) ||
722 view.rules().enabled(featureLendingProtocol)));
723
724 if (pseudoAccount && !hasPrivilege(tx, CreatePseudoAcct))
725 {
726 JLOG(j.fatal()) << "Invariant failed: pseudo-account created by a "
727 "wrong transaction type";
728 return false;
729 }
730
731 std::uint32_t const startingSeq = pseudoAccount ? 0 : view.seq();
732
733 if (accountSeq_ != startingSeq)
734 {
735 JLOG(j.fatal()) << "Invariant failed: account created with "
736 "wrong starting sequence number";
737 return false;
738 }
739
740 if (pseudoAccount)
741 {
742 std::uint32_t const expected = (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
743 if (flags_ != expected)
744 {
745 JLOG(j.fatal()) << "Invariant failed: pseudo-account created with "
746 "wrong flags";
747 return false;
748 }
749 }
750
751 return true;
752 }
753
754 JLOG(j.fatal()) << "Invariant failed: account root created illegally";
755 return false;
756} // namespace xrpl
757
758//------------------------------------------------------------------------------
759
760void
762{
763 if (before && before->getType() == ltRIPPLE_STATE)
765
766 if (before && before->getType() == ltMPTOKEN)
768}
769
770bool
772 STTx const& tx,
773 TER const result,
774 XRPAmount const,
775 ReadView const& view,
776 beast::Journal const& j) const
777{
778 if (tx.getTxnType() != ttCLAWBACK)
779 return true;
780
781 if (isTesSuccess(result))
782 {
783 if (trustlinesChanged_ > 1)
784 {
785 JLOG(j.fatal()) << "Invariant failed: more than one trustline changed.";
786 return false;
787 }
788
789 if (mptokensChanged_ > 1)
790 {
791 JLOG(j.fatal()) << "Invariant failed: more than one mptokens changed.";
792 return false;
793 }
794
795 bool const mptV2Enabled = view.rules().enabled(featureMPTokensV2);
796 if (trustlinesChanged_ == 1 || (mptV2Enabled && mptokensChanged_ == 1))
797 {
798 AccountID const issuer = tx.getAccountID(sfAccount);
799 STAmount const& amount = tx.getFieldAmount(sfAmount);
800 AccountID const& holder = amount.getIssuer();
801 STAmount const holderBalance = amount.asset().visit(
802 [&](Issue const& issue) {
803 return accountHolds(
804 view, holder, issue.currency, issuer, FreezeHandling::IgnoreFreeze, j);
805 },
806 [&](MPTIssue const& issue) {
807 return accountHolds(
808 view,
809 holder,
810 issue,
813 j);
814 });
815
816 if (holderBalance.signum() < 0)
817 {
818 JLOG(j.fatal()) << "Invariant failed: trustline or MPT balance is negative";
819 return false;
820 }
821 }
822 }
823 else
824 {
825 if (trustlinesChanged_ != 0)
826 {
827 JLOG(j.fatal()) << "Invariant failed: some trustlines were changed "
828 "despite failure of the transaction.";
829 return false;
830 }
831
832 if (mptokensChanged_ != 0)
833 {
834 JLOG(j.fatal()) << "Invariant failed: some mptokens were changed "
835 "despite failure of the transaction.";
836 return false;
837 }
838 }
839
840 return true;
841}
842
843//------------------------------------------------------------------------------
844
845void
847{
848 if (isDelete)
849 {
850 // Deletion is ignored
851 return;
852 }
853
854 if (after && after->getType() == ltACCOUNT_ROOT)
855 {
856 bool const isPseudo = [&]() {
857 // isPseudoAccount checks that any of the pseudo-account fields are
858 // set.
860 return true;
861 // Not all pseudo-accounts have a zero sequence, but all accounts
862 // with a zero sequence had better be pseudo-accounts.
863 if (after->at(sfSequence) == 0)
864 return true;
865
866 return false;
867 }();
868 if (isPseudo)
869 {
870 // Pseudo accounts must have the following properties:
871 // 1. Exactly one of the pseudo-account fields is set.
872 // 2. The sequence number is not changed.
873 // 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth
874 // flags are set.
875 // 4. The RegularKey is not set.
876 {
878
879 auto const numFields = std::ranges::count_if(
880 fields,
881 [&after](SField const* sf) -> bool { return after->isFieldPresent(*sf); });
882 if (numFields != 1)
883 {
884 std::stringstream error;
885 error << "pseudo-account has " << numFields << " pseudo-account fields set";
886 errors_.emplace_back(error.str());
887 }
888 }
889 if (before && before->at(sfSequence) != after->at(sfSequence))
890 {
891 errors_.emplace_back("pseudo-account sequence changed");
892 }
893 if (!after->isFlag(lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth))
894 {
895 errors_.emplace_back("pseudo-account flags are not set");
896 }
897 if (after->isFieldPresent(sfRegularKey))
898 {
899 errors_.emplace_back("pseudo-account has a regular key");
900 }
901 }
902 }
903}
904
905bool
907 STTx const& tx,
908 TER const,
909 XRPAmount const,
910 ReadView const& view,
911 beast::Journal const& j)
912{
913 bool const enforce = view.rules().enabled(featureSingleAssetVault);
914 XRPL_ASSERT(
915 errors_.empty() || enforce,
916 "xrpl::ValidPseudoAccounts::finalize : no bad "
917 "changes or enforce invariant");
918 if (!errors_.empty())
919 {
920 for (auto const& error : errors_)
921 {
922 JLOG(j.fatal()) << "Invariant failed: " << error;
923 }
924 if (enforce)
925 return false;
926 }
927 return true;
928}
929
930//------------------------------------------------------------------------------
931
932void
934{
935 if (isDelete || !before)
936 {
937 // Creation and deletion are ignored
938 return;
939 }
940
941 changedEntries_.emplace(before, after);
942}
943
944bool
946 STTx const& tx,
947 TER const,
948 XRPAmount const,
949 ReadView const& view,
950 beast::Journal const& j)
951{
952 static auto const kFieldChanged = [](auto const& before, auto const& after, auto const& field) {
953 bool const beforeField = before->isFieldPresent(field);
954 bool const afterField = after->isFieldPresent(field);
955 return beforeField != afterField || (afterField && before->at(field) != after->at(field));
956 };
957 for (auto const& slePair : changedEntries_)
958 {
959 auto const& before = slePair.first;
960 auto const& after = slePair.second;
961 auto const type = after->getType();
962 bool bad = false;
963 [[maybe_unused]] bool enforce = false;
964 switch (type)
965 {
966 case ltLOAN_BROKER:
967 /*
968 * We check this invariant regardless of lending protocol
969 * amendment status, allowing for detection and logging of
970 * potential issues even when the amendment is disabled.
971 */
972 enforce = view.rules().enabled(featureLendingProtocol);
973 bad = kFieldChanged(before, after, sfLedgerEntryType) ||
974 kFieldChanged(before, after, sfLedgerIndex) ||
975 kFieldChanged(before, after, sfSequence) ||
976 kFieldChanged(before, after, sfOwnerNode) ||
977 kFieldChanged(before, after, sfVaultNode) ||
978 kFieldChanged(before, after, sfVaultID) ||
979 kFieldChanged(before, after, sfAccount) ||
980 kFieldChanged(before, after, sfOwner) ||
981 kFieldChanged(before, after, sfManagementFeeRate) ||
982 kFieldChanged(before, after, sfCoverRateMinimum) ||
983 kFieldChanged(before, after, sfCoverRateLiquidation);
984 break;
985 case ltLOAN:
986 /*
987 * We check this invariant regardless of lending protocol
988 * amendment status, allowing for detection and logging of
989 * potential issues even when the amendment is disabled.
990 */
991 enforce = view.rules().enabled(featureLendingProtocol);
992 bad = kFieldChanged(before, after, sfLedgerEntryType) ||
993 kFieldChanged(before, after, sfLedgerIndex) ||
994 kFieldChanged(before, after, sfSequence) ||
995 kFieldChanged(before, after, sfOwnerNode) ||
996 kFieldChanged(before, after, sfLoanBrokerNode) ||
997 kFieldChanged(before, after, sfLoanBrokerID) ||
998 kFieldChanged(before, after, sfBorrower) ||
999 kFieldChanged(before, after, sfLoanOriginationFee) ||
1000 kFieldChanged(before, after, sfLoanServiceFee) ||
1001 kFieldChanged(before, after, sfLatePaymentFee) ||
1002 kFieldChanged(before, after, sfClosePaymentFee) ||
1003 kFieldChanged(before, after, sfOverpaymentFee) ||
1004 kFieldChanged(before, after, sfInterestRate) ||
1005 kFieldChanged(before, after, sfLateInterestRate) ||
1006 kFieldChanged(before, after, sfCloseInterestRate) ||
1007 kFieldChanged(before, after, sfOverpaymentInterestRate) ||
1008 kFieldChanged(before, after, sfStartDate) ||
1009 kFieldChanged(before, after, sfPaymentInterval) ||
1010 kFieldChanged(before, after, sfGracePeriod) ||
1011 kFieldChanged(before, after, sfLoanScale);
1012 break;
1013 default:
1014 /*
1015 * We check this invariant regardless of lending protocol
1016 * amendment status, allowing for detection and logging of
1017 * potential issues even when the amendment is disabled.
1018 *
1019 * We use the lending protocol as a gate, even though
1020 * all transactions are affected because that's when it
1021 * was added.
1022 */
1023 enforce = view.rules().enabled(featureLendingProtocol);
1024 bad = kFieldChanged(before, after, sfLedgerEntryType) ||
1025 kFieldChanged(before, after, sfLedgerIndex);
1026 }
1027 XRPL_ASSERT(
1028 !bad || enforce,
1029 "xrpl::NoModifiedUnmodifiableFields::finalize : no bad "
1030 "changes or enforce invariant");
1031 if (bad)
1032 {
1033 JLOG(j.fatal()) << "Invariant failed: changed an unchangeable field for "
1034 << tx.getTransactionID();
1035 if (enforce)
1036 return false;
1037 }
1038 }
1039 return true;
1040}
1041
1042void
1044 bool isDelete,
1047{
1048 if (!isDelete && after)
1049 afterEntries_.push_back(after);
1050}
1051
1052bool
1054 STTx const&,
1055 TER const,
1056 XRPAmount const,
1057 ReadView const& view,
1058 beast::Journal const& j) const
1059{
1060 bool const badLedgerEntry = std::ranges::any_of(
1061 afterEntries_, [&](auto const& sle) { return hasInvalidAmount(*sle, j); });
1062
1063 if (badLedgerEntry)
1064 {
1065 JLOG(j.fatal())
1066 << "Invariant failed: ledger entry contains non-canonical MPT or XRP amount";
1067 return !view.rules().enabled(fixCleanup3_2_0);
1068 }
1069
1070 return true;
1071}
1072
1073} // namespace xrpl
T any_of(T... args)
A generic endpoint for log messages.
Definition Journal.h:38
Stream fatal() const
Definition Journal.h:321
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< std::pair< SLE::const_pointer, SLE::const_pointer > > accountsDeleted_
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
Definition Asset.h:107
BaseUInt next() const
Definition base_uint.h:460
A currency issued by an account.
Definition Issue.h:13
Currency currency
Definition Issue.h:15
Item const * findByType(KeyType type) const
Retrieve a format based on its type.
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
static LedgerFormats const & getInstance()
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
std::set< std::pair< SLE::const_pointer, SLE::const_pointer > > changedEntries_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
A view into a ledger.
Definition ReadView.h:31
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:97
virtual std::optional< key_type > succ(key_type const &key, std::optional< key_type > const &last=std::nullopt) const =0
Return the key of the next state item.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:171
Identifies fields.
Definition SField.h:130
int signum() const noexcept
Definition STAmount.h:504
bool native() const noexcept
Definition STAmount.h:453
Asset const & asset() const
Definition STAmount.h:478
AccountID const & getIssuer() const
Definition STAmount.h:498
XRPAmount xrp() const
Definition STAmount.cpp:271
std::shared_ptr< STLedgerEntry const > const & const_ref
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:633
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:647
TxType getTxnType() const
Definition STTx.h:188
uint256 getTransactionID() const
Definition STTx.h:200
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
static bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::vector< std::shared_ptr< SLE const > > afterEntries_
std::uint32_t mptokensChanged_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
std::uint32_t trustlinesChanged_
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
std::vector< std::string > errors_
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
constexpr value_type drops() const
Returns the number of drops.
Definition XRPAmount.h:159
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
T count_if(T... args)
T invoke(T... args)
Keylet computation functions.
Definition Indexes.h:34
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Definition Indexes.cpp:351
Keylet nftokenPageMin(AccountID const &owner)
NFT page keylets.
Definition Indexes.cpp:384
Keylet nftokenPageMax(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:392
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:97
std::vector< SField const * > const & getPseudoAccountFields()
Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account if set.
bool isFeatureEnabled(uint256 const &feature, bool resultIfNoRules)
Check whether a feature is enabled in the current ledger rules.
Definition Rules.cpp:194
bool isXRP(AccountID const &c)
Definition AccountID.h:70
std::array< KeyletDesc< AccountID const & >, 6 > const kDirectAccountKeylets
Definition Indexes.h:360
bool hasPrivilege(STTx const &tx, Privilege priv)
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:554
bool hasInvalidAmount(STBase const &field, beast::Journal j)
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
TERSubset< CanCvtToTER > TER
Definition TER.h:634
bool isPseudoAccount(SLE::const_pointer sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Returns true if and only if sleAcct is a pseudo-account or specific pseudo-accounts in pseudoFieldFil...
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
constexpr std::uint64_t kMaxMpTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:238
constexpr XRPAmount kInitialXrp
Configure the native currency.
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=SpendableHandling::SimpleBalance)
T str(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:19
uint256 key
Definition Keylet.h:20
T to_string(T... args)