rippled
Loading...
Searching...
No Matches
Transactor.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/main/Application.h>
21#include <xrpld/app/misc/CredentialHelpers.h>
22#include <xrpld/app/misc/LoadFeeTrack.h>
23#include <xrpld/app/tx/apply.h>
24#include <xrpld/app/tx/detail/NFTokenUtils.h>
25#include <xrpld/app/tx/detail/SignerEntries.h>
26#include <xrpld/app/tx/detail/Transactor.h>
27#include <xrpld/core/Config.h>
28#include <xrpld/ledger/View.h>
29#include <xrpl/basics/Log.h>
30#include <xrpl/basics/contract.h>
31#include <xrpl/json/to_string.h>
32#include <xrpl/protocol/Feature.h>
33#include <xrpl/protocol/Indexes.h>
34#include <xrpl/protocol/Protocol.h>
35#include <xrpl/protocol/STAccount.h>
36#include <xrpl/protocol/UintTypes.h>
37
38namespace ripple {
39
43{
44 if (!isPseudoTx(ctx.tx) || ctx.tx.isFieldPresent(sfNetworkID))
45 {
46 uint32_t nodeNID = ctx.app.config().NETWORK_ID;
47 std::optional<uint32_t> txNID = ctx.tx[~sfNetworkID];
48
49 if (nodeNID <= 1024)
50 {
51 // legacy networks have ids less than 1024, these networks cannot
52 // specify NetworkID in txn
53 if (txNID)
55 }
56 else
57 {
58 // new networks both require the field to be present and require it
59 // to match
60 if (!txNID)
62
63 if (*txNID != nodeNID)
64 return telWRONG_NETWORK;
65 }
66 }
67
68 auto const txID = ctx.tx.getTransactionID();
69
70 if (txID == beast::zero)
71 {
72 JLOG(ctx.j.warn())
73 << "applyTransaction: transaction id may not be zero";
74 return temINVALID;
75 }
76
77 return tesSUCCESS;
78}
79
83{
84 // This is inappropriate in preflight0, because only Change transactions
85 // skip this function, and those do not allow an sfTicketSequence field.
86 if (ctx.tx.isFieldPresent(sfTicketSequence) &&
87 !ctx.rules.enabled(featureTicketBatch))
88 {
89 return temMALFORMED;
90 }
91
92 auto const ret = preflight0(ctx);
93 if (!isTesSuccess(ret))
94 return ret;
95
96 auto const id = ctx.tx.getAccountID(sfAccount);
97 if (id == beast::zero)
98 {
99 JLOG(ctx.j.warn()) << "preflight1: bad account id";
100 return temBAD_SRC_ACCOUNT;
101 }
102
103 // No point in going any further if the transaction fee is malformed.
104 auto const fee = ctx.tx.getFieldAmount(sfFee);
105 if (!fee.native() || fee.negative() || !isLegalAmount(fee.xrp()))
106 {
107 JLOG(ctx.j.debug()) << "preflight1: invalid fee";
108 return temBAD_FEE;
109 }
110
111 auto const spk = ctx.tx.getSigningPubKey();
112
113 if (!spk.empty() && !publicKeyType(makeSlice(spk)))
114 {
115 JLOG(ctx.j.debug()) << "preflight1: invalid signing key";
116 return temBAD_SIGNATURE;
117 }
118
119 // An AccountTxnID field constrains transaction ordering more than the
120 // Sequence field. Tickets, on the other hand, reduce ordering
121 // constraints. Because Tickets and AccountTxnID work against one
122 // another the combination is unsupported and treated as malformed.
123 //
124 // We return temINVALID for such transactions.
125 if (ctx.tx.getSeqProxy().isTicket() &&
126 ctx.tx.isFieldPresent(sfAccountTxnID))
127 return temINVALID;
128
129 return tesSUCCESS;
130}
131
133NotTEC
135{
136 auto const sigValid = checkValidity(
137 ctx.app.getHashRouter(), ctx.tx, ctx.rules, ctx.app.config());
138 if (sigValid.first == Validity::SigBad)
139 {
140 JLOG(ctx.j.debug()) << "preflight2: bad signature. " << sigValid.second;
141 return temINVALID;
142 }
143 return tesSUCCESS;
144}
145
146//------------------------------------------------------------------------------
147
149 Application& app_,
150 STTx const& tx_,
151 Rules const& rules_,
152 ApplyFlags flags_,
154 : app(app_), tx(tx_), rules(rules_), flags(flags_), j(j_)
155{
156}
157
158//------------------------------------------------------------------------------
159
161 : ctx_(ctx), j_(ctx.journal), account_(ctx.tx.getAccountID(sfAccount))
162{
163}
164
167{
168 // Returns the fee in fee units.
169
170 // The computation has two parts:
171 // * The base fee, which is the same for most transactions.
172 // * The additional cost of each multisignature on the transaction.
173 XRPAmount const baseFee = view.fees().base;
174
175 // Each signer adds one more baseFee to the minimum required fee
176 // for the transaction.
177 std::size_t const signerCount =
178 tx.isFieldPresent(sfSigners) ? tx.getFieldArray(sfSigners).size() : 0;
179
180 return baseFee + (signerCount * baseFee);
181}
182
185 Application& app,
186 XRPAmount baseFee,
187 Fees const& fees,
188 ApplyFlags flags)
189{
190 return scaleFeeLoad(baseFee, app.getFeeTrack(), fees, flags & tapUNLIMITED);
191}
192
193TER
195{
196 if (!ctx.tx[sfFee].native())
197 return temBAD_FEE;
198
199 auto const feePaid = ctx.tx[sfFee].xrp();
200 if (!isLegalAmount(feePaid) || feePaid < beast::zero)
201 return temBAD_FEE;
202
203 // Only check fee is sufficient when the ledger is open.
204 if (ctx.view.open())
205 {
206 auto const feeDue =
207 minimumFee(ctx.app, baseFee, ctx.view.fees(), ctx.flags);
208
209 if (feePaid < feeDue)
210 {
211 JLOG(ctx.j.trace())
212 << "Insufficient fee paid: " << to_string(feePaid) << "/"
213 << to_string(feeDue);
214 return telINSUF_FEE_P;
215 }
216 }
217
218 if (feePaid == beast::zero)
219 return tesSUCCESS;
220
221 auto const id = ctx.tx.getAccountID(sfAccount);
222 auto const sle = ctx.view.read(keylet::account(id));
223 if (!sle)
224 return terNO_ACCOUNT;
225
226 auto const balance = (*sle)[sfBalance].xrp();
227
228 if (balance < feePaid)
229 {
230 JLOG(ctx.j.trace())
231 << "Insufficient balance:" << " balance=" << to_string(balance)
232 << " paid=" << to_string(feePaid);
233
234 if ((balance > beast::zero) && !ctx.view.open())
235 {
236 // Closed ledger, non-zero balance, less than fee
237 return tecINSUFF_FEE;
238 }
239
240 return terINSUF_FEE_B;
241 }
242
243 return tesSUCCESS;
244}
245
246TER
248{
249 auto const feePaid = ctx_.tx[sfFee].xrp();
250
251 auto const sle = view().peek(keylet::account(account_));
252 if (!sle)
253 return tefINTERNAL;
254
255 // Deduct the fee, so it's not available during the transaction.
256 // Will only write the account back if the transaction succeeds.
257
258 mSourceBalance -= feePaid;
259 sle->setFieldAmount(sfBalance, mSourceBalance);
260
261 // VFALCO Should we call view().rawDestroyXRP() here as well?
262
263 return tesSUCCESS;
264}
265
266NotTEC
268 ReadView const& view,
269 STTx const& tx,
271{
272 auto const id = tx.getAccountID(sfAccount);
273
274 auto const sle = view.read(keylet::account(id));
275
276 if (!sle)
277 {
278 JLOG(j.trace())
279 << "applyTransaction: delay: source account does not exist "
280 << toBase58(id);
281 return terNO_ACCOUNT;
282 }
283
284 SeqProxy const t_seqProx = tx.getSeqProxy();
285 SeqProxy const a_seq = SeqProxy::sequence((*sle)[sfSequence]);
286
287 if (t_seqProx.isSeq())
288 {
289 if (tx.isFieldPresent(sfTicketSequence) &&
290 view.rules().enabled(featureTicketBatch))
291 {
292 JLOG(j.trace()) << "applyTransaction: has both a TicketSequence "
293 "and a non-zero Sequence number";
294 return temSEQ_AND_TICKET;
295 }
296 if (t_seqProx != a_seq)
297 {
298 if (a_seq < t_seqProx)
299 {
300 JLOG(j.trace())
301 << "applyTransaction: has future sequence number "
302 << "a_seq=" << a_seq << " t_seq=" << t_seqProx;
303 return terPRE_SEQ;
304 }
305 // It's an already-used sequence number.
306 JLOG(j.trace()) << "applyTransaction: has past sequence number "
307 << "a_seq=" << a_seq << " t_seq=" << t_seqProx;
308 return tefPAST_SEQ;
309 }
310 }
311 else if (t_seqProx.isTicket())
312 {
313 // Bypass the type comparison. Apples and oranges.
314 if (a_seq.value() <= t_seqProx.value())
315 {
316 // If the Ticket number is greater than or equal to the
317 // account sequence there's the possibility that the
318 // transaction to create the Ticket has not hit the ledger
319 // yet. Allow a retry.
320 JLOG(j.trace()) << "applyTransaction: has future ticket id "
321 << "a_seq=" << a_seq << " t_seq=" << t_seqProx;
322 return terPRE_TICKET;
323 }
324
325 // Transaction can never succeed if the Ticket is not in the ledger.
326 if (!view.exists(keylet::ticket(id, t_seqProx)))
327 {
328 JLOG(j.trace())
329 << "applyTransaction: ticket already used or never created "
330 << "a_seq=" << a_seq << " t_seq=" << t_seqProx;
331 return tefNO_TICKET;
332 }
333 }
334
335 return tesSUCCESS;
336}
337
338NotTEC
340{
341 auto const id = ctx.tx.getAccountID(sfAccount);
342
343 auto const sle = ctx.view.read(keylet::account(id));
344
345 if (!sle)
346 {
347 JLOG(ctx.j.trace())
348 << "applyTransaction: delay: source account does not exist "
349 << toBase58(id);
350 return terNO_ACCOUNT;
351 }
352
353 if (ctx.tx.isFieldPresent(sfAccountTxnID) &&
354 (sle->getFieldH256(sfAccountTxnID) !=
355 ctx.tx.getFieldH256(sfAccountTxnID)))
356 return tefWRONG_PRIOR;
357
358 if (ctx.tx.isFieldPresent(sfLastLedgerSequence) &&
359 (ctx.view.seq() > ctx.tx.getFieldU32(sfLastLedgerSequence)))
360 return tefMAX_LEDGER;
361
362 if (ctx.view.txExists(ctx.tx.getTransactionID()))
363 return tefALREADY;
364
365 return tesSUCCESS;
366}
367
368TER
370{
371 XRPL_ASSERT(
372 sleAccount, "ripple::Transactor::consumeSeqProxy : non-null account");
373 SeqProxy const seqProx = ctx_.tx.getSeqProxy();
374 if (seqProx.isSeq())
375 {
376 // Note that if this transaction is a TicketCreate, then
377 // the transaction will modify the account root sfSequence
378 // yet again.
379 sleAccount->setFieldU32(sfSequence, seqProx.value() + 1);
380 return tesSUCCESS;
381 }
382 return ticketDelete(
383 view(), account_, getTicketIndex(account_, seqProx), j_);
384}
385
386// Remove a single Ticket from the ledger.
387TER
389 ApplyView& view,
390 AccountID const& account,
391 uint256 const& ticketIndex,
393{
394 // Delete the Ticket, adjust the account root ticket count, and
395 // reduce the owner count.
396 SLE::pointer const sleTicket = view.peek(keylet::ticket(ticketIndex));
397 if (!sleTicket)
398 {
399 JLOG(j.fatal()) << "Ticket disappeared from ledger.";
400 return tefBAD_LEDGER;
401 }
402
403 std::uint64_t const page{(*sleTicket)[sfOwnerNode]};
404 if (!view.dirRemove(keylet::ownerDir(account), page, ticketIndex, true))
405 {
406 JLOG(j.fatal()) << "Unable to delete Ticket from owner.";
407 return tefBAD_LEDGER;
408 }
409
410 // Update the account root's TicketCount. If the ticket count drops to
411 // zero remove the (optional) field.
412 auto sleAccount = view.peek(keylet::account(account));
413 if (!sleAccount)
414 {
415 JLOG(j.fatal()) << "Could not find Ticket owner account root.";
416 return tefBAD_LEDGER;
417 }
418
419 if (auto ticketCount = (*sleAccount)[~sfTicketCount])
420 {
421 if (*ticketCount == 1)
422 sleAccount->makeFieldAbsent(sfTicketCount);
423 else
424 ticketCount = *ticketCount - 1;
425 }
426 else
427 {
428 JLOG(j.fatal()) << "TicketCount field missing from account root.";
429 return tefBAD_LEDGER;
430 }
431
432 // Update the Ticket owner's reserve.
433 adjustOwnerCount(view, sleAccount, -1, j);
434
435 // Remove Ticket from ledger.
436 view.erase(sleTicket);
437 return tesSUCCESS;
438}
439
440// check stuff before you bother to lock the ledger
441void
443{
444 XRPL_ASSERT(
445 account_ != beast::zero,
446 "ripple::Transactor::preCompute : nonzero account");
447}
448
449TER
451{
452 preCompute();
453
454 // If the transactor requires a valid account and the transaction doesn't
455 // list one, preflight will have already a flagged a failure.
456 auto const sle = view().peek(keylet::account(account_));
457
458 // sle must exist except for transactions
459 // that allow zero account.
460 XRPL_ASSERT(
461 sle != nullptr || account_ == beast::zero,
462 "ripple::Transactor::apply : non-null SLE or zero account");
463
464 if (sle)
465 {
466 mPriorBalance = STAmount{(*sle)[sfBalance]}.xrp();
468
469 TER result = consumeSeqProxy(sle);
470 if (result != tesSUCCESS)
471 return result;
472
473 result = payFee();
474 if (result != tesSUCCESS)
475 return result;
476
477 if (sle->isFieldPresent(sfAccountTxnID))
478 sle->setFieldH256(sfAccountTxnID, ctx_.tx.getTransactionID());
479
480 view().update(sle);
481 }
482
483 return doApply();
484}
485
486NotTEC
488{
489 // If the pk is empty, then we must be multi-signing.
490 if (ctx.tx.getSigningPubKey().empty())
491 return checkMultiSign(ctx);
492
493 return checkSingleSign(ctx);
494}
495
496NotTEC
498{
499 // Check that the value in the signing key slot is a public key.
500 auto const pkSigner = ctx.tx.getSigningPubKey();
501 if (!publicKeyType(makeSlice(pkSigner)))
502 {
503 JLOG(ctx.j.trace())
504 << "checkSingleSign: signing public key type is unknown";
505 return tefBAD_AUTH; // FIXME: should be better error!
506 }
507
508 // Look up the account.
509 auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
510 auto const idAccount = ctx.tx.getAccountID(sfAccount);
511 auto const sleAccount = ctx.view.read(keylet::account(idAccount));
512 if (!sleAccount)
513 return terNO_ACCOUNT;
514
515 bool const isMasterDisabled = sleAccount->isFlag(lsfDisableMaster);
516
517 if (ctx.view.rules().enabled(fixMasterKeyAsRegularKey))
518 {
519 // Signed with regular key.
520 if ((*sleAccount)[~sfRegularKey] == idSigner)
521 {
522 return tesSUCCESS;
523 }
524
525 // Signed with enabled mater key.
526 if (!isMasterDisabled && idAccount == idSigner)
527 {
528 return tesSUCCESS;
529 }
530
531 // Signed with disabled master key.
532 if (isMasterDisabled && idAccount == idSigner)
533 {
534 return tefMASTER_DISABLED;
535 }
536
537 // Signed with any other key.
538 return tefBAD_AUTH;
539 }
540
541 if (idSigner == idAccount)
542 {
543 // Signing with the master key. Continue if it is not disabled.
544 if (isMasterDisabled)
545 return tefMASTER_DISABLED;
546 }
547 else if ((*sleAccount)[~sfRegularKey] == idSigner)
548 {
549 // Signing with the regular key. Continue.
550 }
551 else if (sleAccount->isFieldPresent(sfRegularKey))
552 {
553 // Signing key does not match master or regular key.
554 JLOG(ctx.j.trace())
555 << "checkSingleSign: Not authorized to use account.";
556 return tefBAD_AUTH;
557 }
558 else
559 {
560 // No regular key on account and signing key does not match master key.
561 // FIXME: Why differentiate this case from tefBAD_AUTH?
562 JLOG(ctx.j.trace())
563 << "checkSingleSign: Not authorized to use account.";
564 return tefBAD_AUTH_MASTER;
565 }
566
567 return tesSUCCESS;
568}
569
570NotTEC
572{
573 auto const id = ctx.tx.getAccountID(sfAccount);
574 // Get mTxnAccountID's SignerList and Quorum.
575 std::shared_ptr<STLedgerEntry const> sleAccountSigners =
576 ctx.view.read(keylet::signers(id));
577 // If the signer list doesn't exist the account is not multi-signing.
578 if (!sleAccountSigners)
579 {
580 JLOG(ctx.j.trace())
581 << "applyTransaction: Invalid: Not a multi-signing account.";
583 }
584
585 // We have plans to support multiple SignerLists in the future. The
586 // presence and defaulted value of the SignerListID field will enable that.
587 XRPL_ASSERT(
588 sleAccountSigners->isFieldPresent(sfSignerListID),
589 "ripple::Transactor::checkMultiSign : has signer list ID");
590 XRPL_ASSERT(
591 sleAccountSigners->getFieldU32(sfSignerListID) == 0,
592 "ripple::Transactor::checkMultiSign : signer list ID is 0");
593
594 auto accountSigners =
595 SignerEntries::deserialize(*sleAccountSigners, ctx.j, "ledger");
596 if (!accountSigners)
597 return accountSigners.error();
598
599 // Get the array of transaction signers.
600 STArray const& txSigners(ctx.tx.getFieldArray(sfSigners));
601
602 // Walk the accountSigners performing a variety of checks and see if
603 // the quorum is met.
604
605 // Both the multiSigners and accountSigners are sorted by account. So
606 // matching multi-signers to account signers should be a simple
607 // linear walk. *All* signers must be valid or the transaction fails.
608 std::uint32_t weightSum = 0;
609 auto iter = accountSigners->begin();
610 for (auto const& txSigner : txSigners)
611 {
612 AccountID const txSignerAcctID = txSigner.getAccountID(sfAccount);
613
614 // Attempt to match the SignerEntry with a Signer;
615 while (iter->account < txSignerAcctID)
616 {
617 if (++iter == accountSigners->end())
618 {
619 JLOG(ctx.j.trace())
620 << "applyTransaction: Invalid SigningAccount.Account.";
621 return tefBAD_SIGNATURE;
622 }
623 }
624 if (iter->account != txSignerAcctID)
625 {
626 // The SigningAccount is not in the SignerEntries.
627 JLOG(ctx.j.trace())
628 << "applyTransaction: Invalid SigningAccount.Account.";
629 return tefBAD_SIGNATURE;
630 }
631
632 // We found the SigningAccount in the list of valid signers. Now we
633 // need to compute the accountID that is associated with the signer's
634 // public key.
635 auto const spk = txSigner.getFieldVL(sfSigningPubKey);
636
637 if (!publicKeyType(makeSlice(spk)))
638 {
639 JLOG(ctx.j.trace())
640 << "checkMultiSign: signing public key type is unknown";
641 return tefBAD_SIGNATURE;
642 }
643
644 AccountID const signingAcctIDFromPubKey =
646
647 // Verify that the signingAcctID and the signingAcctIDFromPubKey
648 // belong together. Here are the rules:
649 //
650 // 1. "Phantom account": an account that is not in the ledger
651 // A. If signingAcctID == signingAcctIDFromPubKey and the
652 // signingAcctID is not in the ledger then we have a phantom
653 // account.
654 // B. Phantom accounts are always allowed as multi-signers.
655 //
656 // 2. "Master Key"
657 // A. signingAcctID == signingAcctIDFromPubKey, and signingAcctID
658 // is in the ledger.
659 // B. If the signingAcctID in the ledger does not have the
660 // asfDisableMaster flag set, then the signature is allowed.
661 //
662 // 3. "Regular Key"
663 // A. signingAcctID != signingAcctIDFromPubKey, and signingAcctID
664 // is in the ledger.
665 // B. If signingAcctIDFromPubKey == signingAcctID.RegularKey (from
666 // ledger) then the signature is allowed.
667 //
668 // No other signatures are allowed. (January 2015)
669
670 // In any of these cases we need to know whether the account is in
671 // the ledger. Determine that now.
672 auto sleTxSignerRoot = ctx.view.read(keylet::account(txSignerAcctID));
673
674 if (signingAcctIDFromPubKey == txSignerAcctID)
675 {
676 // Either Phantom or Master. Phantoms automatically pass.
677 if (sleTxSignerRoot)
678 {
679 // Master Key. Account may not have asfDisableMaster set.
680 std::uint32_t const signerAccountFlags =
681 sleTxSignerRoot->getFieldU32(sfFlags);
682
683 if (signerAccountFlags & lsfDisableMaster)
684 {
685 JLOG(ctx.j.trace())
686 << "applyTransaction: Signer:Account lsfDisableMaster.";
687 return tefMASTER_DISABLED;
688 }
689 }
690 }
691 else
692 {
693 // May be a Regular Key. Let's find out.
694 // Public key must hash to the account's regular key.
695 if (!sleTxSignerRoot)
696 {
697 JLOG(ctx.j.trace()) << "applyTransaction: Non-phantom signer "
698 "lacks account root.";
699 return tefBAD_SIGNATURE;
700 }
701
702 if (!sleTxSignerRoot->isFieldPresent(sfRegularKey))
703 {
704 JLOG(ctx.j.trace())
705 << "applyTransaction: Account lacks RegularKey.";
706 return tefBAD_SIGNATURE;
707 }
708 if (signingAcctIDFromPubKey !=
709 sleTxSignerRoot->getAccountID(sfRegularKey))
710 {
711 JLOG(ctx.j.trace())
712 << "applyTransaction: Account doesn't match RegularKey.";
713 return tefBAD_SIGNATURE;
714 }
715 }
716 // The signer is legitimate. Add their weight toward the quorum.
717 weightSum += iter->weight;
718 }
719
720 // Cannot perform transaction if quorum is not met.
721 if (weightSum < sleAccountSigners->getFieldU32(sfSignerQuorum))
722 {
723 JLOG(ctx.j.trace())
724 << "applyTransaction: Signers failed to meet quorum.";
725 return tefBAD_QUORUM;
726 }
727
728 // Met the quorum. Continue.
729 return tesSUCCESS;
730}
731
732//------------------------------------------------------------------------------
733
734static void
736 ApplyView& view,
737 std::vector<uint256> const& offers,
738 beast::Journal viewJ)
739{
740 int removed = 0;
741
742 for (auto const& index : offers)
743 {
744 if (auto const sleOffer = view.peek(keylet::offer(index)))
745 {
746 // offer is unfunded
747 offerDelete(view, sleOffer, viewJ);
748 if (++removed == unfundedOfferRemoveLimit)
749 return;
750 }
751 }
752}
753
754static void
756 ApplyView& view,
757 std::vector<uint256> const& offers,
758 beast::Journal viewJ)
759{
760 std::size_t removed = 0;
761
762 for (auto const& index : offers)
763 {
764 if (auto const offer = view.peek(keylet::nftoffer(index)))
765 {
766 nft::deleteTokenOffer(view, offer);
767 if (++removed == expiredOfferRemoveLimit)
768 return;
769 }
770 }
771}
772
773static void
775 ApplyView& view,
776 std::vector<uint256> const& creds,
777 beast::Journal viewJ)
778{
779 for (auto const& index : creds)
780 {
781 if (auto const sle = view.peek(keylet::credential(index)))
782 credentials::deleteSLE(view, sle, viewJ);
783 }
784}
785
786static void
788 ApplyView& view,
789 std::vector<uint256> const& trustLines,
790 beast::Journal viewJ)
791{
792 if (trustLines.size() > maxDeletableAMMTrustLines)
793 {
794 JLOG(viewJ.error())
795 << "removeDeletedTrustLines: deleted trustlines exceed max "
796 << trustLines.size();
797 return;
798 }
799
800 for (auto const& index : trustLines)
801 {
802 if (auto const sleState = view.peek({ltRIPPLE_STATE, index});
803 deleteAMMTrustLine(view, sleState, std::nullopt, viewJ) !=
805 {
806 JLOG(viewJ.error())
807 << "removeDeletedTrustLines: failed to delete AMM trustline";
808 }
809 }
810}
811
815{
816 ctx_.discard();
817
818 auto const txnAcct =
820 if (!txnAcct)
821 // The account should never be missing from the ledger. But if it
822 // is missing then we can't very well charge it a fee, can we?
823 return {tefINTERNAL, beast::zero};
824
825 auto const balance = txnAcct->getFieldAmount(sfBalance).xrp();
826
827 // balance should have already been checked in checkFee / preFlight.
828 XRPL_ASSERT(
829 balance != beast::zero && (!view().open() || balance >= fee),
830 "ripple::Transactor::reset : valid balance");
831
832 // We retry/reject the transaction if the account balance is zero or we're
833 // applying against an open ledger and the balance is less than the fee
834 if (fee > balance)
835 fee = balance;
836
837 // Since we reset the context, we need to charge the fee and update
838 // the account's sequence number (or consume the Ticket) again.
839 //
840 // If for some reason we are unable to consume the ticket or sequence
841 // then the ledger is corrupted. Rather than make things worse we
842 // reject the transaction.
843 txnAcct->setFieldAmount(sfBalance, balance - fee);
844 TER const ter{consumeSeqProxy(txnAcct)};
845 XRPL_ASSERT(
846 isTesSuccess(ter), "ripple::Transactor::reset : result is tesSUCCESS");
847
848 if (isTesSuccess(ter))
849 view().update(txnAcct);
850
851 return {ter, fee};
852}
853
854// The sole purpose of this function is to provide a convenient, named
855// location to set a breakpoint, to be used when replaying transactions.
856void
858{
859 JLOG(j_.debug()) << "Transaction trapped: " << txHash;
860}
861
862//------------------------------------------------------------------------------
865{
866 JLOG(j_.trace()) << "apply: " << ctx_.tx.getTransactionID();
867
868 // raii classes for the current ledger rules. fixSTAmountCanonicalize and
869 // fixSTAmountCanonicalize predate the rulesGuard and should be replaced.
870 STAmountSO stAmountSO{view().rules().enabled(fixSTAmountCanonicalize)};
871 NumberSO stNumberSO{view().rules().enabled(fixUniversalNumber)};
872 CurrentTransactionRulesGuard currentTransctionRulesGuard(view().rules());
873
874#ifdef DEBUG
875 {
876 Serializer ser;
877 ctx_.tx.add(ser);
878 SerialIter sit(ser.slice());
879 STTx s2(sit);
880
881 if (!s2.isEquivalent(ctx_.tx))
882 {
883 JLOG(j_.fatal()) << "Transaction serdes mismatch";
885 JLOG(j_.fatal()) << s2.getJson(JsonOptions::none);
886 UNREACHABLE(
887 "ripple::Transactor::operator() : transaction serdes mismatch");
888 }
889 }
890#endif
891
892 if (auto const& trap = ctx_.app.trapTxID();
893 trap && *trap == ctx_.tx.getTransactionID())
894 {
895 trapTransaction(*trap);
896 }
897
898 auto result = ctx_.preclaimResult;
899 if (result == tesSUCCESS)
900 result = apply();
901
902 // No transaction can return temUNKNOWN from apply,
903 // and it can't be passed in from a preclaim.
904 XRPL_ASSERT(
905 result != temUNKNOWN,
906 "ripple::Transactor::operator() : result is not temUNKNOWN");
907
908 if (auto stream = j_.trace())
909 stream << "preclaim result: " << transToken(result);
910
911 bool applied = isTesSuccess(result);
912 auto fee = ctx_.tx.getFieldAmount(sfFee).xrp();
913
915 result = tecOVERSIZE;
916
917 if (isTecClaim(result) && (view().flags() & tapFAIL_HARD))
918 {
919 // If the tapFAIL_HARD flag is set, a tec result
920 // must not do anything
921
922 ctx_.discard();
923 applied = false;
924 }
925 else if (
926 (result == tecOVERSIZE) || (result == tecKILLED) ||
927 (result == tecINCOMPLETE) || (result == tecEXPIRED) ||
928 (isTecClaimHardFail(result, view().flags())))
929 {
930 JLOG(j_.trace()) << "reapplying because of " << transToken(result);
931
932 // FIXME: This mechanism for doing work while returning a `tec` is
933 // awkward and very limiting. A more general purpose approach
934 // should be used, making it possible to do more useful work
935 // when transactions fail with a `tec` code.
936 std::vector<uint256> removedOffers;
937 std::vector<uint256> removedTrustLines;
938 std::vector<uint256> expiredNFTokenOffers;
939 std::vector<uint256> expiredCredentials;
940
941 bool const doOffers =
942 ((result == tecOVERSIZE) || (result == tecKILLED));
943 bool const doLines = (result == tecINCOMPLETE);
944 bool const doNFTokenOffers = (result == tecEXPIRED);
945 bool const doCredentials = (result == tecEXPIRED);
946 if (doOffers || doLines || doNFTokenOffers || doCredentials)
947 {
948 ctx_.visit([doOffers,
949 &removedOffers,
950 doLines,
951 &removedTrustLines,
952 doNFTokenOffers,
953 &expiredNFTokenOffers,
954 doCredentials,
955 &expiredCredentials](
956 uint256 const& index,
957 bool isDelete,
958 std::shared_ptr<SLE const> const& before,
960 if (isDelete)
961 {
962 XRPL_ASSERT(
963 before && after,
964 "ripple::Transactor::operator()::visit : non-null SLE "
965 "inputs");
966 if (doOffers && before && after &&
967 (before->getType() == ltOFFER) &&
968 (before->getFieldAmount(sfTakerPays) ==
969 after->getFieldAmount(sfTakerPays)))
970 {
971 // Removal of offer found or made unfunded
972 removedOffers.push_back(index);
973 }
974
975 if (doLines && before && after &&
976 (before->getType() == ltRIPPLE_STATE))
977 {
978 // Removal of obsolete AMM trust line
979 removedTrustLines.push_back(index);
980 }
981
982 if (doNFTokenOffers && before && after &&
983 (before->getType() == ltNFTOKEN_OFFER))
984 expiredNFTokenOffers.push_back(index);
985
986 if (doCredentials && before && after &&
987 (before->getType() == ltCREDENTIAL))
988 expiredCredentials.push_back(index);
989 }
990 });
991 }
992
993 // Reset the context, potentially adjusting the fee.
994 {
995 auto const resetResult = reset(fee);
996 if (!isTesSuccess(resetResult.first))
997 result = resetResult.first;
998
999 fee = resetResult.second;
1000 }
1001
1002 // If necessary, remove any offers found unfunded during processing
1003 if ((result == tecOVERSIZE) || (result == tecKILLED))
1005 view(), removedOffers, ctx_.app.journal("View"));
1006
1007 if (result == tecEXPIRED)
1009 view(), expiredNFTokenOffers, ctx_.app.journal("View"));
1010
1011 if (result == tecINCOMPLETE)
1013 view(), removedTrustLines, ctx_.app.journal("View"));
1014
1015 if (result == tecEXPIRED)
1017 view(), expiredCredentials, ctx_.app.journal("View"));
1018
1019 applied = isTecClaim(result);
1020 }
1021
1022 if (applied)
1023 {
1024 // Check invariants: if `tecINVARIANT_FAILED` is not returned, we can
1025 // proceed to apply the tx
1026 result = ctx_.checkInvariants(result, fee);
1027
1028 if (result == tecINVARIANT_FAILED)
1029 {
1030 // if invariants checking failed again, reset the context and
1031 // attempt to only claim a fee.
1032 auto const resetResult = reset(fee);
1033 if (!isTesSuccess(resetResult.first))
1034 result = resetResult.first;
1035
1036 fee = resetResult.second;
1037
1038 // Check invariants again to ensure the fee claiming doesn't
1039 // violate invariants.
1040 if (isTesSuccess(result) || isTecClaim(result))
1041 result = ctx_.checkInvariants(result, fee);
1042 }
1043
1044 // We ran through the invariant checker, which can, in some cases,
1045 // return a tef error code. Don't apply the transaction in that case.
1046 if (!isTecClaim(result) && !isTesSuccess(result))
1047 applied = false;
1048 }
1049
1050 if (applied)
1051 {
1052 // Transaction succeeded fully or (retries are not allowed and the
1053 // transaction could claim a fee)
1054
1055 // The transactor and invariant checkers guarantee that this will
1056 // *never* trigger but if it, somehow, happens, don't allow a tx
1057 // that charges a negative fee.
1058 if (fee < beast::zero)
1059 Throw<std::logic_error>("fee charged is negative!");
1060
1061 // Charge whatever fee they specified. The fee has already been
1062 // deducted from the balance of the account that issued the
1063 // transaction. We just need to account for it in the ledger
1064 // header.
1065 if (!view().open() && fee != beast::zero)
1066 ctx_.destroyXRP(fee);
1067
1068 // Once we call apply, we will no longer be able to look at view()
1069 ctx_.apply(result);
1070 }
1071
1072 JLOG(j_.trace()) << (applied ? "applied " : "not applied ")
1073 << transToken(result);
1074
1075 return {result, applied};
1076}
1077
1078} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:59
Stream fatal() const
Definition: Journal.h:341
Stream error() const
Definition: Journal.h:335
Stream debug() const
Definition: Journal.h:317
Stream info() const
Definition: Journal.h:323
Stream trace() const
Severity stream access functions.
Definition: Journal.h:311
Stream warn() const
Definition: Journal.h:329
virtual Config & config()=0
virtual LoadFeeTrack & getFeeTrack()=0
virtual beast::Journal journal(std::string const &name)=0
virtual const std::optional< uint256 > & trapTxID() const =0
virtual HashRouter & getHashRouter()=0
State information when applying a tx.
Definition: ApplyContext.h:36
void visit(std::function< void(uint256 const &key, bool isDelete, std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after)> const &func)
Visit unapplied changes.
TER const preclaimResult
Definition: ApplyContext.h:49
void discard()
Discard changes and start fresh.
void destroyXRP(XRPAmount const &fee)
Definition: ApplyContext.h:99
Application & app
Definition: ApplyContext.h:47
std::size_t size()
Get the number of unapplied changes.
void apply(TER)
Apply the transaction result to the base.
TER checkInvariants(TER const result, XRPAmount const fee)
Applies all invariant checkers one by one.
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:136
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
Definition: ApplyView.cpp:189
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void erase(std::shared_ptr< SLE > const &sle)=0
Remove a peeked SLE.
uint32_t NETWORK_ID
Definition: Config.h:163
RAII class to set and restore the current transaction rules.
Definition: Rules.h:108
RAII class to set and restore the Number switchover.
Definition: IOUAmount.h:204
A public key.
Definition: PublicKey.h:62
A view into a ledger.
Definition: ReadView.h:55
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual bool open() const =0
Returns true if this reflects an open ledger.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition: ReadView.h:122
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual bool txExists(key_type const &key) const =0
Returns true if a tx exists in the tx map.
Rules controlling protocol behavior.
Definition: Rules.h:35
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:122
RAII class to set and restore the STAmount canonicalize switchover.
Definition: STAmount.h:702
XRPAmount xrp() const
Definition: STAmount.cpp:273
size_type size() const
Definition: STArray.h:248
AccountID getAccountID(SField const &field) const
Definition: STObject.cpp:621
const STArray & getFieldArray(SField const &field) const
Definition: STObject.cpp:656
bool isEquivalent(const STBase &t) const override
Definition: STObject.cpp:330
std::uint32_t getFieldU32(SField const &field) const
Definition: STObject.cpp:585
void add(Serializer &s) const override
Definition: STObject.cpp:111
STAmount const & getFieldAmount(SField const &field) const
Definition: STObject.cpp:635
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:454
uint256 getFieldH256(SField const &field) const
Definition: STObject.cpp:615
SeqProxy getSeqProxy() const
Definition: STTx.cpp:186
Json::Value getJson(JsonOptions options) const override
Definition: STTx.cpp:233
Blob getSigningPubKey() const
Definition: STTx.h:187
uint256 getTransactionID() const
Definition: STTx.h:193
A type that represents either a sequence value or a ticket value.
Definition: SeqProxy.h:56
static constexpr SeqProxy sequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
Definition: SeqProxy.h:76
constexpr bool isSeq() const
Definition: SeqProxy.h:88
constexpr std::uint32_t value() const
Definition: SeqProxy.h:82
constexpr bool isTicket() const
Definition: SeqProxy.h:94
Slice slice() const noexcept
Definition: Serializer.h:66
static Expected< std::vector< SignerEntry >, NotTEC > deserialize(STObject const &obj, beast::Journal journal, std::string_view annotation)
TER consumeSeqProxy(SLE::pointer const &sleAccount)
Definition: Transactor.cpp:369
static NotTEC checkPriorTxAndLastLedger(PreclaimContext const &ctx)
Definition: Transactor.cpp:339
static TER checkFee(PreclaimContext const &ctx, XRPAmount baseFee)
Definition: Transactor.cpp:194
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition: Transactor.cpp:166
static NotTEC checkSeqProxy(ReadView const &view, STTx const &tx, beast::Journal j)
Definition: Transactor.cpp:267
static NotTEC checkSign(PreclaimContext const &ctx)
Definition: Transactor.cpp:487
void trapTransaction(uint256) const
Definition: Transactor.cpp:857
static XRPAmount minimumFee(Application &app, 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...
Definition: Transactor.cpp:184
static NotTEC checkSingleSign(PreclaimContext const &ctx)
Definition: Transactor.cpp:497
std::pair< TER, bool > operator()()
Process the transaction.
Definition: Transactor.cpp:864
AccountID const account_
Definition: Transactor.h:91
ApplyView & view()
Definition: Transactor.h:107
beast::Journal const j_
Definition: Transactor.h:89
XRPAmount mPriorBalance
Definition: Transactor.h:92
virtual void preCompute()
Definition: Transactor.cpp:442
static TER ticketDelete(ApplyView &view, AccountID const &account, uint256 const &ticketIndex, beast::Journal j)
Definition: Transactor.cpp:388
XRPAmount mSourceBalance
Definition: Transactor.h:93
ApplyContext & ctx_
Definition: Transactor.h:88
virtual TER doApply()=0
std::pair< TER, XRPAmount > reset(XRPAmount fee)
Reset the context, discarding any changes made and adjust the fee.
Definition: Transactor.cpp:814
Transactor(Transactor const &)=delete
static NotTEC checkMultiSign(PreclaimContext const &ctx)
Definition: Transactor.cpp:571
T empty(T... args)
TER deleteSLE(ApplyView &view, std::shared_ptr< SLE > const &sleCredential, beast::Journal j)
Keylet credential(AccountID const &subject, AccountID const &issuer, Slice const &credType) noexcept
Definition: Indexes.cpp:521
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:160
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:350
Keylet signers(AccountID const &account) noexcept
A SignerList.
Definition: Indexes.cpp:306
Keylet nftoffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Definition: Indexes.cpp:403
static ticket_t const ticket
Definition: Indexes.h:170
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:250
bool deleteTokenOffer(ApplyView &view, std::shared_ptr< SLE > const &offer)
Deletes the given token offer.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:106
bool isTecClaim(TER x)
Definition: TER.h:662
@ telWRONG_NETWORK
Definition: TER.h:65
@ telINSUF_FEE_P
Definition: TER.h:57
@ telREQUIRES_NETWORK_ID
Definition: TER.h:66
@ telNETWORK_ID_MAKES_TX_NON_CANONICAL
Definition: TER.h:67
bool isLegalAmount(XRPAmount const &amount)
Returns true if the amount does not exceed the initial XRP in existence.
TER deleteAMMTrustLine(ApplyView &view, std::shared_ptr< SLE > sleState, std::optional< AccountID > const &ammAccountID, beast::Journal j)
Delete trustline to AMM.
Definition: View.cpp:1966
bool isPseudoTx(STObject const &tx)
Check whether a transaction is a pseudo-transaction.
Definition: STTx.cpp:613
std::size_t constexpr unfundedOfferRemoveLimit
The maximum number of unfunded offers to delete at once.
Definition: Protocol.h:47
NotTEC preflight0(PreflightContext const &ctx)
Performs early sanity checks on the txid.
Definition: Transactor.cpp:42
std::size_t constexpr expiredOfferRemoveLimit
The maximum number of expired offers to delete at once.
Definition: Protocol.h:50
@ lsfDisableMaster
std::size_t constexpr oversizeMetaDataCap
The maximum number of metadata entries allowed in one transaction.
Definition: Protocol.h:53
bool isTesSuccess(TER x)
Definition: TER.h:656
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:82
AccountID calcAccountID(PublicKey const &pk)
Definition: AccountID.cpp:160
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition: View.cpp:1010
static void removeUnfundedOffers(ApplyView &view, std::vector< uint256 > const &offers, beast::Journal viewJ)
Definition: Transactor.cpp:735
@ tefNOT_MULTI_SIGNING
Definition: TER.h:181
@ tefPAST_SEQ
Definition: TER.h:175
@ tefNO_TICKET
Definition: TER.h:185
@ tefBAD_AUTH_MASTER
Definition: TER.h:182
@ tefMAX_LEDGER
Definition: TER.h:178
@ tefWRONG_PRIOR
Definition: TER.h:176
@ tefBAD_AUTH
Definition: TER.h:169
@ tefBAD_QUORUM
Definition: TER.h:180
@ tefBAD_SIGNATURE
Definition: TER.h:179
@ tefBAD_LEDGER
Definition: TER.h:170
@ tefALREADY
Definition: TER.h:167
@ tefMASTER_DISABLED
Definition: TER.h:177
@ tefINTERNAL
Definition: TER.h:173
@ open
We haven't closed our ledger yet, but others might have.
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
Definition: PublicKey.cpp:207
static void removeExpiredCredentials(ApplyView &view, std::vector< uint256 > const &creds, beast::Journal viewJ)
Definition: Transactor.cpp:774
static bool adjustOwnerCount(ApplyContext &ctx, int count)
Definition: SetOracle.cpp:186
std::string transToken(TER code)
Definition: TER.cpp:251
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:134
static void removeExpiredNFTokenOffers(ApplyView &view, std::vector< uint256 > const &offers, beast::Journal viewJ)
Definition: Transactor.cpp:755
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
Definition: Slice.h:243
@ tecINSUFF_FEE
Definition: TER.h:289
@ tecINCOMPLETE
Definition: TER.h:322
@ tecKILLED
Definition: TER.h:303
@ tecINVARIANT_FAILED
Definition: TER.h:300
@ tecOVERSIZE
Definition: TER.h:298
@ tecEXPIRED
Definition: TER.h:301
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:36
@ tesSUCCESS
Definition: TER.h:242
uint256 getTicketIndex(AccountID const &account, std::uint32_t uSequence)
Definition: Indexes.cpp:132
@ SigBad
Signature is bad. Didn't do local checks.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
static void removeDeletedTrustLines(ApplyView &view, std::vector< uint256 > const &trustLines, beast::Journal viewJ)
Definition: Transactor.cpp:787
XRPAmount scaleFeeLoad(XRPAmount fee, LoadFeeTrack const &feeTrack, Fees const &fees, bool bUnlimited)
ApplyFlags
Definition: ApplyView.h:30
@ tapFAIL_HARD
Definition: ApplyView.h:35
@ tapUNLIMITED
Definition: ApplyView.h:42
std::uint16_t constexpr maxDeletableAMMTrustLines
The maximum number of trustlines to delete as part of AMM account deletion cleanup.
Definition: Protocol.h:130
std::pair< Validity, std::string > checkValidity(HashRouter &router, STTx const &tx, Rules const &rules, Config const &config)
Checks transaction signature and local checks.
Definition: apply.cpp:37
@ terINSUF_FEE_B
Definition: TER.h:216
@ terNO_ACCOUNT
Definition: TER.h:217
@ terPRE_SEQ
Definition: TER.h:221
@ terPRE_TICKET
Definition: TER.h:226
static bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: Escrow.cpp:89
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:587
@ temBAD_SRC_ACCOUNT
Definition: TER.h:106
@ temUNKNOWN
Definition: TER.h:124
@ temBAD_FEE
Definition: TER.h:92
@ temSEQ_AND_TICKET
Definition: TER.h:126
@ temMALFORMED
Definition: TER.h:87
@ temINVALID
Definition: TER.h:110
@ temBAD_SIGNATURE
Definition: TER.h:105
T push_back(T... args)
T size(T... args)
Reflects the fee settings for a particular ledger.
Definition: protocol/Fees.h:33
XRPAmount base
Definition: protocol/Fees.h:34
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:53
ReadView const & view
Definition: Transactor.h:56
Application & app
Definition: Transactor.h:55
beast::Journal const j
Definition: Transactor.h:60
State information when preflighting a tx.
Definition: Transactor.h:32
PreflightContext(Application &app_, STTx const &tx_, Rules const &rules_, ApplyFlags flags_, beast::Journal j_)
Definition: Transactor.cpp:148
beast::Journal const j
Definition: Transactor.h:38