xrpld
Loading...
Searching...
No Matches
Transactor.cpp
1#include <xrpl/tx/Transactor.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Slice.h>
5#include <xrpl/basics/base_uint.h>
6#include <xrpl/basics/contract.h>
7#include <xrpl/beast/utility/Zero.h>
8#include <xrpl/beast/utility/instrumentation.h>
9#include <xrpl/core/NetworkIDService.h>
10#include <xrpl/core/ServiceRegistry.h>
11#include <xrpl/json/to_string.h> // IWYU pragma: keep
12#include <xrpl/ledger/ApplyView.h>
13#include <xrpl/ledger/ReadView.h>
14#include <xrpl/ledger/helpers/AccountRootHelpers.h>
15#include <xrpl/ledger/helpers/CredentialHelpers.h>
16#include <xrpl/ledger/helpers/DelegateHelpers.h>
17#include <xrpl/ledger/helpers/NFTokenHelpers.h>
18#include <xrpl/ledger/helpers/OfferHelpers.h>
19#include <xrpl/ledger/helpers/RippleStateHelpers.h>
20#include <xrpl/ledger/helpers/SponsorHelpers.h>
21#include <xrpl/protocol/AccountID.h>
22#include <xrpl/protocol/Feature.h>
23#include <xrpl/protocol/Indexes.h>
24#include <xrpl/protocol/LedgerFormats.h>
25#include <xrpl/protocol/Permissions.h>
26#include <xrpl/protocol/Protocol.h>
27#include <xrpl/protocol/PublicKey.h>
28#include <xrpl/protocol/Rules.h>
29#include <xrpl/protocol/SField.h>
30#include <xrpl/protocol/STAmount.h>
31#include <xrpl/protocol/STLedgerEntry.h>
32#include <xrpl/protocol/STTx.h>
33#include <xrpl/protocol/Serializer.h> // IWYU pragma: keep
34#include <xrpl/protocol/SystemParameters.h>
35#include <xrpl/protocol/TER.h>
36#include <xrpl/protocol/TxFlags.h>
37#include <xrpl/protocol/TxMeta.h>
38#include <xrpl/protocol/XRPAmount.h>
39#include <xrpl/server/LoadFeeTrack.h>
40#include <xrpl/tx/ApplyContext.h>
41#include <xrpl/tx/SignerEntries.h>
42#include <xrpl/tx/apply.h>
43#include <xrpl/tx/applySteps.h>
44#include <xrpl/tx/invariants/InvariantRunner.h>
45
46#include <algorithm>
47#include <cstddef>
48#include <cstdint>
49#include <functional>
50#include <map>
51#include <optional>
52#include <stdexcept>
53#include <tuple>
54#include <unordered_set>
55#include <utility>
56#include <vector>
57
58namespace xrpl {
59
65{
66 if (isPseudoTx(ctx.tx) && ctx.tx.isFlag(tfInnerBatchTxn))
67 {
68 JLOG(ctx.j.warn()) << "Pseudo transactions cannot contain the "
69 "tfInnerBatchTxn flag.";
70 return temINVALID_FLAG;
71 }
72
73 if (!isPseudoTx(ctx.tx) || ctx.tx.isFieldPresent(sfNetworkID))
74 {
75 uint32_t const nodeNID = ctx.registry.get().getNetworkIDService().getNetworkID();
76 std::optional<uint32_t> txNID = ctx.tx[~sfNetworkID];
77
78 if (nodeNID <= 1024)
79 {
80 // legacy networks have ids less than 1024, these networks cannot
81 // specify NetworkID in txn
82 if (txNID)
84 }
85 else
86 {
87 // new networks both require the field to be present and require it
88 // to match
89 if (!txNID)
91
92 if (*txNID != nodeNID)
93 return telWRONG_NETWORK;
94 }
95 }
96
97 auto const txID = ctx.tx.getTransactionID();
98
99 if (txID == beast::kZero)
100 {
101 JLOG(ctx.j.warn()) << "applyTransaction: transaction id may not be zero";
102 return temINVALID;
103 }
104
105 if ((ctx.tx.getFlags() & flagMask) != 0u)
106 {
107 JLOG(ctx.j.debug()) << ctx.tx.peekAtField(sfTransactionType).getFullText()
108 << ": invalid flags.";
109 return temINVALID_FLAG;
110 }
111
112 return tesSUCCESS;
113}
114
115namespace detail {
116
122NotTEC
124{
125 if (auto const spk = sigObject.getFieldVL(sfSigningPubKey);
126 !spk.empty() && !publicKeyType(makeSlice(spk)))
127 {
128 JLOG(j.debug()) << "preflightCheckSigningKey: invalid signing key";
129 return temBAD_SIGNATURE;
130 }
131 return tesSUCCESS;
132}
133
136{
137 if ((flags & TapDryRun) != 0u) // simulation
138 {
139 std::optional<Slice> const signature = sigObject[~sfTxnSignature];
140 if (signature && !signature->empty())
141 {
142 // NOTE: This code should never be hit because it's checked in the
143 // `simulate` RPC
144 return temINVALID; // LCOV_EXCL_LINE
145 }
146
147 if (!sigObject.isFieldPresent(sfSigners))
148 {
149 // no signers, no signature - a valid simulation
150 return tesSUCCESS;
151 }
152
153 for (auto const& signer : sigObject.getFieldArray(sfSigners))
154 {
155 if (signer.isFieldPresent(sfTxnSignature) && !signer[sfTxnSignature].empty())
156 {
157 // NOTE: This code should never be hit because it's
158 // checked in the `simulate` RPC
159 return temINVALID; // LCOV_EXCL_LINE
160 }
161 }
162
163 Slice const signingPubKey = sigObject[sfSigningPubKey];
164 if (!signingPubKey.empty())
165 {
166 // trying to single-sign _and_ multi-sign a transaction
167 return temINVALID;
168 }
169 return tesSUCCESS;
170 }
171 return {};
172}
173
174} // namespace detail
175
176static NotTEC
178{
179 bool const hasSponsor = ctx.tx.isFieldPresent(sfSponsor);
180 bool const hasSponsorFlags = ctx.tx.isFieldPresent(sfSponsorFlags);
181 bool const hasSponsorSig = ctx.tx.isFieldPresent(sfSponsorSignature);
182
183 if ((hasSponsor || hasSponsorFlags || hasSponsorSig) && !ctx.rules.enabled(featureSponsor))
184 return temDISABLED;
185
186 if (hasSponsor != hasSponsorFlags)
187 {
188 JLOG(ctx.j.debug()) << "preflight1: sponsor and sponsor flags mismatch";
189 return temINVALID_FLAG;
190 }
191 if (hasSponsorSig && (!hasSponsor || !hasSponsorFlags))
192 {
193 JLOG(ctx.j.debug()) << "preflight1: sponsor signature without sponsor definition";
194 return temMALFORMED;
195 }
196
197 if (hasSponsorFlags)
198 {
199 auto const sponsorFlags = ctx.tx.getFieldU32(sfSponsorFlags);
200 if (((sponsorFlags & spfSponsorFlagMask) != 0u) || sponsorFlags == 0)
201 {
202 JLOG(ctx.j.debug()) << "preflight1: invalid sponsor flags";
203 return temINVALID_FLAG;
204 }
205
206 // Reserve sponsorship is only permitted for an explicit allow-list of
207 // transaction types, for v1. All other tx types reject spfSponsorReserve here.
208 if (isReserveSponsored(ctx.tx))
209 {
211 {
212 JLOG(ctx.j.debug())
213 << "preflight1: spfSponsorReserve not allowed for this transaction type";
214 return temINVALID_FLAG;
215 }
216 }
217 }
218
219 if (hasSponsor && ctx.tx.getAccountID(sfSponsor) == ctx.tx.getAccountID(sfAccount))
220 {
221 JLOG(ctx.j.debug()) << "preflight1: Sponsor account cannot be the same as the account";
222 return temMALFORMED;
223 }
224
225 return tesSUCCESS;
226}
227
231NotTEC
233{
234 if (ctx.tx.isFieldPresent(sfDelegate))
235 {
236 if (!ctx.rules.enabled(featurePermissionDelegationV1_1))
237 return temDISABLED;
238
239 if (ctx.tx[sfDelegate] == ctx.tx[sfAccount])
240 return temBAD_SIGNER;
241
242 auto const& perm = Permission::getInstance();
243 auto const txType = ctx.tx.getTxnType();
244
245 // If the transaction is not delegable and does not have granular permissions, fail earlier
246 // with temINVALID. This is to prevent transactions that are not delegable at all from
247 // being processed further in the invokeCheckPermission function.
248 if (!perm.isDelegable(Permission::txToPermissionType(txType), ctx.rules) &&
249 !perm.hasGranularPermissions(txType))
250 return temINVALID;
251 }
252
253 if (auto const ret = preflight0(ctx, flagMask))
254 return ret;
255
256 auto const id = ctx.tx.getAccountID(sfAccount);
257 if (id == beast::kZero)
258 {
259 JLOG(ctx.j.warn()) << "preflight1: bad account id";
260 return temBAD_SRC_ACCOUNT;
261 }
262
263 // No point in going any further if the transaction fee is malformed.
264 auto const fee = ctx.tx.getFieldAmount(sfFee);
265 if (!fee.native() || fee.negative() || !isLegalAmount(fee.xrp()))
266 {
267 JLOG(ctx.j.debug()) << "preflight1: invalid fee";
268 return temBAD_FEE;
269 }
270
271 if (auto const ret = detail::preflightCheckSigningKey(ctx.tx, ctx.j))
272 return ret;
273
274 // An AccountTxnID field constrains transaction ordering more than the
275 // Sequence field. Tickets, on the other hand, reduce ordering
276 // constraints. Because Tickets and AccountTxnID work against one
277 // another the combination is unsupported and treated as malformed.
278 //
279 // We return temINVALID for such transactions.
280 if (ctx.tx.getSeqProxy().isTicket() && ctx.tx.isFieldPresent(sfAccountTxnID))
281 return temINVALID;
282
283 if (ctx.tx.isFlag(tfInnerBatchTxn) && !ctx.rules.enabled(featureBatchV1_1))
284 return temINVALID_FLAG;
285
286 // Reject if the inner batch flag and parentBatchId are inconsistent.
287 // A standalone tx with tfInnerBatchTxn but no parentBatchId is an
288 // attack attempt. A tx with parentBatchId but without tfInnerBatchTxn
289 // is a programming error.
290 if (ctx.tx.isFlag(tfInnerBatchTxn) != ctx.parentBatchId.has_value())
292
293 if (auto const ter = preflight1Sponsor(ctx); !isTesSuccess(ter))
294 return ter;
295
296 return tesSUCCESS;
297}
298
302NotTEC
304{
305 if (auto const ret = detail::preflightCheckSimulateKeys(ctx.flags, ctx.tx, ctx.j))
306 {
307 // Skips following checks if the transaction is being simulated,
308 // regardless of success or failure
309 return *ret;
310 }
311
312 // Skip the signature check on batch inner transactions. preflight1 already
313 // enforces both conditions; re-checking them as defense in depth guarantees
314 // we never return success (and so skip signature validation) for an inner
315 // transaction unless the amendment is enabled and it really sits inside a
316 // batch.
317 if (ctx.tx.isFlag(tfInnerBatchTxn))
318 {
319 if (!ctx.rules.enabled(featureBatchV1_1))
320 return temINVALID_FLAG;
321 if (!ctx.parentBatchId.has_value())
323 return tesSUCCESS;
324 }
325 // Do not add any checks after this point that are relevant for
326 // batch inner transactions. They will be skipped.
327
328 auto const sigValid = checkValidity(ctx.registry.get().getHashRouter(), ctx.tx, ctx.rules);
329 if (sigValid.first == Validity::SigBad)
330 { // LCOV_EXCL_START
331 JLOG(ctx.j.debug()) << "preflight2: bad signature. " << sigValid.second;
332 return temINVALID;
333 // LCOV_EXCL_STOP
334 }
335
336 // Do not add any checks after this point that are relevant for
337 // batch inner transactions. They will be skipped.
338
339 return tesSUCCESS;
340}
341
342NotTEC
344{
345 if (ctx.rules.enabled(fixCleanup3_2_0) && hasInvalidAmount(ctx.tx, ctx.j))
346 return temBAD_AMOUNT;
347
348 return tesSUCCESS;
349}
350
351//------------------------------------------------------------------------------
352
354 : ctx_(ctx)
355 , sink_(ctx.journal, toShortString(ctx.tx.getTransactionID()) + " ")
356 , j_(sink_)
357 , accountID_(ctx.tx.getAccountID(sfAccount))
358{
359}
360
361bool
363{
364 if (!slice)
365 return true;
366 return !slice->empty() && slice->length() <= maxLength;
367}
368
374
375NotTEC
380
381NotTEC
383 ReadView const& view,
384 STTx const& tx,
385 std::unordered_set<GranularPermissionType>& heldGranularPermissions)
386{
387 auto const delegate = tx[~sfDelegate];
388 if (!delegate)
389 return tesSUCCESS;
390
391 auto const sle = view.read(keylet::delegate(tx[sfAccount], *delegate));
392 if (!sle)
394
395 if (isTesSuccess(checkTxPermission(sle, tx)))
396 return tesSUCCESS;
397
398 if (!Permission::getInstance().hasGranularPermissions(tx.getTxnType()))
400
401 heldGranularPermissions = getGranularPermission(sle, tx.getTxnType());
402 if (heldGranularPermissions.empty())
404
405 if (!Permission::getInstance().checkGranularSandbox(tx, heldGranularPermissions))
407
408 return tesSUCCESS;
409}
410
411NotTEC
413{
414 if (!tx.isFieldPresent(sfSponsor))
415 return tesSUCCESS;
416
417 // Reserve sponsorship with permissioned delegation is disallowed.
418 if (tx.isFieldPresent(sfDelegate) && isReserveSponsored(tx))
419 return temINVALID;
420
421 if (!view.exists(keylet::account(tx.getAccountID(sfSponsor))))
422 return terNO_ACCOUNT;
423
424 // Skip Sponsorship existence checks if the sponsor has signed the transaction - this
425 // transaction is valid regardless of the Sponsorship object.
426 // The use of the Sponsorship object is properly handled in
427 // getFeePayer/checkReserve/increaseOwnerCount/decreaseOwnerCount.
428 if (tx.isFieldPresent(sfSponsorSignature))
429 return tesSUCCESS;
430
431 // If the transaction contains sfDelegate, the Sponsorship object should be
432 // between the sponsor and the delegate.
433 auto const sponsorshipSle =
434 view.read(keylet::sponsorship(tx.getAccountID(sfSponsor), tx.getInitiator()));
435
436 // sponsorship object missing for pre-funded (no co-signing) tx
437 if (!sponsorshipSle)
438 return terNO_PERMISSION;
439
440 if (isFeeSponsored(tx) && sponsorshipSle->isFlag(lsfSponsorshipRequireSignForFee))
441 return terNO_PERMISSION;
442
443 if (isReserveSponsored(tx) && sponsorshipSle->isFlag(lsfSponsorshipRequireSignForReserve))
444 return terNO_PERMISSION;
445
446 return tesSUCCESS;
447}
448
451{
452 // Returns the fee in fee units.
453
454 // The computation has two parts:
455 // * The base fee, which is the same for most transactions.
456 // * The additional cost of each multisignature on the transaction.
457 // * The additional cost of each multisignature on the sponsor.
458 XRPAmount const baseFee = view.fees().base;
459
460 // Each signer adds one more baseFee to the minimum required fee
461 // for the transaction.
462 std::size_t const signerCount =
463 tx.isFieldPresent(sfSigners) ? tx.getFieldArray(sfSigners).size() : 0;
464
465 std::size_t sponsorSignerCount = 0;
466 if (tx.isFieldPresent(sfSponsorSignature))
467 {
468 auto const sponsorObj = tx.getFieldObject(sfSponsorSignature);
469 if (sponsorObj.isFieldPresent(sfSigners))
470 sponsorSignerCount += sponsorObj.getFieldArray(sfSigners).size();
471 }
472
473 return baseFee + ((signerCount + sponsorSignerCount) * baseFee);
474}
475
478 ReadView const& view,
479 STTx const& tx,
480 std::uint32_t extraBaseFeeMultiplier)
481{
482 return calculateBaseFee(view, tx) + view.fees().base * extraBaseFeeMultiplier;
483}
484
485// Returns the fee in fee units, not scaled for load.
488{
489 // Assumption: One reserve increment is typically much greater than one base
490 // fee.
491 // This check is in an assert so that it will come to the attention of
492 // developers if that assumption is not correct. If the owner reserve is not
493 // significantly larger than the base fee (or even worse, smaller), we will
494 // need to rethink charging an owner reserve as a transaction fee.
495 // TODO: This function is static, and I don't want to add more parameters.
496 // When it is finally refactored to be in a context that has access to the
497 // Application, include "app().getOverlay().networkID() > 2 ||" in the
498 // condition.
499 XRPL_ASSERT(
500 view.fees().increment > view.fees().base * 100,
501 "xrpl::Transactor::calculateOwnerReserveFee : Owner reserve is "
502 "reasonable");
503 return view.fees().increment;
504}
505
508 ServiceRegistry& registry,
509 XRPAmount baseFee,
510 Fees const& fees,
511 ApplyFlags flags)
512{
513 return scaleFeeLoad(baseFee, registry.getFeeTrack(), fees, (flags & TapUnlimited) != 0u);
514}
515
516TER
518{
519 if (!ctx.tx[sfFee].native())
520 return temBAD_FEE;
521
522 auto const feePaid = ctx.tx[sfFee].xrp();
523
524 if ((ctx.flags & TapBatch) != 0u)
525 {
526 if (feePaid == beast::kZero)
527 return tesSUCCESS;
528
529 JLOG(ctx.j.trace()) << "Batch: Fee must be zero.";
530 return temBAD_FEE; // LCOV_EXCL_LINE
531 }
532
533 if (!isLegalAmount(feePaid) || feePaid < beast::kZero)
534 return temBAD_FEE;
535
536 // Only check fee is sufficient when the ledger is open.
537 if (ctx.view.open())
538 {
539 auto const feeDue = minimumFee(ctx.registry, baseFee, ctx.view.fees(), ctx.flags);
540
541 if (feePaid < feeDue)
542 {
543 JLOG(ctx.j.trace()) << "Insufficient fee paid: " << to_string(feePaid) << "/"
544 << to_string(feeDue);
545 return telINSUF_FEE_P;
546 }
547 }
548
549 if (feePaid == beast::kZero)
550 return tesSUCCESS;
551
552 auto const feePayer = getFeePayer(ctx.view, ctx.tx);
553 auto const payerSle = ctx.view.read(feePayer.keylet);
554
555 if (!payerSle)
556 {
557 if (feePayer.type == FeePayerType::SponsorPreFunded)
558 {
559 // Sanity check: already checked in checkSponsor
560 return tefINTERNAL; // LCOV_EXCL_LINE
561 }
562
563 return terNO_ACCOUNT;
564 }
565
566 XRPAmount maxSpendable = beast::kZero;
567
568 if (feePayer.type == FeePayerType::SponsorPreFunded)
569 {
570 if (payerSle->getType() != ltSPONSORSHIP)
571 return tefINTERNAL; // LCOV_EXCL_LINE
572
573 if (payerSle->isFieldPresent(feePayer.balanceField))
574 maxSpendable = payerSle->getFieldAmount(feePayer.balanceField).xrp();
575
576 if (payerSle->isFieldPresent(sfMaxFee))
577 {
578 auto const cap = payerSle->getFieldAmount(sfMaxFee).xrp();
579 maxSpendable = std::min(maxSpendable, cap);
580 }
581 }
582 else
583 {
584 if (payerSle->getType() != ltACCOUNT_ROOT)
585 return tefINTERNAL; // LCOV_EXCL_LINE
586
587 if (feePayer.type == FeePayerType::SponsorCoSigned)
588 {
589 auto const sponsorReserve = accountReserve(ctx.view, payerSle, ctx.j);
590 maxSpendable = payerSle->getFieldAmount(sfBalance).xrp() - sponsorReserve;
591 }
592 else
593 {
594 maxSpendable = payerSle->getFieldAmount(feePayer.balanceField).xrp();
595 }
596 }
597
598 // NOTE: Because preclaim evaluates against a static readview, it
599 // does not reflect fee deductions from other transactions paid by
600 // the same account within the current ledger.
601 // As a result, if an account's balance is over-committed across multiple
602 // transactions, this check may pass optimistically.
603 // The fee shortfall will be handled by the Transactor::reset mechanism,
604 // which caps the fee to the remaining actual balance.
605 if (maxSpendable < feePaid)
606 {
607 JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(maxSpendable)
608 << " paid=" << to_string(feePaid);
609
610 if ((maxSpendable > beast::kZero) && !ctx.view.open())
611 {
612 // Closed ledger, non-zero balance, less than fee
613 return tecINSUFF_FEE;
614 }
615
616 return terINSUF_FEE_B;
617 }
618
619 return tesSUCCESS;
620}
621
622TER
624{
625 auto const feePaid = ctx_.tx[sfFee].xrp();
626
627 auto const feePayer = getFeePayer(view(), ctx_.tx);
628 auto const sle = view().peek(feePayer.keylet);
629
630 JLOG(j_.trace()) << "Fee payer: " + to_string(feePayer.id);
631
632 if (!sle)
633 return tefINTERNAL; // LCOV_EXCL_LINE
634
635 if (feePaid == beast::kZero)
636 return tesSUCCESS;
637
638 XRPAmount balance = beast::kZero;
639 if (sle->isFieldPresent(feePayer.balanceField))
640 {
641 balance = sle->getFieldAmount(feePayer.balanceField).xrp();
642 }
643 else if (feePayer.balanceField != sfFeeAmount)
644 {
645 return tefINTERNAL; // LCOV_EXCL_LINE
646 }
647
648 // A co-signed sponsor pays the fee out of its own account balance, but must
649 // never be charged into its account reserve, and a pre-funded sponsorship's
650 // fee is capped by sfMaxFee. Mirror the spendable amount computed in
651 // checkFee() so both limits are enforced on the apply path too.
652 XRPAmount spendable = balance;
653 if (feePayer.type == FeePayerType::SponsorCoSigned)
654 {
655 auto const sponsorReserve = accountReserve(view(), sle, j_);
656 // max(balance - reserve, 0) with overflow handling
657 spendable = balance > sponsorReserve ? balance - sponsorReserve : beast::kZero;
658 }
659 else if (feePayer.type == FeePayerType::SponsorPreFunded && sle->isFieldPresent(sfMaxFee))
660 {
661 auto const cap = sle->getFieldAmount(sfMaxFee).xrp();
662 spendable = std::min(spendable, cap);
663 }
664
665 // Only sponsor fee-payers reject here on insufficient funds. For an
666 // ordinary account, the fee falls through and is capped by reset(), which
667 // caps to the account's balance. That capping is wrong for sponsors: a
668 // co-signed sponsor would be charged into its own reserve, and a prefunded
669 // sponsorship's fee amount should be rejected rather than partially spent.
670 if (feePaid > spendable &&
671 (feePayer.type == FeePayerType::SponsorPreFunded ||
672 feePayer.type == FeePayerType::SponsorCoSigned))
673 {
674 if ((spendable > beast::kZero) && !view().open())
675 return tecINSUFF_FEE;
676
677 return terINSUF_FEE_B;
678 }
679
680 auto const feeAmountAfter = balance - feePaid;
681
682 if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount)
683 {
684 // Because ltSponsorship.sfFeeAmount is soeOptional
685 sle->makeFieldAbsent(feePayer.balanceField);
686 }
687 else
688 {
689 sle->setFieldAmount(feePayer.balanceField, feeAmountAfter);
690 }
691
692 view().update(sle);
693
694 // VFALCO Should we call view().rawDestroyXRP() here as well?
695 return tesSUCCESS;
696}
697
698NotTEC
700{
701 auto const id = tx.getAccountID(sfAccount);
702
703 auto const sle = view.read(keylet::account(id));
704
705 if (!sle)
706 {
707 JLOG(j.trace()) << "applyTransaction: delay: source account does not exist "
708 << toBase58(id);
709 return terNO_ACCOUNT;
710 }
711
712 SeqProxy const tSeqProx = tx.getSeqProxy();
713 SeqProxy const aSeq = SeqProxy::rawSequence((*sle)[sfSequence]);
714
715 if (tSeqProx.isSeq())
716 {
717 if (tx.isFieldPresent(sfTicketSequence))
718 {
719 JLOG(j.trace()) << "applyTransaction: has both a TicketSequence "
720 "and a non-zero Sequence number";
721 return temSEQ_AND_TICKET;
722 }
723 if (tSeqProx != aSeq)
724 {
725 if (aSeq < tSeqProx)
726 {
727 JLOG(j.trace()) << "applyTransaction: has future sequence number "
728 << "a_seq=" << aSeq << " t_seq=" << tSeqProx;
729 return terPRE_SEQ;
730 }
731 // It's an already-used sequence number.
732 JLOG(j.trace()) << "applyTransaction: has past sequence number "
733 << "a_seq=" << aSeq << " t_seq=" << tSeqProx;
734 return tefPAST_SEQ;
735 }
736 }
737 else if (tSeqProx.isTicket())
738 {
739 // Bypass the type comparison. Apples and oranges.
740 if (aSeq.value() <= tSeqProx.value())
741 {
742 // If the Ticket number is greater than or equal to the
743 // account sequence there's the possibility that the
744 // transaction to create the Ticket has not hit the ledger
745 // yet. Allow a retry.
746 JLOG(j.trace()) << "applyTransaction: has future ticket id "
747 << "a_seq=" << aSeq << " t_seq=" << tSeqProx;
748 return terPRE_TICKET;
749 }
750
751 // Transaction can never succeed if the Ticket is not in the ledger.
752 if (!view.exists(keylet::ticket(id, tSeqProx)))
753 {
754 JLOG(j.trace()) << "applyTransaction: ticket already used or never created "
755 << "a_seq=" << aSeq << " t_seq=" << tSeqProx;
756 return tefNO_TICKET;
757 }
758 }
759
760 return tesSUCCESS;
761}
762
763NotTEC
765{
766 auto const id = ctx.tx.getAccountID(sfAccount);
767
768 auto const sle = ctx.view.read(keylet::account(id));
769
770 if (!sle)
771 {
772 JLOG(ctx.j.trace()) << "applyTransaction: delay: source account does not exist "
773 << toBase58(id);
774 return terNO_ACCOUNT;
775 }
776
777 if (ctx.tx.isFieldPresent(sfAccountTxnID) &&
778 (sle->getFieldH256(sfAccountTxnID) != ctx.tx.getFieldH256(sfAccountTxnID)))
779 return tefWRONG_PRIOR;
780
781 if (ctx.tx.isFieldPresent(sfLastLedgerSequence) &&
782 (ctx.view.seq() > ctx.tx.getFieldU32(sfLastLedgerSequence)))
783 return tefMAX_LEDGER;
784
785 if (ctx.view.txExists(ctx.tx.getTransactionID()))
786 return tefALREADY;
787
788 return tesSUCCESS;
789}
790
791TER
793{
794 XRPL_ASSERT(sleAccount, "xrpl::Transactor::consumeSeqProxy : non-null account");
795 SeqProxy const seqProxy = ctx_.tx.getSeqProxy();
796 if (seqProxy.isSeq())
797 {
798 // Note that if this transaction is a TicketCreate, then
799 // the transaction will modify the account root sfSequence
800 // yet again.
801 sleAccount->setFieldU32(sfSequence, seqProxy.value() + 1);
802 return tesSUCCESS;
803 }
804 auto const keylet = keylet::ticket(accountID_, seqProxy);
805 return ticketDelete(view(), accountID_, keylet.key, j_);
806}
807
808// Remove a single Ticket from the ledger.
809TER
812 AccountID const& account,
813 UInt256 const& ticketIndex,
815{
816 // Delete the Ticket, adjust the account root ticket count, and
817 // reduce the owner count.
818 SLE::pointer const sleTicket = view.peek(keylet::ticket(ticketIndex));
819 if (!sleTicket)
820 {
821 // LCOV_EXCL_START
822 JLOG(j.fatal()) << "Ticket disappeared from ledger.";
823 return tefBAD_LEDGER;
824 // LCOV_EXCL_STOP
825 }
826
827 std::uint64_t const page{(*sleTicket)[sfOwnerNode]};
828 if (!view.dirRemove(keylet::ownerDir(account), page, ticketIndex, true))
829 {
830 // LCOV_EXCL_START
831 JLOG(j.fatal()) << "Unable to delete Ticket from owner.";
832 return tefBAD_LEDGER;
833 // LCOV_EXCL_STOP
834 }
835
836 // Update the account root's TicketCount. If the ticket count drops to
837 // zero remove the (optional) field.
838 auto sleAccount = view.peek(keylet::account(account));
839 if (!sleAccount)
840 {
841 // LCOV_EXCL_START
842 JLOG(j.fatal()) << "Could not find Ticket owner account root.";
843 return tefBAD_LEDGER;
844 // LCOV_EXCL_STOP
845 }
846
847 if (auto ticketCount = (*sleAccount)[~sfTicketCount])
848 {
849 if (*ticketCount == 1)
850 {
851 sleAccount->makeFieldAbsent(sfTicketCount);
852 }
853 else
854 {
855 ticketCount = *ticketCount - 1;
856 }
857 }
858 else
859 {
860 // LCOV_EXCL_START
861 JLOG(j.fatal()) << "TicketCount field missing from account root.";
862 return tefBAD_LEDGER;
863 // LCOV_EXCL_STOP
864 }
865
866 // Update the Ticket owner's reserve.
867 decreaseOwnerCountForObject(view, sleAccount, sleTicket, 1, j);
868
869 // Remove Ticket from ledger.
870 view.erase(sleTicket);
871 return tesSUCCESS;
872}
873
874// check stuff before you bother to lock the ledger
875void
877{
878 XRPL_ASSERT(accountID_ != beast::kZero, "xrpl::Transactor::preCompute : nonzero account");
879}
880
881TER
883{
884 preCompute();
885
886 // If the transactor requires a valid account and the transaction doesn't
887 // list one, preflight will have already a flagged a failure.
888 auto const sle = view().peek(keylet::account(accountID_));
889
890 // sle must exist except for transactions
891 // that allow zero account.
892 XRPL_ASSERT(
893 sle != nullptr || accountID_ == beast::kZero,
894 "xrpl::Transactor::apply : non-null SLE or zero account");
895
896 if (sle)
897 {
898 preFeeBalance_ = STAmount{(*sle)[sfBalance]}.xrp();
899
900 TER result = consumeSeqProxy(sle);
901 if (!isTesSuccess(result))
902 return result;
903
904 result = payFee();
905 if (!isTesSuccess(result))
906 return result;
907
908 if (sle->isFieldPresent(sfAccountTxnID))
909 sle->setFieldH256(sfAccountTxnID, ctx_.tx.getTransactionID());
910
911 view().update(sle);
912 }
913
914 return doApply();
915}
916
917NotTEC
919 ReadView const& view,
920 ApplyFlags flags,
921 std::optional<UInt256 const> const& parentBatchId,
922 AccountID const& idAccount,
923 STObject const& sigObject,
924 beast::Journal const j,
925 bool permitUncreatedAccount)
926{
927 {
928 auto const sle = view.read(keylet::account(idAccount));
929
930 if ((view.rules().enabled(featureLendingProtocol) ||
931 view.rules().enabled(featureBatchV1_1) || view.rules().enabled(fixCleanup3_3_0)) &&
932 isPseudoAccount(sle))
933 {
934 // Pseudo-accounts can't sign transactions. This check is gated on a
935 // few different amendments so that it takes effect as soon as any of
936 // them is activated.
937 return tefBAD_AUTH;
938 }
939 }
940
941 auto const pkSigner = sigObject.getFieldVL(sfSigningPubKey);
942 // Ignore signature check on batch inner transactions
943 if (parentBatchId && view.rules().enabled(featureBatchV1_1))
944 {
945 // Defensive Check: These values are also checked in Batch::preflight
946 if (sigObject.isFieldPresent(sfTxnSignature) || !pkSigner.empty() ||
947 sigObject.isFieldPresent(sfSigners))
948 {
949 return temINVALID_FLAG; // LCOV_EXCL_LINE
950 }
951 return tesSUCCESS;
952 }
953
954 if (((flags & TapDryRun) != 0u) && pkSigner.empty() && !sigObject.isFieldPresent(sfSigners))
955 {
956 // simulate: skip signature validation when neither SigningPubKey nor
957 // Signers are provided
958 return tesSUCCESS;
959 }
960
961 if (sigObject.isFieldPresent(sfSponsorSignature))
962 {
963 // Co-signed sponsorship
964
965 // Sanity check: already checked in preflight1
966 if (!sigObject.isFieldPresent(sfSponsor))
967 return tefINTERNAL; // LCOV_EXCL_LINE
968
969 auto const sponsorID = sigObject.getAccountID(sfSponsor);
970 auto const sponsorSignature = sigObject.getFieldObject(sfSponsorSignature);
971 if (auto const ret = checkSign(view, flags, std::nullopt, sponsorID, sponsorSignature, j);
972 !isTesSuccess(ret))
973 return ret;
974 }
975
976 // If the pk is empty and not simulate or simulate and signers,
977 // then we must be multi-signing.
978 if (sigObject.isFieldPresent(sfSigners))
979 {
980 return checkMultiSign(view, flags, idAccount, sigObject, j);
981 }
982
983 // Check Single Sign
984 XRPL_ASSERT(!pkSigner.empty(), "xrpl::Transactor::checkSign : non-empty signer");
985
986 if (!publicKeyType(makeSlice(pkSigner)))
987 {
988 JLOG(j.trace()) << "checkSign: signing public key type is unknown";
989 return tefBAD_AUTH; // FIXME: should be better error!
990 }
991
992 // Look up the account.
993 auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
994 auto const sleAccount = view.read(keylet::account(idAccount));
995 if (!sleAccount)
996 {
997 // An account that does not exist yet can only be authorized by its own
998 // master key, and only where an un-created signer is permitted (a batch
999 // whose earlier inner creates the account). Otherwise it cannot sign.
1000 if (!permitUncreatedAccount)
1001 return terNO_ACCOUNT;
1002 if (idAccount != idSigner)
1003 return tefBAD_AUTH;
1004 return tesSUCCESS;
1005 }
1006
1007 return checkSingleSign(view, idSigner, idAccount, sleAccount, j);
1008}
1009
1010NotTEC
1012{
1013 auto const idAccount = ctx.tx.isFieldPresent(sfDelegate) ? ctx.tx.getAccountID(sfDelegate)
1014 : ctx.tx.getAccountID(sfAccount);
1015 return checkSign(ctx.view, ctx.flags, ctx.parentBatchId, idAccount, ctx.tx, ctx.j);
1016}
1017
1018NotTEC
1020 ReadView const& view,
1021 AccountID const& idSigner,
1022 AccountID const& idAccount,
1023 SLE::const_pointer sleAccount,
1024 beast::Journal const j)
1025{
1026 bool const isMasterDisabled = sleAccount->isFlag(lsfDisableMaster);
1027
1028 // Signed with regular key.
1029 if ((*sleAccount)[~sfRegularKey] == idSigner)
1030 {
1031 return tesSUCCESS;
1032 }
1033
1034 // Signed with enabled master key.
1035 if (!isMasterDisabled && idAccount == idSigner)
1036 {
1037 return tesSUCCESS;
1038 }
1039
1040 // Signed with disabled master key.
1041 if (isMasterDisabled && idAccount == idSigner)
1042 {
1043 return tefMASTER_DISABLED;
1044 }
1045
1046 // Signed with any other key.
1047 return tefBAD_AUTH;
1048}
1049
1050NotTEC
1052 ReadView const& view,
1053 ApplyFlags flags,
1054 AccountID const& id,
1055 STObject const& sigObject,
1056 beast::Journal const j)
1057{
1058 // Get id's SignerList and Quorum.
1059 STLedgerEntry::const_pointer const sleAccountSigners = view.read(keylet::signerList(id));
1060 // If the signer list doesn't exist the account is not multi-signing.
1061 if (!sleAccountSigners)
1062 {
1063 JLOG(j.trace()) << "applyTransaction: Invalid: Not a multi-signing account.";
1064 return tefNOT_MULTI_SIGNING;
1065 }
1066
1067 // We have plans to support multiple SignerLists in the future. The
1068 // presence and defaulted value of the SignerListID field will enable that.
1069 XRPL_ASSERT(
1070 sleAccountSigners->isFieldPresent(sfSignerListID),
1071 "xrpl::Transactor::checkMultiSign : has signer list ID");
1072 XRPL_ASSERT(
1073 sleAccountSigners->getFieldU32(sfSignerListID) == 0,
1074 "xrpl::Transactor::checkMultiSign : signer list ID is 0");
1075
1076 auto accountSigners = SignerEntries::deserialize(*sleAccountSigners, j, "ledger");
1077 if (!accountSigners)
1078 return accountSigners.error();
1079
1080 // Get the array of transaction signers.
1081 STArray const& txSigners(sigObject.getFieldArray(sfSigners));
1082
1083 // Walk the accountSigners performing a variety of checks and see if
1084 // the quorum is met.
1085
1086 // Both the multiSigners and accountSigners are sorted by account. So
1087 // matching multi-signers to account signers should be a simple
1088 // linear walk. *All* signers must be valid or the transaction fails.
1089 std::uint32_t weightSum = 0;
1090 auto iter = accountSigners->begin();
1091 for (auto const& txSigner : txSigners)
1092 {
1093 AccountID const txSignerAcctID = txSigner.getAccountID(sfAccount);
1094
1095 // Attempt to match the SignerEntry with a Signer;
1096 while (iter->account < txSignerAcctID)
1097 {
1098 if (++iter == accountSigners->end())
1099 {
1100 JLOG(j.trace()) << "applyTransaction: Invalid SigningAccount.Account.";
1101 return tefBAD_SIGNATURE;
1102 }
1103 }
1104 if (iter->account != txSignerAcctID)
1105 {
1106 // The SigningAccount is not in the SignerEntries.
1107 JLOG(j.trace()) << "applyTransaction: Invalid SigningAccount.Account.";
1108 return tefBAD_SIGNATURE;
1109 }
1110
1111 // We found the SigningAccount in the list of valid signers. Now we
1112 // need to compute the accountID that is associated with the signer's
1113 // public key.
1114 auto const spk = txSigner.getFieldVL(sfSigningPubKey);
1115
1116 // spk being non-empty in non-simulate is checked in
1117 // STTx::checkMultiSign
1118 if (!spk.empty() && !publicKeyType(makeSlice(spk)))
1119 {
1120 JLOG(j.trace()) << "checkMultiSign: signing public key type is unknown";
1121 return tefBAD_SIGNATURE;
1122 }
1123
1124 XRPL_ASSERT(
1125 (flags & TapDryRun) || !spk.empty(),
1126 "xrpl::Transactor::checkMultiSign : non-empty signer or "
1127 "simulation");
1128 AccountID const signingAcctIDFromPubKey =
1129 spk.empty() ? txSignerAcctID : calcAccountID(PublicKey(makeSlice(spk)));
1130
1131 // Verify that the signingAcctID and the signingAcctIDFromPubKey
1132 // belong together. Here are the rules:
1133 //
1134 // 1. "Phantom account": an account that is not in the ledger
1135 // A. If signingAcctID == signingAcctIDFromPubKey and the
1136 // signingAcctID is not in the ledger then we have a phantom
1137 // account.
1138 // B. Phantom accounts are always allowed as multi-signers.
1139 //
1140 // 2. "Master Key"
1141 // A. signingAcctID == signingAcctIDFromPubKey, and signingAcctID
1142 // is in the ledger.
1143 // B. If the signingAcctID in the ledger does not have the
1144 // asfDisableMaster flag set, then the signature is allowed.
1145 //
1146 // 3. "Regular Key"
1147 // A. signingAcctID != signingAcctIDFromPubKey, and signingAcctID
1148 // is in the ledger.
1149 // B. If signingAcctIDFromPubKey == signingAcctID.RegularKey (from
1150 // ledger) then the signature is allowed.
1151 //
1152 // No other signatures are allowed. (January 2015)
1153
1154 // In any of these cases we need to know whether the account is in
1155 // the ledger. Determine that now.
1156 auto const sleTxSignerRoot = view.read(keylet::account(txSignerAcctID));
1157
1158 if (signingAcctIDFromPubKey == txSignerAcctID)
1159 {
1160 // Either Phantom or Master. Phantoms automatically pass.
1161 if (sleTxSignerRoot)
1162 {
1163 // Master Key. Account may not have asfDisableMaster set.
1164 std::uint32_t const signerAccountFlags = sleTxSignerRoot->getFieldU32(sfFlags);
1165
1166 if ((signerAccountFlags & lsfDisableMaster) != 0u)
1167 {
1168 JLOG(j.trace()) << "applyTransaction: Signer:Account lsfDisableMaster.";
1169 return tefMASTER_DISABLED;
1170 }
1171 }
1172 }
1173 else
1174 {
1175 // May be a Regular Key. Let's find out.
1176 // Public key must hash to the account's regular key.
1177 if (!sleTxSignerRoot)
1178 {
1179 JLOG(j.trace()) << "applyTransaction: Non-phantom signer "
1180 "lacks account root.";
1181 return tefBAD_SIGNATURE;
1182 }
1183
1184 if (!sleTxSignerRoot->isFieldPresent(sfRegularKey))
1185 {
1186 JLOG(j.trace()) << "applyTransaction: Account lacks RegularKey.";
1187 return tefBAD_SIGNATURE;
1188 }
1189 if (signingAcctIDFromPubKey != sleTxSignerRoot->getAccountID(sfRegularKey))
1190 {
1191 JLOG(j.trace()) << "applyTransaction: Account doesn't match RegularKey.";
1192 return tefBAD_SIGNATURE;
1193 }
1194 }
1195 // The signer is legitimate. Add their weight toward the quorum.
1196 weightSum += iter->weight;
1197 }
1198
1199 // Cannot perform transaction if quorum is not met.
1200 if (weightSum < sleAccountSigners->getFieldU32(sfSignerQuorum))
1201 {
1202 JLOG(j.trace()) << "applyTransaction: Signers failed to meet quorum.";
1203 return tefBAD_QUORUM;
1204 }
1205
1206 // Met the quorum. Continue.
1207 return tesSUCCESS;
1208}
1209
1210//------------------------------------------------------------------------------
1211
1212static void
1214{
1215 int removed = 0;
1216
1217 for (auto const& index : offers)
1218 {
1219 if (auto const sleOffer = view.peek(keylet::offer(index)))
1220 {
1221 // offer is unfunded
1222 offerDelete(view, sleOffer, viewJ);
1223 if (++removed == kUnfundedOfferRemoveLimit)
1224 return;
1225 }
1226 }
1227}
1228
1229static void
1231 ApplyView& view,
1232 std::vector<UInt256> const& offers,
1233 beast::Journal viewJ)
1234{
1235 std::size_t removed = 0;
1236
1237 for (auto const& index : offers)
1238 {
1239 if (auto const offer = view.peek(keylet::nftokenOffer(index)))
1240 {
1241 nft::deleteTokenOffer(view, offer);
1242 if (++removed == kExpiredOfferRemoveLimit)
1243 return;
1244 }
1245 }
1246}
1247
1248static void
1250{
1251 for (auto const& index : creds)
1252 {
1253 if (auto const sle = view.peek(keylet::credential(index)))
1254 {
1255 if (auto const ter = credentials::deleteSLE(view, sle, viewJ); !isTesSuccess(ter))
1256 {
1257 JLOG(viewJ.error())
1258 << "removeExpiredCredentials: failed to delete expired credential. Err: "
1259 << transToken(ter);
1260 }
1261 }
1262 }
1263}
1264
1265static void
1267 ApplyView& view,
1268 std::vector<UInt256> const& trustLines,
1269 beast::Journal viewJ)
1270{
1271 if (trustLines.size() > kMaxDeletableAmmTrustLines)
1272 {
1273 JLOG(viewJ.error()) << "removeDeletedTrustLines: deleted trustlines exceed max "
1274 << trustLines.size();
1275 return;
1276 }
1277
1278 for (auto const& index : trustLines)
1279 {
1280 if (auto const sleState = view.peek({ltRIPPLE_STATE, index});
1281 !isTesSuccess(deleteAMMTrustLine(view, sleState, std::nullopt, viewJ)))
1282 {
1283 JLOG(viewJ.error()) << "removeDeletedTrustLines: failed to delete AMM trustline";
1284 }
1285 }
1286}
1287
1296{
1297 ctx_.discard();
1298
1299 auto const txnAcct = view().peek(keylet::account(ctx_.tx.getAccountID(sfAccount)));
1300
1301 // The account should never be missing from the ledger. But if it
1302 // is missing then we can't very well charge it a fee, can we?
1303 if (!txnAcct)
1304 return {tefINTERNAL, beast::kZero};
1305
1306 auto const feePayer = getFeePayer(view(), ctx_.tx);
1307 auto const payerSle = view().peek(feePayer.keylet);
1308
1309 if (!payerSle)
1310 return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE
1311
1312 XRPAmount balance = beast::kZero;
1313 if (payerSle->isFieldPresent(feePayer.balanceField))
1314 {
1315 balance = payerSle->getFieldAmount(feePayer.balanceField).xrp();
1316 }
1317 else if (feePayer.balanceField != sfFeeAmount)
1318 {
1319 return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE
1320 }
1321
1322 if (feePayer.type == FeePayerType::SponsorPreFunded && payerSle->isFieldPresent(sfMaxFee))
1323 {
1324 auto const cap = payerSle->getFieldAmount(sfMaxFee).xrp();
1325 fee = std::min(fee, cap);
1326 }
1327
1328 // A co-signed sponsor must never be charged into its own account reserve,
1329 // so the fee is capped to the balance above the reserve rather than to the
1330 // full balance.
1331 XRPAmount spendable = balance;
1332 if (feePayer.type == FeePayerType::SponsorCoSigned)
1333 {
1334 auto const sponsorReserve = accountReserve(view(), payerSle, j_);
1335 // max(balance - reserve, 0) with overflow handling
1336 spendable = balance > sponsorReserve ? balance - sponsorReserve : beast::kZero;
1337 }
1338
1339 // balance should have already been checked in checkFee / preFlight.
1340 XRPL_ASSERT(
1341 (fee == beast::kZero || balance != beast::kZero) && (!view().open() || balance >= fee),
1342 "xrpl::Transactor::reset : valid balance");
1343
1344 // We retry/reject the transaction if the account balance is zero or
1345 // we're applying against an open ledger and the balance is less than
1346 // the fee
1347 if (fee > spendable)
1348 fee = spendable;
1349
1350 // Since we reset the context, we need to charge the fee and update
1351 // the account's sequence number (or consume the Ticket) again.
1352 //
1353 // If for some reason we are unable to consume the ticket or sequence
1354 // then the ledger is corrupted. Rather than make things worse we
1355 // reject the transaction.
1356 auto const feeAmountAfter = balance - fee;
1357 if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount)
1358 {
1359 // Because ltSponsorship.sfFeeAmount is soeOptional
1360 payerSle->makeFieldAbsent(feePayer.balanceField);
1361 }
1362 else
1363 {
1364 payerSle->setFieldAmount(feePayer.balanceField, feeAmountAfter);
1365 }
1366
1367 TER const ter{consumeSeqProxy(txnAcct)};
1368 XRPL_ASSERT(isTesSuccess(ter), "xrpl::Transactor::reset : result is tesSUCCESS");
1369
1370 if (isTesSuccess(ter))
1371 {
1372 view().update(txnAcct);
1373 if (payerSle != txnAcct)
1374 view().update(payerSle);
1375 }
1376
1377 return {ter, fee};
1378}
1379
1382{
1383 if (tx.isFieldPresent(sfSponsor) && isFeeSponsored(tx))
1384 {
1385 auto const sponsorID = tx.getAccountID(sfSponsor);
1386 auto const sponseeID = tx.getInitiator();
1387 auto const sponsorshipKeylet = keylet::sponsorship(sponsorID, sponseeID);
1388
1389 // if pre-funded sponsorship exists, prefer it
1390 if (view.exists(sponsorshipKeylet))
1391 {
1392 // pre funded
1393 return FeePayer{
1394 .id = sponsorID,
1395 .keylet = sponsorshipKeylet,
1396 .balanceField = sfFeeAmount,
1398 }
1399
1400 // Checked in Transactor::checkSponsor
1401 XRPL_ASSERT(
1402 tx.isFieldPresent(sfSponsorSignature),
1403 "xrpl::getFeePayer has sponsor signature without a sponsorship object");
1404
1405 // co-signed
1406 return FeePayer{
1407 .id = sponsorID,
1408 .keylet = keylet::account(sponsorID),
1409 .balanceField = sfBalance,
1411 }
1412
1413 AccountID const payerID = tx.getInitiator();
1414 auto const payerAccountKeylet = keylet::account(payerID);
1415 auto const payerType =
1417
1418 return FeePayer{
1419 .id = payerID, .keylet = payerAccountKeylet, .balanceField = sfBalance, .type = payerType};
1420}
1421
1422// The sole purpose of this function is to provide a convenient, named
1423// location to set a breakpoint, to be used when replaying transactions.
1424void
1426{
1427 JLOG(j_.debug()) << "Transaction trapped: " << txHash;
1428}
1429
1432{
1433 JLOG(j_.trace()) << "reapplying because of " << transToken(result);
1434
1435 // FIXME: This mechanism for doing work while returning a `tec` is
1436 // awkward and very limiting. A more general purpose approach
1437 // should be used, making it possible to do more useful work
1438 // when transactions fail with a `tec` code.
1439
1440 auto typesForResult = [](TER const ter) {
1442 if ((ter == tecOVERSIZE) || (ter == tecKILLED))
1443 {
1444 types.insert(ltOFFER);
1445 }
1446 else if (ter == tecINCOMPLETE)
1447 {
1448 types.insert(ltRIPPLE_STATE);
1449 }
1450 else if (ter == tecEXPIRED)
1451 {
1452 types.insert(ltNFTOKEN_OFFER);
1453 types.insert(ltCREDENTIAL);
1454 }
1455 return types;
1456 };
1457
1458 // Build a list of ledger entry types to collect, based on the
1459 // result code. Only deleted objects of these types will be
1460 // re-applied after the context is reset.
1461 auto const typesToCollect = typesForResult(result);
1462
1464 if (!typesToCollect.empty())
1465 {
1466 ctx_.visit(
1467 [&typesToCollect, &deletedObjects](
1468 UInt256 const& index, bool isDelete, SLE::ConstRef before, SLE::ConstRef after) {
1469 if (isDelete)
1470 {
1471 XRPL_ASSERT(
1472 before && after,
1473 "xrpl::Transactor::processPersistentChanges : non-null "
1474 "SLE inputs");
1475 if (before && after)
1476 {
1477 auto const type = before->getType();
1478 if (typesToCollect.contains(type))
1479 {
1480 // For offers, only collect unfunded removals
1481 // (where TakerPays is unchanged)
1482 if (type == ltOFFER &&
1483 before->getFieldAmount(sfTakerPays) !=
1484 after->getFieldAmount(sfTakerPays))
1485 return;
1486
1487 deletedObjects[type].push_back(index);
1488 }
1489 }
1490 }
1491 });
1492 }
1493
1494 // Reset the context, potentially adjusting the fee.
1495 {
1496 auto const resetResult = reset(fee);
1497 if (!isTesSuccess(resetResult.first))
1498 result = resetResult.first;
1499
1500 fee = resetResult.second;
1501 }
1502
1503 // Re-apply the collected deletions, but only if the reset succeeded
1504 // and the post-reset result still allows the same deletion type.
1505 auto const typesToApply = typesForResult(result);
1506 if (isTecClaim(result) && !typesToApply.empty())
1507 {
1508 auto const viewJ = ctx_.registry.get().getJournal("View");
1509 for (auto const& [type, ids] : deletedObjects)
1510 {
1511 if (ids.empty() || !typesToApply.contains(type))
1512 continue;
1513
1514 switch (type)
1515 {
1516 case ltOFFER:
1517 removeUnfundedOffers(view(), ids, viewJ);
1518 break;
1519 case ltNFTOKEN_OFFER:
1520 removeExpiredNFTokenOffers(view(), ids, viewJ);
1521 break;
1522 case ltRIPPLE_STATE:
1523 removeDeletedTrustLines(view(), ids, viewJ);
1524 break;
1525 case ltCREDENTIAL:
1526 removeExpiredCredentials(view(), ids, viewJ);
1527 break;
1528 // LCOV_EXCL_START
1529 default:
1530 UNREACHABLE(
1531 "xrpl::Transactor::processPersistentChanges() : "
1532 "unexpected type");
1533 break;
1534 // LCOV_EXCL_STOP
1535 }
1536 }
1537 }
1538
1539 return {result, fee, isTecClaim(result)};
1540}
1541
1542[[nodiscard]] TER
1544{
1545 if (scope == InvariantScope::Full)
1546 return xrpl::checkInvariants(ctx_, result, fee, *this);
1547
1548 return xrpl::checkInvariants(ctx_, result, fee);
1549}
1550
1551//------------------------------------------------------------------------------
1554{
1555 JLOG(j_.trace()) << "apply: " << ctx_.tx.getTransactionID();
1556
1557 // These global updates really should have been for every Transaction
1558 // step: preflight, preclaim, and doApply. And even calculateBaseFee. See
1559 // with_txn_type().
1560 //
1561 // raii classes for the current ledger rules.
1562 CurrentTransactionRulesGuard const currentTransactionRulesGuard(view().rules());
1563
1564#ifdef DEBUG
1565 {
1566 Serializer ser;
1567 ctx_.tx.add(ser);
1568 SerialIter sit(ser.slice());
1569 STTx const s2(sit);
1570
1571 if (!s2.isEquivalent(ctx_.tx))
1572 {
1573 // LCOV_EXCL_START
1574 JLOG(j_.fatal()) << "Transaction serdes mismatch";
1575 JLOG(j_.fatal()) << ctx_.tx.getJson(JsonOptions::Values::None);
1576 JLOG(j_.fatal()) << s2.getJson(JsonOptions::Values::None);
1577 UNREACHABLE("xrpl::Transactor::operator() : transaction serdes mismatch");
1578 // LCOV_EXCL_STOP
1579 }
1580 }
1581#endif
1582
1583 if (auto const& trap = ctx_.registry.get().getTrapTxID();
1584 trap && *trap == ctx_.tx.getTransactionID())
1585 {
1586 trapTransaction(*trap);
1587 }
1588
1589 auto result = ctx_.preclaimResult;
1590 if (isTesSuccess(result))
1591 result = apply();
1592
1593 // No transaction can return temUNKNOWN from apply,
1594 // and it can't be passed in from a preclaim.
1595 XRPL_ASSERT(result != temUNKNOWN, "xrpl::Transactor::operator() : result is not temUNKNOWN");
1596
1597 if (auto stream = j_.trace())
1598 stream << "preclaim result: " << transToken(result);
1599
1600 auto fee = ctx_.tx.getFieldAmount(sfFee).xrp();
1601 bool const canApply = std::invoke([&result, &fee, this] {
1602 bool canApplyTmp = isTesSuccess(result);
1603
1604 if (ctx_.size() > kOversizeMetaDataCap)
1605 result = tecOVERSIZE;
1606
1607 if (isTecClaim(result) && ((view().flags() & TapFailHard) != 0u))
1608 {
1609 // If the TapFailHard flag is set, a tec result
1610 // must not do anything
1611 ctx_.discard();
1612 canApplyTmp = false;
1613 }
1614 else if (
1615 (result == tecOVERSIZE) || (result == tecKILLED) || (result == tecINCOMPLETE) ||
1616 (result == tecEXPIRED) || (isTecClaimHardFail(result, view().flags())))
1617 {
1618 // This is and must remain the only place where `canApplyTmp` can change from false to
1619 // true. Changing from true to false is no problem.
1620 std::tie(result, fee, canApplyTmp) = processPersistentChanges(result, fee);
1621 }
1622 return canApplyTmp;
1623 });
1624
1625 auto const logger = [this](
1626 TER result,
1627 bool canApply,
1628 std::optional<TxMeta>&& metadata = std::nullopt) -> ApplyResult {
1629 JLOG(j_.trace()) << (canApply ? "applied " : "not applied ") << transToken(result);
1630 return {result, canApply, std::move(metadata)};
1631 };
1632
1633 if (!canApply)
1634 return logger(result, canApply);
1635
1636 // First invariant pass: both protocol and transaction-specific
1637 // checks run against the transaction's tentative outcome. If it
1638 // does not return tecINVARIANT_FAILED, we can proceed to apply the
1639 // tx.
1640 result = checkInvariants(result, fee, InvariantScope::Full);
1641 if (result == tecINVARIANT_FAILED)
1642 {
1643 // Fee-claim reset: roll the transaction's effects back so that
1644 // only the fee deduction remains. This is the reset referenced
1645 // by InvariantScope::ProtocolOnly.
1646 auto const resetResult = reset(fee);
1647 if (!isTesSuccess(resetResult.first))
1648 result = resetResult.first;
1649
1650 fee = resetResult.second;
1651
1652 // Re-check invariants against the post-reset (fee-claim only)
1653 // state. The transaction's effects are gone, so the
1654 // transaction-specific invariants no longer apply and only the
1655 // protocol invariants are re-run. A failure here escalates to
1656 // tefINVARIANT_FAILED and excludes the tx from the ledger.
1657 if (isTesSuccess(result) || isTecClaim(result))
1658 result = checkInvariants(result, fee, InvariantScope::ProtocolOnly);
1659 }
1660
1661 // We ran through the invariant checker, which can, in some cases,
1662 // return a tef error code. Don't apply the transaction in that case.
1663 if (!isTecClaim(result) && !isTesSuccess(result))
1664 return logger(result, false);
1665
1666 std::optional<TxMeta> metadata;
1667
1668 // Transaction succeeded fully or (retries are not allowed and the
1669 // transaction could claim a fee)
1670
1671 // The transactor and invariant checkers guarantee that this will
1672 // *never* trigger but if it, somehow, happens, don't allow a tx
1673 // that charges a negative fee.
1674 if (fee < beast::kZero)
1675 Throw<std::logic_error>("fee charged is negative!");
1676
1677 // Charge whatever fee they specified. The fee has already been
1678 // deducted from the balance of the account that issued the
1679 // transaction. We just need to account for it in the ledger
1680 // header.
1681 if (!view().open() && fee != beast::kZero)
1682 ctx_.destroyXRP(fee);
1683
1684 // Once we call apply, we will no longer be able to look at view()
1685 metadata = ctx_.apply(result);
1686
1687 if ((ctx_.flags() & TapDryRun) != 0u)
1688 return logger(result, false, std::move(metadata));
1689
1690 return logger(result, canApply, std::move(metadata));
1691}
1692
1693} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
Stream fatal() const
Definition Journal.h:368
Stream error() const
Definition Journal.h:362
Stream debug() const
Definition Journal.h:344
Stream trace() const
Severity stream access functions.
Definition Journal.h:338
Stream warn() const
Definition Journal.h:356
State information when applying a tx.
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:141
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void update(SLE::Ref sle)=0
Indicate changes to a peeked SLE.
RAII class to set and restore the current transaction rules.
Definition Rules.h:113
static uint32_t txToPermissionType(TxType type)
static Permission const & getInstance()
A public key.
Definition PublicKey.h:53
A view into a ledger.
Definition ReadView.h:41
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
virtual bool txExists(key_type const &key) const =0
Returns true if a tx exists in the tx map.
virtual bool open() const =0
Returns true if this reflects an open ledger.
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:115
bool enabled(UInt256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:182
XRPAmount xrp() const
Definition STAmount.cpp:272
size_type size() const
Definition STArray.h:248
virtual std::string getFullText() const
Definition STBase.cpp:60
std::shared_ptr< STLedgerEntry > pointer
std::shared_ptr< STLedgerEntry const > const & ConstRef
std::shared_ptr< STLedgerEntry const > const_pointer
Blob getFieldVL(SField const &field) const
Definition STObject.cpp:649
bool isEquivalent(STBase const &t) const override
Definition STObject.cpp:351
UInt256 getFieldH256(SField const &field) const
Definition STObject.cpp:631
std::uint32_t getFieldU32(SField const &field) const
Definition STObject.cpp:601
STArray const & getFieldArray(SField const &field) const
Definition STObject.cpp:688
bool isFlag(std::uint32_t) const
Definition STObject.cpp:511
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:464
STBase const & peekAtField(SField const &field) const
Definition STObject.cpp:409
STObject getFieldObject(SField const &field) const
Definition STObject.cpp:678
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:643
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:657
std::uint32_t getFlags() const
Definition STObject.cpp:517
SeqProxy getSeqProxy() const
Definition STTx.cpp:198
TxType getTxnType() const
Definition STTx.h:250
UInt256 getTransactionID() const
Definition STTx.h:262
json::Value getJson(JsonOptions options) const override
Definition STTx.cpp:351
AccountID getInitiator() const
The account responsible for the authorization: the delegate when sfDelegate is present,...
Definition STTx.cpp:659
A type that represents either a sequence value or a ticket value.
Definition SeqProxy.h:37
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
Definition SeqProxy.h:62
constexpr bool isTicket() const
Definition SeqProxy.h:92
constexpr std::uint32_t value() const
Definition SeqProxy.h:80
constexpr bool isSeq() const
Definition SeqProxy.h:86
Slice slice() const noexcept
Definition Serializer.h:141
Service registry for dependency injection.
virtual LoadFeeTrack & getFeeTrack()=0
static std::expected< std::vector< SignerEntry >, NotTEC > deserialize(STObject const &obj, beast::Journal journal, std::string_view annotation)
An immutable linear range of bytes.
Definition Slice.h:28
bool empty() const noexcept
Return true if the byte range is empty.
Definition Slice.h:58
static NotTEC preflight1(PreflightContext const &ctx, std::uint32_t flagMask)
Performs early sanity checks on the account and fee fields.
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
TER consumeSeqProxy(SLE::pointer const &sleAccount)
static NotTEC checkPermission(ReadView const &view, STTx const &tx, std::unordered_set< GranularPermissionType > &heldGranularPermissions)
static TER checkFee(PreclaimContext const &ctx, XRPAmount baseFee)
beast::WrappedSink sink_
Definition Transactor.h:163
static XRPAmount minimumFee(ServiceRegistry &registry, XRPAmount baseFee, Fees const &fees, ApplyFlags flags)
Compute the minimum fee required to process a transaction with a given baseFee based on the current s...
static NotTEC checkSign(PreclaimContext const &ctx)
static NotTEC checkSponsor(ReadView const &view, STTx const &tx)
static XRPAmount calculateOwnerReserveFee(ReadView const &view, STTx const &tx)
ApplyResult operator()()
Process the transaction.
TER checkInvariants(TER result, XRPAmount fee, InvariantScope scope)
Check all invariants for the current transaction.
void trapTransaction(UInt256) const
static NotTEC preflightSigValidated(PreflightContext const &ctx)
static NotTEC checkSeqProxy(ReadView const &view, STTx const &tx, beast::Journal j)
beast::Journal const j_
Definition Transactor.h:164
virtual TER doApply()=0
InvariantScope
Which invariant layers to check.
Definition Transactor.h:217
static FeePayer getFeePayer(ReadView const &view, STTx const &tx)
static NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
static NotTEC checkSingleSign(ReadView const &view, AccountID const &idSigner, AccountID const &idAccount, SLE::const_pointer sleAccount, beast::Journal const j)
ApplyView & view()
Definition Transactor.h:184
Transactor(Transactor const &)=delete
static NotTEC preflightUniversal(PreflightContext const &ctx)
Universal validations.
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
AccountID const accountID_
Definition Transactor.h:166
static NotTEC checkPriorTxAndLastLedger(PreclaimContext const &ctx)
XRPAmount preFeeBalance_
Definition Transactor.h:167
static NotTEC checkMultiSign(ReadView const &view, ApplyFlags flags, AccountID const &id, STObject const &sigObject, beast::Journal const j)
static bool validDataLength(std::optional< Slice > const &slice, std::size_t maxLength)
virtual void preCompute()
ApplyContext & ctx_
Definition Transactor.h:162
std::pair< TER, XRPAmount > reset(XRPAmount fee)
Reset the context, discarding any changes made and adjust the fee.
static TER ticketDelete(ApplyView &view, AccountID const &account, UInt256 const &ticketIndex, beast::Journal j)
std::tuple< TER, XRPAmount, bool > processPersistentChanges(TER result, XRPAmount fee)
T empty(T... args)
T insert(T... args)
T invoke(T... args)
T min(T... args)
constexpr Zero kZero
Definition Zero.h:30
TER deleteSLE(ApplyView &view, SLE::Ref sleCredential, beast::Journal j)
std::optional< NotTEC > preflightCheckSimulateKeys(ApplyFlags flags, STObject const &sigObject, beast::Journal j)
Checks the special signing key state needed for simulation.
NotTEC preflightCheckSigningKey(STObject const &sigObject, beast::Journal j)
Checks the validity of the transactor signing key.
Keylet computation functions.
Definition Indexes.h:40
Keylet nftokenOffer(AccountID const &owner, SeqProxy const &seq)
An offer from an account to buy or sell an NFT.
Definition Indexes.cpp:453
Keylet offer(AccountID const &id, SeqProxy const &seq) noexcept
An offer from an account.
Definition Indexes.cpp:298
Keylet signerList(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:348
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:403
Keylet ticket(AccountID const &id, SeqProxy const &ticketSeq)
A ticket belonging to an account.
Definition Indexes.cpp:332
Keylet delegate(AccountID const &account, AccountID const &authorizedAccount) noexcept
A keylet for Delegate object.
Definition Indexes.cpp:511
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
Keylet sponsorship(AccountID const &sponsor, AccountID const &sponsee) noexcept
A Sponsorship.
Definition Indexes.cpp:354
Keylet credential(AccountID const &subject, AccountID const &issuer, Slice const &credType) noexcept
Definition Indexes.cpp:585
bool deleteTokenOffer(ApplyView &view, SLE::Ref offer)
Deletes the given token offer.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
static void removeDeletedTrustLines(ApplyView &view, std::vector< UInt256 > const &trustLines, beast::Journal viewJ)
@ telWRONG_NETWORK
Definition TER.h:51
@ telNETWORK_ID_MAKES_TX_NON_CANONICAL
Definition TER.h:53
@ telINSUF_FEE_P
Definition TER.h:43
@ telREQUIRES_NETWORK_ID
Definition TER.h:52
@ terPRE_SEQ
Definition TER.h:222
@ terNO_PERMISSION
Definition TER.h:233
@ terINSUF_FEE_B
Definition TER.h:217
@ terNO_DELEGATE_PERMISSION
Definition TER.h:231
@ terNO_ACCOUNT
Definition TER.h:218
@ terPRE_TICKET
Definition TER.h:227
constexpr FlagValue tfInnerBatchTxn
Definition TxFlags.h:44
TER offerDelete(ApplyView &view, SLE::Ref sle, beast::Journal j)
Delete an offer.
bool isLegalAmount(XRPAmount const &amount)
Returns true if the amount does not exceed the initial XRP in existence.
std::unordered_set< GranularPermissionType > getGranularPermission(SLE::ConstRef delegate, TxType const &type)
Load the granular permissions granted to the delegate account for the specified transaction type.
@ SigBad
Signature is bad.
Definition apply.h:28
TER deleteAMMTrustLine(ApplyView &view, SLE::pointer sleState, std::optional< AccountID > const &ammAccountID, beast::Journal j)
Delete trustline to AMM.
static void removeExpiredNFTokenOffers(ApplyView &view, std::vector< UInt256 > const &offers, beast::Journal viewJ)
bool isTecClaimHardFail(TER ter, ApplyFlags flags)
Return true if the transaction can claim a fee (tec), and the ApplyFlags do not allow soft failures.
Definition applySteps.h:42
std::pair< Validity, std::string > checkValidity(HashRouter &router, STTx const &tx, Rules const &rules)
Checks transaction signature and local checks.
Definition apply.cpp:62
@ tefBAD_QUORUM
Definition TER.h:175
@ tefMAX_LEDGER
Definition TER.h:173
@ tefMASTER_DISABLED
Definition TER.h:172
@ tefALREADY
Definition TER.h:162
@ tefBAD_LEDGER
Definition TER.h:165
@ tefWRONG_PRIOR
Definition TER.h:171
@ tefNO_TICKET
Definition TER.h:180
@ tefBAD_SIGNATURE
Definition TER.h:174
@ tefINTERNAL
Definition TER.h:168
@ tefBAD_AUTH
Definition TER.h:164
@ tefPAST_SEQ
Definition TER.h:170
@ tefNOT_MULTI_SIGNING
Definition TER.h:176
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
NotTEC checkTxPermission(SLE::ConstRef delegate, STTx const &tx)
Check if the delegate account has permission to execute the transaction.
static void removeExpiredCredentials(ApplyView &view, std::vector< UInt256 > const &creds, beast::Journal viewJ)
constexpr std::uint16_t kMaxDeletableAmmTrustLines
The maximum number of trustlines to delete as part of AMM account deletion cleanup.
Definition Protocol.h:409
std::string transToken(TER code)
Definition TER.cpp:257
bool isReserveSponsored(STTx const &tx)
Whether the transaction's reserve is sponsored (sfSponsor present + spfSponsorReserve set).
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
constexpr std::size_t kExpiredOfferRemoveLimit
The maximum number of expired offers to delete at once.
Definition Protocol.h:48
TER checkInvariants(ApplyContext &ctx, TER result, XRPAmount fee, std::optional< std::reference_wrapper< TxInvariantCheck > > txCheck)
Run all protocol invariant checks plus the transaction-specific check in a single pass over the modif...
Slice makeSlice(std::array< T, N > const &a)
Definition Slice.h:228
void open(soci::session &s, BasicConfig const &config, std::string const &dbName)
Open a soci session.
Definition SociDB.cpp:91
bool isReserveSponsorAllowed(TxType txType)
Whether the given transaction type may use reserve sponsorship (v1).
std::string toShortString(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:664
BaseUInt< 256 > UInt256
Definition base_uint.h:580
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:614
static void removeUnfundedOffers(ApplyView &view, std::vector< UInt256 > const &offers, beast::Journal viewJ)
void decreaseOwnerCountForObject(ApplyView &view, SLE::Ref accountSle, SLE::Ref objectSle, std::uint32_t count, beast::Journal j)
Decrease owner-count fields for an existing ledger object.
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:644
bool isFeeSponsored(STTx const &tx)
Whether the transaction's fee is sponsored (sfSponsor present + spfSponsorFee set).
static NotTEC preflight1Sponsor(PreflightContext const &ctx)
AccountID calcAccountID(PublicKey const &pk)
bool hasInvalidAmount(STBase const &field, beast::Journal j)
ApplyFlags
Definition ApplyView.h:27
@ TapDryRun
Definition ApplyView.h:46
@ TapUnlimited
Definition ApplyView.h:39
@ TapFailHard
Definition ApplyView.h:32
@ TapBatch
Definition ApplyView.h:42
bool isPseudoAccount(SLE::const_pointer sleAcct)
Returns true if and only if sleAcct is a pseudo-account of any kind (i.e.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
constexpr FlagValue spfSponsorFlagMask
Definition TxFlags.h:467
@ temBAD_FEE
Definition TER.h:80
@ temINVALID
Definition TER.h:98
@ temINVALID_FLAG
Definition TER.h:99
@ temBAD_SRC_ACCOUNT
Definition TER.h:94
@ temMALFORMED
Definition TER.h:75
@ temSEQ_AND_TICKET
Definition TER.h:114
@ temDISABLED
Definition TER.h:102
@ temUNKNOWN
Definition TER.h:112
@ temBAD_AMOUNT
Definition TER.h:77
@ temBAD_SIGNATURE
Definition TER.h:93
@ temINVALID_INNER_BATCH
Definition TER.h:131
@ temBAD_SIGNER
Definition TER.h:103
XRPAmount scaleFeeLoad(XRPAmount fee, LoadFeeTrack const &feeTrack, Fees const &fees, bool bUnlimited)
bool isTesSuccess(TER x) noexcept
Definition TER.h:683
TERSubset< CanCvtToTER > TER
Definition TER.h:654
XRPAmount accountReserve(ReadView const &view, SLE::ConstRef sle, beast::Journal j, Adjustment adj={})
Returns the account reserve, in drops.
NotTEC preflight0(PreflightContext const &ctx, std::uint32_t flagMask)
Performs early sanity checks on the txid and flags.
@ tecINSUFF_FEE
Definition TER.h:310
@ tecINCOMPLETE
Definition TER.h:343
@ tecINVARIANT_FAILED
Definition TER.h:321
@ tecEXPIRED
Definition TER.h:322
@ tecKILLED
Definition TER.h:324
@ tecOVERSIZE
Definition TER.h:319
bool isTecClaim(TER x) noexcept
Definition TER.h:690
constexpr std::size_t kUnfundedOfferRemoveLimit
The maximum number of unfunded offers to delete at once.
Definition Protocol.h:43
constexpr std::size_t kOversizeMetaDataCap
The maximum number of metadata entries allowed in one transaction.
Definition Protocol.h:53
bool isPseudoTx(STObject const &tx)
Check whether a transaction is a pseudo-transaction.
Definition STTx.cpp:889
constexpr FlagValue tfUniversalMask
Definition TxFlags.h:46
@ tesSUCCESS
Definition TER.h:250
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
T size(T... args)
Reflects the fee settings for a particular ledger.
LedgerEntryType type
Definition Keylet.h:22
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:92
ReadView const & view
Definition Transactor.h:95
std::reference_wrapper< ServiceRegistry > registry
Definition Transactor.h:94
std::optional< UInt256 const > const parentBatchId
Definition Transactor.h:99
beast::Journal const j
Definition Transactor.h:100
State information when preflighting a tx.
Definition Transactor.h:39
beast::Journal const j
Definition Transactor.h:46
std::optional< UInt256 const > parentBatchId
Definition Transactor.h:45
std::reference_wrapper< ServiceRegistry > registry
Definition Transactor.h:41
T tie(T... args)