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