xrpld
Loading...
Searching...
No Matches
Transactor.cpp
1#include <xrpl/tx/Transactor.h>
2
3#include <xrpl/basics/Blob.h>
4#include <xrpl/basics/Log.h>
5#include <xrpl/basics/Slice.h>
6#include <xrpl/basics/base_uint.h>
7#include <xrpl/basics/contract.h>
8#include <xrpl/beast/utility/Zero.h>
9#include <xrpl/beast/utility/instrumentation.h>
10#include <xrpl/core/NetworkIDService.h>
11#include <xrpl/core/ServiceRegistry.h>
12#include <xrpl/json/to_string.h> // IWYU pragma: keep
13#include <xrpl/ledger/ApplyView.h>
14#include <xrpl/ledger/ReadView.h>
15#include <xrpl/ledger/helpers/AccountRootHelpers.h>
16#include <xrpl/ledger/helpers/CredentialHelpers.h>
17#include <xrpl/ledger/helpers/DelegateHelpers.h>
18#include <xrpl/ledger/helpers/NFTokenHelpers.h>
19#include <xrpl/ledger/helpers/OfferHelpers.h>
20#include <xrpl/ledger/helpers/RippleStateHelpers.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 <cstddef>
46#include <cstdint>
47#include <exception>
48#include <map>
49#include <optional>
50#include <stdexcept>
51#include <tuple>
52#include <unordered_set>
53#include <utility>
54#include <vector>
55
56namespace xrpl {
57
61{
62 if (isPseudoTx(ctx.tx) && ctx.tx.isFlag(tfInnerBatchTxn))
63 {
64 JLOG(ctx.j.warn()) << "Pseudo transactions cannot contain the "
65 "tfInnerBatchTxn flag.";
66 return temINVALID_FLAG;
67 }
68
69 if (!isPseudoTx(ctx.tx) || ctx.tx.isFieldPresent(sfNetworkID))
70 {
71 uint32_t const nodeNID = ctx.registry.get().getNetworkIDService().getNetworkID();
72 std::optional<uint32_t> txNID = ctx.tx[~sfNetworkID];
73
74 if (nodeNID <= 1024)
75 {
76 // legacy networks have ids less than 1024, these networks cannot
77 // specify NetworkID in txn
78 if (txNID)
80 }
81 else
82 {
83 // new networks both require the field to be present and require it
84 // to match
85 if (!txNID)
87
88 if (*txNID != nodeNID)
89 return telWRONG_NETWORK;
90 }
91 }
92
93 auto const txID = ctx.tx.getTransactionID();
94
95 if (txID == beast::kZero)
96 {
97 JLOG(ctx.j.warn()) << "applyTransaction: transaction id may not be zero";
98 return temINVALID;
99 }
100
101 if ((ctx.tx.getFlags() & flagMask) != 0u)
102 {
103 JLOG(ctx.j.debug()) << ctx.tx.peekAtField(sfTransactionType).getFullText()
104 << ": invalid flags.";
105 return temINVALID_FLAG;
106 }
107
108 return tesSUCCESS;
109}
110
111namespace detail {
112
117NotTEC
119{
120 if (auto const spk = sigObject.getFieldVL(sfSigningPubKey);
121 !spk.empty() && !publicKeyType(makeSlice(spk)))
122 {
123 JLOG(j.debug()) << "preflightCheckSigningKey: invalid signing key";
124 return temBAD_SIGNATURE;
125 }
126 return tesSUCCESS;
127}
128
131{
132 if ((flags & TapDryRun) != 0u) // simulation
133 {
134 std::optional<Slice> const signature = sigObject[~sfTxnSignature];
135 if (signature && !signature->empty())
136 {
137 // NOTE: This code should never be hit because it's checked in the
138 // `simulate` RPC
139 return temINVALID; // LCOV_EXCL_LINE
140 }
141
142 if (!sigObject.isFieldPresent(sfSigners))
143 {
144 // no signers, no signature - a valid simulation
145 return tesSUCCESS;
146 }
147
148 for (auto const& signer : sigObject.getFieldArray(sfSigners))
149 {
150 if (signer.isFieldPresent(sfTxnSignature) && !signer[sfTxnSignature].empty())
151 {
152 // NOTE: This code should never be hit because it's
153 // checked in the `simulate` RPC
154 return temINVALID; // LCOV_EXCL_LINE
155 }
156 }
157
158 Slice const signingPubKey = sigObject[sfSigningPubKey];
159 if (!signingPubKey.empty())
160 {
161 // trying to single-sign _and_ multi-sign a transaction
162 return temINVALID;
163 }
164 return tesSUCCESS;
165 }
166 return {};
167}
168
169} // namespace detail
170
172NotTEC
174{
175 if (ctx.tx.isFieldPresent(sfDelegate))
176 {
177 if (!ctx.rules.enabled(featurePermissionDelegationV1_1))
178 return temDISABLED;
179
180 if (ctx.tx[sfDelegate] == ctx.tx[sfAccount])
181 return temBAD_SIGNER;
182
183 auto const& perm = Permission::getInstance();
184 auto const txType = ctx.tx.getTxnType();
185
186 // If the transaction is not delegable and does not have granular permissions, fail earlier
187 // with temINVALID. This is to prevent transactions that are not delegable at all from
188 // being processed further in the invokeCheckPermission function.
189 if (!perm.isDelegable(Permission::txToPermissionType(txType), ctx.rules) &&
190 !perm.hasGranularPermissions(txType))
191 return temINVALID;
192 }
193
194 if (auto const ret = preflight0(ctx, flagMask))
195 return ret;
196
197 auto const id = ctx.tx.getAccountID(sfAccount);
198 if (id == beast::kZero)
199 {
200 JLOG(ctx.j.warn()) << "preflight1: bad account id";
201 return temBAD_SRC_ACCOUNT;
202 }
203
204 // No point in going any further if the transaction fee is malformed.
205 auto const fee = ctx.tx.getFieldAmount(sfFee);
206 if (!fee.native() || fee.negative() || !isLegalAmount(fee.xrp()))
207 {
208 JLOG(ctx.j.debug()) << "preflight1: invalid fee";
209 return temBAD_FEE;
210 }
211
212 if (auto const ret = detail::preflightCheckSigningKey(ctx.tx, ctx.j))
213 return ret;
214
215 // An AccountTxnID field constrains transaction ordering more than the
216 // Sequence field. Tickets, on the other hand, reduce ordering
217 // constraints. Because Tickets and AccountTxnID work against one
218 // another the combination is unsupported and treated as malformed.
219 //
220 // We return temINVALID for such transactions.
221 if (ctx.tx.getSeqProxy().isTicket() && ctx.tx.isFieldPresent(sfAccountTxnID))
222 return temINVALID;
223
224 if (ctx.tx.isFlag(tfInnerBatchTxn) && !ctx.rules.enabled(featureBatch))
225 return temINVALID_FLAG;
226
227 XRPL_ASSERT(
228 ctx.tx.isFlag(tfInnerBatchTxn) == ctx.parentBatchId.has_value() ||
229 !ctx.rules.enabled(featureBatch),
230 "Inner batch transaction must have a parent batch ID.");
231
232 return tesSUCCESS;
233}
234
236NotTEC
238{
239 if (auto const ret = detail::preflightCheckSimulateKeys(ctx.flags, ctx.tx, ctx.j))
240 {
241 // Skips following checks if the transaction is being simulated,
242 // regardless of success or failure
243 return *ret;
244 }
245
246 // It should be impossible for the InnerBatchTxn flag to be set without
247 // featureBatch being enabled
248 XRPL_ASSERT_PARTS(
249 !ctx.tx.isFlag(tfInnerBatchTxn) || ctx.rules.enabled(featureBatch),
250 "xrpl::Transactor::preflight2",
251 "InnerBatch flag only set if feature enabled");
252 // Skip signature check on batch inner transactions
253 if (ctx.tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatch))
254 return tesSUCCESS;
255 // Do not add any checks after this point that are relevant for
256 // batch inner transactions. They will be skipped.
257
258 auto const sigValid = checkValidity(ctx.registry.get().getHashRouter(), ctx.tx, ctx.rules);
259 if (sigValid.first == Validity::SigBad)
260 { // LCOV_EXCL_START
261 JLOG(ctx.j.debug()) << "preflight2: bad signature. " << sigValid.second;
262 return temINVALID;
263 // LCOV_EXCL_STOP
264 }
265
266 // Do not add any checks after this point that are relevant for
267 // batch inner transactions. They will be skipped.
268
269 return tesSUCCESS;
270}
271
272NotTEC
274{
275 if (ctx.rules.enabled(fixCleanup3_2_0) && hasInvalidAmount(ctx.tx, ctx.j))
276 return temBAD_AMOUNT;
277
278 return tesSUCCESS;
279}
280
281//------------------------------------------------------------------------------
282
284 : ctx_(ctx)
285 , sink_(ctx.journal, toShortString(ctx.tx.getTransactionID()) + " ")
286 , j_(sink_)
287 , accountID_(ctx.tx.getAccountID(sfAccount))
288{
289}
290
291bool
293{
294 if (!slice)
295 return true;
296 return !slice->empty() && slice->length() <= maxLength;
297}
298
304
305NotTEC
310
311NotTEC
313 ReadView const& view,
314 STTx const& tx,
315 std::unordered_set<GranularPermissionType>& heldGranularPermissions)
316{
317 auto const delegate = tx[~sfDelegate];
318 if (!delegate)
319 return tesSUCCESS;
320
321 auto const sle = view.read(keylet::delegate(tx[sfAccount], *delegate));
322 if (!sle)
324
325 if (isTesSuccess(checkTxPermission(sle, tx)))
326 return tesSUCCESS;
327
328 if (!Permission::getInstance().hasGranularPermissions(tx.getTxnType()))
330
331 heldGranularPermissions = getGranularPermission(sle, tx.getTxnType());
332 if (heldGranularPermissions.empty())
334
335 if (!Permission::getInstance().checkGranularSandbox(tx, heldGranularPermissions))
337
338 return tesSUCCESS;
339}
340
343{
344 // Returns the fee in fee units.
345
346 // The computation has two parts:
347 // * The base fee, which is the same for most transactions.
348 // * The additional cost of each multisignature on the transaction.
349 XRPAmount const baseFee = view.fees().base;
350
351 // Each signer adds one more baseFee to the minimum required fee
352 // for the transaction.
353 std::size_t const signerCount =
354 tx.isFieldPresent(sfSigners) ? tx.getFieldArray(sfSigners).size() : 0;
355
356 return baseFee + (signerCount * baseFee);
357}
358
361 ReadView const& view,
362 STTx const& tx,
363 std::uint32_t extraBaseFeeMultiplier)
364{
365 return calculateBaseFee(view, tx) + view.fees().base * extraBaseFeeMultiplier;
366}
367
368// Returns the fee in fee units, not scaled for load.
371{
372 // Assumption: One reserve increment is typically much greater than one base
373 // fee.
374 // This check is in an assert so that it will come to the attention of
375 // developers if that assumption is not correct. If the owner reserve is not
376 // significantly larger than the base fee (or even worse, smaller), we will
377 // need to rethink charging an owner reserve as a transaction fee.
378 // TODO: This function is static, and I don't want to add more parameters.
379 // When it is finally refactored to be in a context that has access to the
380 // Application, include "app().getOverlay().networkID() > 2 ||" in the
381 // condition.
382 XRPL_ASSERT(
383 view.fees().increment > view.fees().base * 100,
384 "xrpl::Transactor::calculateOwnerReserveFee : Owner reserve is "
385 "reasonable");
386 return view.fees().increment;
387}
388
391 ServiceRegistry& registry,
392 XRPAmount baseFee,
393 Fees const& fees,
394 ApplyFlags flags)
395{
396 return scaleFeeLoad(baseFee, registry.getFeeTrack(), fees, (flags & TapUnlimited) != 0u);
397}
398
399TER
401{
402 if (!ctx.tx[sfFee].native())
403 return temBAD_FEE;
404
405 auto const feePaid = ctx.tx[sfFee].xrp();
406
407 if ((ctx.flags & TapBatch) != 0u)
408 {
409 if (feePaid == beast::kZero)
410 return tesSUCCESS;
411
412 JLOG(ctx.j.trace()) << "Batch: Fee must be zero.";
413 return temBAD_FEE; // LCOV_EXCL_LINE
414 }
415
416 if (!isLegalAmount(feePaid) || feePaid < beast::kZero)
417 return temBAD_FEE;
418
419 // Only check fee is sufficient when the ledger is open.
420 if (ctx.view.open())
421 {
422 auto const feeDue = minimumFee(ctx.registry, baseFee, ctx.view.fees(), ctx.flags);
423
424 if (feePaid < feeDue)
425 {
426 JLOG(ctx.j.trace()) << "Insufficient fee paid: " << to_string(feePaid) << "/"
427 << to_string(feeDue);
428 return telINSUF_FEE_P;
429 }
430 }
431
432 if (feePaid == beast::kZero)
433 return tesSUCCESS;
434
435 auto const id = ctx.tx.getFeePayer();
436 auto const sle = ctx.view.read(keylet::account(id));
437 if (!sle)
438 return terNO_ACCOUNT;
439
440 auto const balance = (*sle)[sfBalance].xrp();
441
442 // NOTE: Because preclaim evaluates against a static readview, it
443 // does not reflect fee deductions from other transactions paid by
444 // the same account within the current ledger.
445 // As a result, if an account's balance is over-committed across multiple
446 // transactions, this check may pass optimistically.
447 // The fee shortfall will be handled by the Transactor::reset mechanism,
448 // which caps the fee to the remaining actual balance.
449 if (balance < feePaid)
450 {
451 JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(balance)
452 << " paid=" << to_string(feePaid);
453
454 if ((balance > beast::kZero) && !ctx.view.open())
455 {
456 // Closed ledger, non-zero balance, less than fee
457 return tecINSUFF_FEE;
458 }
459
460 return terINSUF_FEE_B;
461 }
462
463 return tesSUCCESS;
464}
465
466TER
468{
469 auto const feePaid = ctx_.tx[sfFee].xrp();
470
471 auto const feePayer = ctx_.tx.getFeePayer();
472 auto const sle = view().peek(keylet::account(feePayer));
473 if (!sle)
474 return tefINTERNAL; // LCOV_EXCL_LINE
475
476 // Deduct the fee, so it's not available during the transaction.
477 // Will only write the account back if the transaction succeeds.
478 sle->setFieldAmount(sfBalance, sle->getFieldAmount(sfBalance) - feePaid);
479 if (feePayer != accountID_)
480 view().update(sle); // done in `apply()` for the account
481
482 // VFALCO Should we call view().rawDestroyXRP() here as well?
483 return tesSUCCESS;
484}
485
486NotTEC
488{
489 auto const id = tx.getAccountID(sfAccount);
490
491 auto const sle = view.read(keylet::account(id));
492
493 if (!sle)
494 {
495 JLOG(j.trace()) << "applyTransaction: delay: source account does not exist "
496 << toBase58(id);
497 return terNO_ACCOUNT;
498 }
499
500 SeqProxy const tSeqProx = tx.getSeqProxy();
501 SeqProxy const aSeq = SeqProxy::sequence((*sle)[sfSequence]);
502
503 if (tSeqProx.isSeq())
504 {
505 if (tx.isFieldPresent(sfTicketSequence))
506 {
507 JLOG(j.trace()) << "applyTransaction: has both a TicketSequence "
508 "and a non-zero Sequence number";
509 return temSEQ_AND_TICKET;
510 }
511 if (tSeqProx != aSeq)
512 {
513 if (aSeq < tSeqProx)
514 {
515 JLOG(j.trace()) << "applyTransaction: has future sequence number "
516 << "a_seq=" << aSeq << " t_seq=" << tSeqProx;
517 return terPRE_SEQ;
518 }
519 // It's an already-used sequence number.
520 JLOG(j.trace()) << "applyTransaction: has past sequence number "
521 << "a_seq=" << aSeq << " t_seq=" << tSeqProx;
522 return tefPAST_SEQ;
523 }
524 }
525 else if (tSeqProx.isTicket())
526 {
527 // Bypass the type comparison. Apples and oranges.
528 if (aSeq.value() <= tSeqProx.value())
529 {
530 // If the Ticket number is greater than or equal to the
531 // account sequence there's the possibility that the
532 // transaction to create the Ticket has not hit the ledger
533 // yet. Allow a retry.
534 JLOG(j.trace()) << "applyTransaction: has future ticket id "
535 << "a_seq=" << aSeq << " t_seq=" << tSeqProx;
536 return terPRE_TICKET;
537 }
538
539 // Transaction can never succeed if the Ticket is not in the ledger.
540 if (!view.exists(keylet::ticket(id, tSeqProx)))
541 {
542 JLOG(j.trace()) << "applyTransaction: ticket already used or never created "
543 << "a_seq=" << aSeq << " t_seq=" << tSeqProx;
544 return tefNO_TICKET;
545 }
546 }
547
548 return tesSUCCESS;
549}
550
551NotTEC
553{
554 auto const id = ctx.tx.getAccountID(sfAccount);
555
556 auto const sle = ctx.view.read(keylet::account(id));
557
558 if (!sle)
559 {
560 JLOG(ctx.j.trace()) << "applyTransaction: delay: source account does not exist "
561 << toBase58(id);
562 return terNO_ACCOUNT;
563 }
564
565 if (ctx.tx.isFieldPresent(sfAccountTxnID) &&
566 (sle->getFieldH256(sfAccountTxnID) != ctx.tx.getFieldH256(sfAccountTxnID)))
567 return tefWRONG_PRIOR;
568
569 if (ctx.tx.isFieldPresent(sfLastLedgerSequence) &&
570 (ctx.view.seq() > ctx.tx.getFieldU32(sfLastLedgerSequence)))
571 return tefMAX_LEDGER;
572
573 if (ctx.view.txExists(ctx.tx.getTransactionID()))
574 return tefALREADY;
575
576 return tesSUCCESS;
577}
578
579TER
581{
582 XRPL_ASSERT(sleAccount, "xrpl::Transactor::consumeSeqProxy : non-null account");
583 SeqProxy const seqProx = ctx_.tx.getSeqProxy();
584 if (seqProx.isSeq())
585 {
586 // Note that if this transaction is a TicketCreate, then
587 // the transaction will modify the account root sfSequence
588 // yet again.
589 sleAccount->setFieldU32(sfSequence, seqProx.value() + 1);
590 return tesSUCCESS;
591 }
593}
594
595// Remove a single Ticket from the ledger.
596TER
599 AccountID const& account,
600 uint256 const& ticketIndex,
602{
603 // Delete the Ticket, adjust the account root ticket count, and
604 // reduce the owner count.
605 SLE::pointer const sleTicket = view.peek(keylet::ticket(ticketIndex));
606 if (!sleTicket)
607 {
608 // LCOV_EXCL_START
609 JLOG(j.fatal()) << "Ticket disappeared from ledger.";
610 return tefBAD_LEDGER;
611 // LCOV_EXCL_STOP
612 }
613
614 std::uint64_t const page{(*sleTicket)[sfOwnerNode]};
615 if (!view.dirRemove(keylet::ownerDir(account), page, ticketIndex, true))
616 {
617 // LCOV_EXCL_START
618 JLOG(j.fatal()) << "Unable to delete Ticket from owner.";
619 return tefBAD_LEDGER;
620 // LCOV_EXCL_STOP
621 }
622
623 // Update the account root's TicketCount. If the ticket count drops to
624 // zero remove the (optional) field.
625 auto sleAccount = view.peek(keylet::account(account));
626 if (!sleAccount)
627 {
628 // LCOV_EXCL_START
629 JLOG(j.fatal()) << "Could not find Ticket owner account root.";
630 return tefBAD_LEDGER;
631 // LCOV_EXCL_STOP
632 }
633
634 if (auto ticketCount = (*sleAccount)[~sfTicketCount])
635 {
636 if (*ticketCount == 1)
637 {
638 sleAccount->makeFieldAbsent(sfTicketCount);
639 }
640 else
641 {
642 ticketCount = *ticketCount - 1;
643 }
644 }
645 else
646 {
647 // LCOV_EXCL_START
648 JLOG(j.fatal()) << "TicketCount field missing from account root.";
649 return tefBAD_LEDGER;
650 // LCOV_EXCL_STOP
651 }
652
653 // Update the Ticket owner's reserve.
654 adjustOwnerCount(view, sleAccount, -1, j);
655
656 // Remove Ticket from ledger.
657 view.erase(sleTicket);
658 return tesSUCCESS;
659}
660
661// check stuff before you bother to lock the ledger
662void
664{
665 XRPL_ASSERT(accountID_ != beast::kZero, "xrpl::Transactor::preCompute : nonzero account");
666}
667
668TER
670{
671 preCompute();
672
673 // If the transactor requires a valid account and the transaction doesn't
674 // list one, preflight will have already a flagged a failure.
675 auto const sle = view().peek(keylet::account(accountID_));
676
677 // sle must exist except for transactions
678 // that allow zero account.
679 XRPL_ASSERT(
680 sle != nullptr || accountID_ == beast::kZero,
681 "xrpl::Transactor::apply : non-null SLE or zero account");
682
683 if (sle)
684 {
685 preFeeBalance_ = STAmount{(*sle)[sfBalance]}.xrp();
686
687 TER result = consumeSeqProxy(sle);
688 if (!isTesSuccess(result))
689 return result;
690
691 result = payFee();
692 if (!isTesSuccess(result))
693 return result;
694
695 if (sle->isFieldPresent(sfAccountTxnID))
696 sle->setFieldH256(sfAccountTxnID, ctx_.tx.getTransactionID());
697
698 view().update(sle);
699 }
700
701 return doApply();
702}
703
704NotTEC
706 ReadView const& view,
707 ApplyFlags flags,
708 std::optional<uint256 const> const& parentBatchId,
709 AccountID const& idAccount,
710 STObject const& sigObject,
711 beast::Journal const j)
712{
713 {
714 auto const sle = view.read(keylet::account(idAccount));
715
716 if (view.rules().enabled(featureLendingProtocol) && isPseudoAccount(sle))
717 {
718 // Pseudo-accounts can't sign transactions. This check is gated on
719 // the Lending Protocol amendment because that's the project it was
720 // added under, and it doesn't justify another amendment
721 return tefBAD_AUTH;
722 }
723 }
724
725 auto const pkSigner = sigObject.getFieldVL(sfSigningPubKey);
726 // Ignore signature check on batch inner transactions
727 if (parentBatchId && view.rules().enabled(featureBatch))
728 {
729 // Defensive Check: These values are also checked in Batch::preflight
730 if (sigObject.isFieldPresent(sfTxnSignature) || !pkSigner.empty() ||
731 sigObject.isFieldPresent(sfSigners))
732 {
733 return temINVALID_FLAG; // LCOV_EXCL_LINE
734 }
735 return tesSUCCESS;
736 }
737
738 if (((flags & TapDryRun) != 0u) && pkSigner.empty() && !sigObject.isFieldPresent(sfSigners))
739 {
740 // simulate: skip signature validation when neither SigningPubKey nor
741 // Signers are provided
742 return tesSUCCESS;
743 }
744
745 // If the pk is empty and not simulate or simulate and signers,
746 // then we must be multi-signing.
747 if (sigObject.isFieldPresent(sfSigners))
748 {
749 return checkMultiSign(view, flags, idAccount, sigObject, j);
750 }
751
752 // Check Single Sign
753 XRPL_ASSERT(!pkSigner.empty(), "xrpl::Transactor::checkSign : non-empty signer");
754
755 if (!publicKeyType(makeSlice(pkSigner)))
756 {
757 JLOG(j.trace()) << "checkSign: signing public key type is unknown";
758 return tefBAD_AUTH; // FIXME: should be better error!
759 }
760
761 // Look up the account.
762 auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
763 auto const sleAccount = view.read(keylet::account(idAccount));
764 if (!sleAccount)
765 return terNO_ACCOUNT;
766
767 return checkSingleSign(view, idSigner, idAccount, sleAccount, j);
768}
769
770NotTEC
772{
773 auto const idAccount = ctx.tx.isFieldPresent(sfDelegate) ? ctx.tx.getAccountID(sfDelegate)
774 : ctx.tx.getAccountID(sfAccount);
775 return checkSign(ctx.view, ctx.flags, ctx.parentBatchId, idAccount, ctx.tx, ctx.j);
776}
777
778NotTEC
780{
781 NotTEC ret = tesSUCCESS;
782 STArray const& signers{ctx.tx.getFieldArray(sfBatchSigners)};
783 for (auto const& signer : signers)
784 {
785 auto const idAccount = signer.getAccountID(sfAccount);
786
787 Blob const& pkSigner = signer.getFieldVL(sfSigningPubKey);
788 if (pkSigner.empty())
789 {
790 if (ret = checkMultiSign(ctx.view, ctx.flags, idAccount, signer, ctx.j);
791 !isTesSuccess(ret))
792 return ret;
793 }
794 else
795 {
796 // LCOV_EXCL_START
797 if (!publicKeyType(makeSlice(pkSigner)))
798 return tefBAD_AUTH;
799 // LCOV_EXCL_STOP
800
801 auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
802 auto const sleAccount = ctx.view.read(keylet::account(idAccount));
803
804 // A batch can include transactions from an un-created account ONLY
805 // when the account master key is the signer
806 if (!sleAccount)
807 {
808 if (idAccount != idSigner)
809 return tefBAD_AUTH;
810
811 return tesSUCCESS;
812 }
813
814 if (ret = checkSingleSign(ctx.view, idSigner, idAccount, sleAccount, ctx.j);
815 !isTesSuccess(ret))
816 return ret;
817 }
818 }
819 return ret;
820}
821
822NotTEC
824 ReadView const& view,
825 AccountID const& idSigner,
826 AccountID const& idAccount,
827 SLE::const_pointer sleAccount,
828 beast::Journal const j)
829{
830 bool const isMasterDisabled = sleAccount->isFlag(lsfDisableMaster);
831
832 // Signed with regular key.
833 if ((*sleAccount)[~sfRegularKey] == idSigner)
834 {
835 return tesSUCCESS;
836 }
837
838 // Signed with enabled master key.
839 if (!isMasterDisabled && idAccount == idSigner)
840 {
841 return tesSUCCESS;
842 }
843
844 // Signed with disabled master key.
845 if (isMasterDisabled && idAccount == idSigner)
846 {
847 return tefMASTER_DISABLED;
848 }
849
850 // Signed with any other key.
851 return tefBAD_AUTH;
852}
853
854NotTEC
856 ReadView const& view,
857 ApplyFlags flags,
858 AccountID const& id,
859 STObject const& sigObject,
860 beast::Journal const j)
861{
862 // Get id's SignerList and Quorum.
863 STLedgerEntry::const_pointer const sleAccountSigners = view.read(keylet::signerList(id));
864 // If the signer list doesn't exist the account is not multi-signing.
865 if (!sleAccountSigners)
866 {
867 JLOG(j.trace()) << "applyTransaction: Invalid: Not a multi-signing account.";
869 }
870
871 // We have plans to support multiple SignerLists in the future. The
872 // presence and defaulted value of the SignerListID field will enable that.
873 XRPL_ASSERT(
874 sleAccountSigners->isFieldPresent(sfSignerListID),
875 "xrpl::Transactor::checkMultiSign : has signer list ID");
876 XRPL_ASSERT(
877 sleAccountSigners->getFieldU32(sfSignerListID) == 0,
878 "xrpl::Transactor::checkMultiSign : signer list ID is 0");
879
880 auto accountSigners = SignerEntries::deserialize(*sleAccountSigners, j, "ledger");
881 if (!accountSigners)
882 return accountSigners.error();
883
884 // Get the array of transaction signers.
885 STArray const& txSigners(sigObject.getFieldArray(sfSigners));
886
887 // Walk the accountSigners performing a variety of checks and see if
888 // the quorum is met.
889
890 // Both the multiSigners and accountSigners are sorted by account. So
891 // matching multi-signers to account signers should be a simple
892 // linear walk. *All* signers must be valid or the transaction fails.
893 std::uint32_t weightSum = 0;
894 auto iter = accountSigners->begin();
895 for (auto const& txSigner : txSigners)
896 {
897 AccountID const txSignerAcctID = txSigner.getAccountID(sfAccount);
898
899 // Attempt to match the SignerEntry with a Signer;
900 while (iter->account < txSignerAcctID)
901 {
902 if (++iter == accountSigners->end())
903 {
904 JLOG(j.trace()) << "applyTransaction: Invalid SigningAccount.Account.";
905 return tefBAD_SIGNATURE;
906 }
907 }
908 if (iter->account != txSignerAcctID)
909 {
910 // The SigningAccount is not in the SignerEntries.
911 JLOG(j.trace()) << "applyTransaction: Invalid SigningAccount.Account.";
912 return tefBAD_SIGNATURE;
913 }
914
915 // We found the SigningAccount in the list of valid signers. Now we
916 // need to compute the accountID that is associated with the signer's
917 // public key.
918 auto const spk = txSigner.getFieldVL(sfSigningPubKey);
919
920 // spk being non-empty in non-simulate is checked in
921 // STTx::checkMultiSign
922 if (!spk.empty() && !publicKeyType(makeSlice(spk)))
923 {
924 JLOG(j.trace()) << "checkMultiSign: signing public key type is unknown";
925 return tefBAD_SIGNATURE;
926 }
927
928 XRPL_ASSERT(
929 (flags & TapDryRun) || !spk.empty(),
930 "xrpl::Transactor::checkMultiSign : non-empty signer or "
931 "simulation");
932 AccountID const signingAcctIDFromPubKey =
933 spk.empty() ? txSignerAcctID : calcAccountID(PublicKey(makeSlice(spk)));
934
935 // Verify that the signingAcctID and the signingAcctIDFromPubKey
936 // belong together. Here are the rules:
937 //
938 // 1. "Phantom account": an account that is not in the ledger
939 // A. If signingAcctID == signingAcctIDFromPubKey and the
940 // signingAcctID is not in the ledger then we have a phantom
941 // account.
942 // B. Phantom accounts are always allowed as multi-signers.
943 //
944 // 2. "Master Key"
945 // A. signingAcctID == signingAcctIDFromPubKey, and signingAcctID
946 // is in the ledger.
947 // B. If the signingAcctID in the ledger does not have the
948 // asfDisableMaster flag set, then the signature is allowed.
949 //
950 // 3. "Regular Key"
951 // A. signingAcctID != signingAcctIDFromPubKey, and signingAcctID
952 // is in the ledger.
953 // B. If signingAcctIDFromPubKey == signingAcctID.RegularKey (from
954 // ledger) then the signature is allowed.
955 //
956 // No other signatures are allowed. (January 2015)
957
958 // In any of these cases we need to know whether the account is in
959 // the ledger. Determine that now.
960 auto const sleTxSignerRoot = view.read(keylet::account(txSignerAcctID));
961
962 if (signingAcctIDFromPubKey == txSignerAcctID)
963 {
964 // Either Phantom or Master. Phantoms automatically pass.
965 if (sleTxSignerRoot)
966 {
967 // Master Key. Account may not have asfDisableMaster set.
968 std::uint32_t const signerAccountFlags = sleTxSignerRoot->getFieldU32(sfFlags);
969
970 if ((signerAccountFlags & lsfDisableMaster) != 0u)
971 {
972 JLOG(j.trace()) << "applyTransaction: Signer:Account lsfDisableMaster.";
973 return tefMASTER_DISABLED;
974 }
975 }
976 }
977 else
978 {
979 // May be a Regular Key. Let's find out.
980 // Public key must hash to the account's regular key.
981 if (!sleTxSignerRoot)
982 {
983 JLOG(j.trace()) << "applyTransaction: Non-phantom signer "
984 "lacks account root.";
985 return tefBAD_SIGNATURE;
986 }
987
988 if (!sleTxSignerRoot->isFieldPresent(sfRegularKey))
989 {
990 JLOG(j.trace()) << "applyTransaction: Account lacks RegularKey.";
991 return tefBAD_SIGNATURE;
992 }
993 if (signingAcctIDFromPubKey != sleTxSignerRoot->getAccountID(sfRegularKey))
994 {
995 JLOG(j.trace()) << "applyTransaction: Account doesn't match RegularKey.";
996 return tefBAD_SIGNATURE;
997 }
998 }
999 // The signer is legitimate. Add their weight toward the quorum.
1000 weightSum += iter->weight;
1001 }
1002
1003 // Cannot perform transaction if quorum is not met.
1004 if (weightSum < sleAccountSigners->getFieldU32(sfSignerQuorum))
1005 {
1006 JLOG(j.trace()) << "applyTransaction: Signers failed to meet quorum.";
1007 return tefBAD_QUORUM;
1008 }
1009
1010 // Met the quorum. Continue.
1011 return tesSUCCESS;
1012}
1013
1014//------------------------------------------------------------------------------
1015
1016static void
1018{
1019 int removed = 0;
1020
1021 for (auto const& index : offers)
1022 {
1023 if (auto const sleOffer = view.peek(keylet::offer(index)))
1024 {
1025 // offer is unfunded
1026 offerDelete(view, sleOffer, viewJ);
1027 if (++removed == kUnfundedOfferRemoveLimit)
1028 return;
1029 }
1030 }
1031}
1032
1033static void
1035 ApplyView& view,
1036 std::vector<uint256> const& offers,
1037 beast::Journal viewJ)
1038{
1039 std::size_t removed = 0;
1040
1041 for (auto const& index : offers)
1042 {
1043 if (auto const offer = view.peek(keylet::nftokenOffer(index)))
1044 {
1045 nft::deleteTokenOffer(view, offer);
1046 if (++removed == kExpiredOfferRemoveLimit)
1047 return;
1048 }
1049 }
1050}
1051
1052static void
1054{
1055 for (auto const& index : creds)
1056 {
1057 if (auto const sle = view.peek(keylet::credential(index)))
1058 {
1059 if (auto const ter = credentials::deleteSLE(view, sle, viewJ); !isTesSuccess(ter))
1060 {
1061 JLOG(viewJ.error())
1062 << "removeExpiredCredentials: failed to delete expired credential. Err: "
1063 << transToken(ter);
1064 }
1065 }
1066 }
1067}
1068
1069static void
1071 ApplyView& view,
1072 std::vector<uint256> const& trustLines,
1073 beast::Journal viewJ)
1074{
1075 if (trustLines.size() > kMaxDeletableAmmTrustLines)
1076 {
1077 JLOG(viewJ.error()) << "removeDeletedTrustLines: deleted trustlines exceed max "
1078 << trustLines.size();
1079 return;
1080 }
1081
1082 for (auto const& index : trustLines)
1083 {
1084 if (auto const sleState = view.peek({ltRIPPLE_STATE, index});
1085 !isTesSuccess(deleteAMMTrustLine(view, sleState, std::nullopt, viewJ)))
1086 {
1087 JLOG(viewJ.error()) << "removeDeletedTrustLines: failed to delete AMM trustline";
1088 }
1089 }
1090}
1091
1099{
1100 ctx_.discard();
1101
1102 auto const txnAcct = view().peek(keylet::account(ctx_.tx.getAccountID(sfAccount)));
1103
1104 // The account should never be missing from the ledger. But if it
1105 // is missing then we can't very well charge it a fee, can we?
1106 if (!txnAcct)
1107 return {tefINTERNAL, beast::kZero};
1108
1109 auto const payerSle = view().peek(keylet::account(ctx_.tx.getFeePayer()));
1110 if (!payerSle)
1111 return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE
1112
1113 auto const balance = payerSle->getFieldAmount(sfBalance).xrp();
1114
1115 // balance should have already been checked in checkFee / preFlight.
1116 XRPL_ASSERT(
1117 balance != beast::kZero && (!view().open() || balance >= fee),
1118 "xrpl::Transactor::reset : valid balance");
1119
1120 // We retry/reject the transaction if the account balance is zero or
1121 // we're applying against an open ledger and the balance is less than
1122 // the fee
1123 if (fee > balance)
1124 fee = balance;
1125
1126 // Since we reset the context, we need to charge the fee and update
1127 // the account's sequence number (or consume the Ticket) again.
1128 //
1129 // If for some reason we are unable to consume the ticket or sequence
1130 // then the ledger is corrupted. Rather than make things worse we
1131 // reject the transaction.
1132 payerSle->setFieldAmount(sfBalance, balance - fee);
1133 TER const ter{consumeSeqProxy(txnAcct)};
1134 XRPL_ASSERT(isTesSuccess(ter), "xrpl::Transactor::reset : result is tesSUCCESS");
1135
1136 if (isTesSuccess(ter))
1137 {
1138 view().update(txnAcct);
1139 if (payerSle != txnAcct)
1140 view().update(payerSle);
1141 }
1142
1143 return {ter, fee};
1144}
1145
1146// The sole purpose of this function is to provide a convenient, named
1147// location to set a breakpoint, to be used when replaying transactions.
1148void
1150{
1151 JLOG(j_.debug()) << "Transaction trapped: " << txHash;
1152}
1153
1156{
1157 JLOG(j_.trace()) << "reapplying because of " << transToken(result);
1158
1159 // FIXME: This mechanism for doing work while returning a `tec` is
1160 // awkward and very limiting. A more general purpose approach
1161 // should be used, making it possible to do more useful work
1162 // when transactions fail with a `tec` code.
1163
1164 auto typesForResult = [](TER const ter) {
1166 if ((ter == tecOVERSIZE) || (ter == tecKILLED))
1167 {
1168 types.insert(ltOFFER);
1169 }
1170 else if (ter == tecINCOMPLETE)
1171 {
1172 types.insert(ltRIPPLE_STATE);
1173 }
1174 else if (ter == tecEXPIRED)
1175 {
1176 types.insert(ltNFTOKEN_OFFER);
1177 types.insert(ltCREDENTIAL);
1178 }
1179 return types;
1180 };
1181
1182 // Build a list of ledger entry types to collect, based on the
1183 // result code. Only deleted objects of these types will be
1184 // re-applied after the context is reset.
1185 auto const typesToCollect = typesForResult(result);
1186
1188 if (!typesToCollect.empty())
1189 {
1190 ctx_.visit(
1191 [&typesToCollect, &deletedObjects](
1192 uint256 const& index, bool isDelete, SLE::const_ref before, SLE::const_ref after) {
1193 if (isDelete)
1194 {
1195 XRPL_ASSERT(
1196 before && after,
1197 "xrpl::Transactor::processPersistentChanges : non-null "
1198 "SLE inputs");
1199 if (before && after)
1200 {
1201 auto const type = before->getType();
1202 if (typesToCollect.contains(type))
1203 {
1204 // For offers, only collect unfunded removals
1205 // (where TakerPays is unchanged)
1206 if (type == ltOFFER &&
1207 before->getFieldAmount(sfTakerPays) !=
1208 after->getFieldAmount(sfTakerPays))
1209 return;
1210
1211 deletedObjects[type].push_back(index);
1212 }
1213 }
1214 }
1215 });
1216 }
1217
1218 // Reset the context, potentially adjusting the fee.
1219 {
1220 auto const resetResult = reset(fee);
1221 if (!isTesSuccess(resetResult.first))
1222 result = resetResult.first;
1223
1224 fee = resetResult.second;
1225 }
1226
1227 // Re-apply the collected deletions, but only if the reset succeeded
1228 // and the post-reset result still allows the same deletion type.
1229 auto const typesToApply = typesForResult(result);
1230 if (isTecClaim(result) && !typesToApply.empty())
1231 {
1232 auto const viewJ = ctx_.registry.get().getJournal("View");
1233 for (auto const& [type, ids] : deletedObjects)
1234 {
1235 if (ids.empty() || !typesToApply.contains(type))
1236 continue;
1237
1238 switch (type)
1239 {
1240 case ltOFFER:
1241 removeUnfundedOffers(view(), ids, viewJ);
1242 break;
1243 case ltNFTOKEN_OFFER:
1244 removeExpiredNFTokenOffers(view(), ids, viewJ);
1245 break;
1246 case ltRIPPLE_STATE:
1247 removeDeletedTrustLines(view(), ids, viewJ);
1248 break;
1249 case ltCREDENTIAL:
1250 removeExpiredCredentials(view(), ids, viewJ);
1251 break;
1252 // LCOV_EXCL_START
1253 default:
1254 UNREACHABLE(
1255 "xrpl::Transactor::processPersistentChanges() : "
1256 "unexpected type");
1257 break;
1258 // LCOV_EXCL_STOP
1259 }
1260 }
1261 }
1262
1263 return {result, fee, isTecClaim(result)};
1264}
1265
1266[[nodiscard]] TER
1268{
1269 try
1270 {
1271 // Phase 1: visit modified entries
1272 ctx_.visit(
1273 [this](uint256 const&, bool isDelete, SLE::const_ref before, SLE::const_ref after) {
1274 this->visitInvariantEntry(isDelete, before, after);
1275 });
1276
1277 // Phase 2: finalize
1278 if (!this->finalizeInvariants(ctx_.tx, result, fee, ctx_.view(), ctx_.journal))
1279 {
1280 JLOG(ctx_.journal.fatal()) << //
1281 "Transaction has failed one or more transaction invariants, tx: " << //
1283 return tecINVARIANT_FAILED;
1284 }
1285 }
1286 catch (std::exception const& ex)
1287 {
1288 JLOG(ctx_.journal.fatal()) << //
1289 "Exception while checking transaction invariants: " << //
1290 ex.what() << //
1291 ", tx: " << //
1293
1294 return tecINVARIANT_FAILED;
1295 }
1296
1297 return result;
1298}
1299
1300[[nodiscard]] TER
1302{
1303 /*
1304 * DISABLED for 3.2.0 — Must be re-introduced for 3.3.0
1305 *
1306 * Transaction invariants are disabled due to a performance regression:
1307 * the two-pass design (transaction-specific invariants + protocol invariants)
1308 * iterates over modified ledger entries twice per transaction.
1309 *
1310 * Until resolved, only protocol invariants are checked (delegated to ctx_).
1311 * This is safe because all transaction invariants in 3.2.0 are no-ops.
1312 */
1313 return ctx_.checkInvariants(result, fee);
1314}
1315
1316//------------------------------------------------------------------------------
1319{
1320 JLOG(j_.trace()) << "apply: " << ctx_.tx.getTransactionID();
1321
1322 // These global updates really should have been for every Transaction
1323 // step: preflight, preclaim, and doApply. And even calculateBaseFee. See
1324 // with_txn_type().
1325 //
1326 // raii classes for the current ledger rules.
1327 CurrentTransactionRulesGuard const currentTransactionRulesGuard(view().rules());
1328
1329#ifdef DEBUG
1330 {
1331 Serializer ser;
1332 ctx_.tx.add(ser);
1333 SerialIter sit(ser.slice());
1334 STTx const s2(sit);
1335
1336 if (!s2.isEquivalent(ctx_.tx))
1337 {
1338 // LCOV_EXCL_START
1339 JLOG(j_.fatal()) << "Transaction serdes mismatch";
1340 JLOG(j_.fatal()) << ctx_.tx.getJson(JsonOptions::Values::None);
1341 JLOG(j_.fatal()) << s2.getJson(JsonOptions::Values::None);
1342 UNREACHABLE("xrpl::Transactor::operator() : transaction serdes mismatch");
1343 // LCOV_EXCL_STOP
1344 }
1345 }
1346#endif
1347
1348 if (auto const& trap = ctx_.registry.get().getTrapTxID();
1349 trap && *trap == ctx_.tx.getTransactionID())
1350 {
1351 trapTransaction(*trap);
1352 }
1353
1354 auto result = ctx_.preclaimResult;
1355 if (isTesSuccess(result))
1356 result = apply();
1357
1358 // No transaction can return temUNKNOWN from apply,
1359 // and it can't be passed in from a preclaim.
1360 XRPL_ASSERT(result != temUNKNOWN, "xrpl::Transactor::operator() : result is not temUNKNOWN");
1361
1362 if (auto stream = j_.trace())
1363 stream << "preclaim result: " << transToken(result);
1364
1365 bool applied = isTesSuccess(result);
1366 auto fee = ctx_.tx.getFieldAmount(sfFee).xrp();
1367
1368 if (ctx_.size() > kOversizeMetaDataCap)
1369 result = tecOVERSIZE;
1370
1371 if (isTecClaim(result) && ((view().flags() & TapFailHard) != 0u))
1372 {
1373 // If the TapFailHard flag is set, a tec result
1374 // must not do anything
1375 ctx_.discard();
1376 applied = false;
1377 }
1378 else if (
1379 (result == tecOVERSIZE) || (result == tecKILLED) || (result == tecINCOMPLETE) ||
1380 (result == tecEXPIRED) || (isTecClaimHardFail(result, view().flags())))
1381 {
1382 std::tie(result, fee, applied) = processPersistentChanges(result, fee);
1383 }
1384
1385 if (applied)
1386 {
1387 // Check invariants: if `tecINVARIANT_FAILED` is not returned, we can
1388 // proceed to apply the tx
1389 result = checkInvariants(result, fee);
1390 if (result == tecINVARIANT_FAILED)
1391 {
1392 // Reset to fee-claim only
1393 auto const resetResult = reset(fee);
1394 if (!isTesSuccess(resetResult.first))
1395 result = resetResult.first;
1396
1397 fee = resetResult.second;
1398
1399 // Check invariants again to ensure the fee claiming doesn't violate
1400 // invariants. After reset, only protocol invariants are re-checked.
1401 // Transaction invariants are not meaningful here — the transaction's
1402 // effects have been rolled back.
1403 if (isTesSuccess(result) || isTecClaim(result))
1404 result = ctx_.checkInvariants(result, fee);
1405 }
1406
1407 // We ran through the invariant checker, which can, in some cases,
1408 // return a tef error code. Don't apply the transaction in that case.
1409 if (!isTecClaim(result) && !isTesSuccess(result))
1410 applied = false;
1411 }
1412
1413 std::optional<TxMeta> metadata;
1414 if (applied)
1415 {
1416 // Transaction succeeded fully or (retries are not allowed and the
1417 // transaction could claim a fee)
1418
1419 // The transactor and invariant checkers guarantee that this will
1420 // *never* trigger but if it, somehow, happens, don't allow a tx
1421 // that charges a negative fee.
1422 if (fee < beast::kZero)
1423 Throw<std::logic_error>("fee charged is negative!");
1424
1425 // Charge whatever fee they specified. The fee has already been
1426 // deducted from the balance of the account that issued the
1427 // transaction. We just need to account for it in the ledger
1428 // header.
1429 if (!view().open() && fee != beast::kZero)
1430 ctx_.destroyXRP(fee);
1431
1432 // Once we call apply, we will no longer be able to look at view()
1433 metadata = ctx_.apply(result);
1434 }
1435
1436 if ((ctx_.flags() & TapDryRun) != 0u)
1437 {
1438 applied = false;
1439 }
1440
1441 JLOG(j_.trace()) << (applied ? "applied " : "not applied ") << transToken(result);
1442
1443 return {result, applied, metadata};
1444}
1445
1446} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream fatal() const
Definition Journal.h:321
Stream error() const
Definition Journal.h:315
Stream debug() const
Definition Journal.h:297
Stream trace() const
Severity stream access functions.
Definition Journal.h:291
Stream warn() const
Definition Journal.h:309
State information when applying a tx.
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:118
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:104
static uint32_t txToPermissionType(TxType type)
static Permission const & getInstance()
A public key.
Definition PublicKey.h:42
A view into a ledger.
Definition ReadView.h:31
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:97
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:171
XRPAmount xrp() const
Definition STAmount.cpp:271
size_type size() const
Definition STArray.h:240
virtual std::string getFullText() const
Definition STBase.cpp:66
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:639
bool isEquivalent(STBase const &t) const override
Definition STObject.cpp:341
std::uint32_t getFieldU32(SField const &field) const
Definition STObject.cpp:591
STArray const & getFieldArray(SField const &field) const
Definition STObject.cpp:678
bool isFlag(std::uint32_t) const
Definition STObject.cpp:501
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:454
uint256 getFieldH256(SField const &field) const
Definition STObject.cpp:621
STBase const & peekAtField(SField const &field) const
Definition STObject.cpp:399
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:633
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:647
std::uint32_t getFlags() const
Definition STObject.cpp:507
SeqProxy getSeqProxy() const
Definition STTx.cpp:193
AccountID getFeePayer() const
Definition STTx.cpp:216
TxType getTxnType() const
Definition STTx.h:188
json::Value getJson(JsonOptions options) const override
Definition STTx.cpp:316
uint256 getTransactionID() const
Definition STTx.h:200
A type that represents either a sequence value or a ticket value.
Definition SeqProxy.h:36
static constexpr SeqProxy sequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
Definition SeqProxy.h:56
constexpr bool isTicket() const
Definition SeqProxy.h:74
constexpr std::uint32_t value() const
Definition SeqProxy.h:62
constexpr bool isSeq() const
Definition SeqProxy.h:68
Slice slice() const noexcept
Definition Serializer.h:44
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:26
bool empty() const noexcept
Return true if the byte range is empty.
Definition Slice.h:50
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:117
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 XRPAmount calculateOwnerReserveFee(ReadView const &view, STTx const &tx)
ApplyResult operator()()
Process the transaction.
static NotTEC preflightSigValidated(PreflightContext const &ctx)
static NotTEC checkBatchSign(PreclaimContext const &ctx)
static NotTEC checkSeqProxy(ReadView const &view, STTx const &tx, beast::Journal j)
beast::Journal const j_
Definition Transactor.h:118
virtual TER doApply()=0
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:136
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:120
static NotTEC checkPriorTxAndLastLedger(PreclaimContext const &ctx)
XRPAmount preFeeBalance_
Definition Transactor.h:121
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:116
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)
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 ticket(AccountID const &id, std::uint32_t ticketSeq)
A ticket belonging to an account.
Definition Indexes.cpp:295
Keylet signerList(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:316
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:357
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition Indexes.cpp:264
Keylet delegate(AccountID const &account, AccountID const &authorizedAccount) noexcept
A keylet for Delegate object.
Definition Indexes.cpp:465
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
Keylet credential(AccountID const &subject, AccountID const &issuer, Slice const &credType) noexcept
Definition Indexes.cpp:545
Keylet nftokenOffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Definition Indexes.cpp:407
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:49
@ telNETWORK_ID_MAKES_TX_NON_CANONICAL
Definition TER.h:51
@ telINSUF_FEE_P
Definition TER.h:41
@ telREQUIRES_NETWORK_ID
Definition TER.h:50
@ terPRE_SEQ
Definition TER.h:213
@ terINSUF_FEE_B
Definition TER.h:208
@ terNO_DELEGATE_PERMISSION
Definition TER.h:222
@ terNO_ACCOUNT
Definition TER.h:209
@ terPRE_TICKET
Definition TER.h:218
static void removeExpiredNFTokenOffers(ApplyView &view, std::vector< uint256 > const &offers, beast::Journal viewJ)
constexpr FlagValue tfInnerBatchTxn
Definition TxFlags.h:43
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. Didn't do local checks.
Definition apply.h:21
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:28
std::pair< Validity, std::string > checkValidity(HashRouter &router, STTx const &tx, Rules const &rules)
Checks transaction signature and local checks.
Definition apply.cpp:37
uint256 getTicketIndex(AccountID const &account, std::uint32_t uSequence)
Definition Indexes.cpp:159
@ tefBAD_QUORUM
Definition TER.h:170
@ tefMAX_LEDGER
Definition TER.h:168
@ tefMASTER_DISABLED
Definition TER.h:167
@ tefALREADY
Definition TER.h:157
@ tefBAD_LEDGER
Definition TER.h:160
@ tefWRONG_PRIOR
Definition TER.h:166
@ tefNO_TICKET
Definition TER.h:175
@ tefBAD_SIGNATURE
Definition TER.h:169
@ tefINTERNAL
Definition TER.h:163
@ tefBAD_AUTH
Definition TER.h:159
@ tefPAST_SEQ
Definition TER.h:165
@ tefNOT_MULTI_SIGNING
Definition TER.h:171
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:93
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:280
std::string transToken(TER code)
Definition TER.cpp:247
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.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
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:35
void open(soci::session &s, BasicConfig const &config, std::string const &dbName)
Open a soci session.
Definition SociDB.cpp:92
std::string toShortString(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:640
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:594
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:554
void adjustOwnerCount(ApplyView &view, SLE::ref sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
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:12
@ TapDryRun
Definition ApplyView.h:31
@ TapUnlimited
Definition ApplyView.h:24
@ TapFailHard
Definition ApplyView.h:17
@ TapBatch
Definition ApplyView.h:27
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
@ temBAD_FEE
Definition TER.h:78
@ temINVALID
Definition TER.h:96
@ temINVALID_FLAG
Definition TER.h:97
@ temBAD_SRC_ACCOUNT
Definition TER.h:92
@ temSEQ_AND_TICKET
Definition TER.h:112
@ temDISABLED
Definition TER.h:100
@ temUNKNOWN
Definition TER.h:110
@ temBAD_AMOUNT
Definition TER.h:75
@ temBAD_SIGNATURE
Definition TER.h:91
@ temBAD_SIGNER
Definition TER.h:101
XRPAmount scaleFeeLoad(XRPAmount fee, LoadFeeTrack const &feeTrack, Fees const &fees, bool bUnlimited)
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
TERSubset< CanCvtToTER > TER
Definition TER.h:634
NotTEC preflight0(PreflightContext const &ctx, std::uint32_t flagMask)
Performs early sanity checks on the txid and flags.
@ tecINSUFF_FEE
Definition TER.h:300
@ tecINCOMPLETE
Definition TER.h:333
@ tecINVARIANT_FAILED
Definition TER.h:311
@ tecEXPIRED
Definition TER.h:312
@ tecKILLED
Definition TER.h:314
@ tecOVERSIZE
Definition TER.h:309
bool isTecClaim(TER x) noexcept
Definition TER.h:670
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...
std::vector< unsigned char > Blob
Storage for linear binary data.
Definition Blob.h:10
constexpr std::size_t kUnfundedOfferRemoveLimit
The maximum number of unfunded offers to delete at once.
Definition Protocol.h:32
constexpr std::size_t kOversizeMetaDataCap
The maximum number of metadata entries allowed in one transaction.
Definition Protocol.h:38
bool isPseudoTx(STObject const &tx)
Check whether a transaction is a pseudo-transaction.
Definition STTx.cpp:810
BaseUInt< 256 > uint256
Definition base_uint.h:562
constexpr FlagValue tfUniversalMask
Definition TxFlags.h:45
@ tesSUCCESS
Definition TER.h:240
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:49
std::enable_if_t< std::is_same_v< T, char >||std::is_same_v< T, unsigned char >, Slice > makeSlice(std::array< T, N > const &a)
Definition Slice.h:215
T size(T... args)
Reflects the fee settings for a particular ledger.
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
std::reference_wrapper< ServiceRegistry > registry
Definition Transactor.h:63
beast::Journal const j
Definition Transactor.h:69
std::optional< uint256 const > const parentBatchId
Definition Transactor.h:68
State information when preflighting a tx.
Definition Transactor.h:18
beast::Journal const j
Definition Transactor.h:25
std::optional< uint256 const > parentBatchId
Definition Transactor.h:24
std::reference_wrapper< ServiceRegistry > registry
Definition Transactor.h:20
T tie(T... args)
T what(T... args)