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