xrpld
Loading...
Searching...
No Matches
Batch.cpp
1#include <xrpl/tx/transactors/system/Batch.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/base_uint.h>
5#include <xrpl/beast/utility/Zero.h>
6#include <xrpl/ledger/ApplyView.h>
7#include <xrpl/ledger/ReadView.h>
8#include <xrpl/protocol/AccountID.h>
9#include <xrpl/protocol/Protocol.h>
10#include <xrpl/protocol/SField.h>
11#include <xrpl/protocol/STLedgerEntry.h>
12#include <xrpl/protocol/STObject.h>
13#include <xrpl/protocol/STTx.h>
14#include <xrpl/protocol/SystemParameters.h>
15#include <xrpl/protocol/TER.h>
16#include <xrpl/protocol/TxFlags.h>
17#include <xrpl/protocol/TxFormats.h>
18#include <xrpl/protocol/XRPAmount.h>
19#include <xrpl/tx/Transactor.h>
20#include <xrpl/tx/applySteps.h>
21
22#include <algorithm>
23#include <bit>
24#include <cstdint>
25#include <limits>
26#include <unordered_map>
27#include <unordered_set>
28#include <utility>
29
30namespace xrpl {
31
54{
56
57 // batchBase: view.fees().base for batch processing + default base fee
58 XRPAmount const baseFee = Transactor::calculateBaseFee(view, tx);
59
60 // LCOV_EXCL_START
61 if (baseFee > maxAmount - view.fees().base)
62 {
63 JLOG(debugLog().error()) << "BatchTrace: Base fee overflow detected.";
64 return XRPAmount{kInitialXrp};
65 }
66 // LCOV_EXCL_STOP
67
68 XRPAmount const batchBase = view.fees().base + baseFee;
69
70 // Calculate the Inner Txn Fees
71 XRPAmount txnFees{0};
72 if (tx.isFieldPresent(sfRawTransactions))
73 {
74 auto const& txns = tx.getFieldArray(sfRawTransactions);
75
76 // LCOV_EXCL_START
77 if (txns.size() > kMaxBatchTxCount)
78 {
79 JLOG(debugLog().error()) << "BatchTrace: Raw Transactions array exceeds max entries.";
80 return XRPAmount{kInitialXrp};
81 }
82 // LCOV_EXCL_STOP
83
84 for (STObject txn : txns)
85 {
86 STTx const stx = STTx{std::move(txn)};
87
88 // LCOV_EXCL_START
89 if (stx.getTxnType() == ttBATCH)
90 {
91 JLOG(debugLog().error()) << "BatchTrace: Inner Batch transaction found.";
92 return XRPAmount{kInitialXrp};
93 }
94 // LCOV_EXCL_STOP
95
96 auto const fee = xrpl::calculateBaseFee(view, stx);
97 // LCOV_EXCL_START
98 if (txnFees > maxAmount - fee)
99 {
100 JLOG(debugLog().error())
101 << "BatchTrace: XRPAmount overflow in txnFees calculation.";
102 return XRPAmount{kInitialXrp};
103 }
104 // LCOV_EXCL_STOP
105 txnFees += fee;
106 }
107 }
108
109 // Calculate the Signers/BatchSigners Fees
110 std::int32_t signerCount = 0;
111 if (tx.isFieldPresent(sfBatchSigners))
112 {
113 auto const& signers = tx.getFieldArray(sfBatchSigners);
114
115 // LCOV_EXCL_START
116 if (signers.size() > kMaxBatchTxCount)
117 {
118 JLOG(debugLog().error()) << "BatchTrace: Batch Signers array exceeds max entries.";
119 return XRPAmount{kInitialXrp};
120 }
121 // LCOV_EXCL_STOP
122
123 for (STObject const& signer : signers)
124 {
125 if (signer.isFieldPresent(sfTxnSignature))
126 {
127 signerCount += 1;
128 }
129 else if (signer.isFieldPresent(sfSigners))
130 {
131 signerCount += signer.getFieldArray(sfSigners).size();
132 }
133 }
134 }
135
136 // LCOV_EXCL_START
137 if (signerCount > 0 && view.fees().base > maxAmount / signerCount)
138 {
139 JLOG(debugLog().error()) << "BatchTrace: XRPAmount overflow in signerCount calculation.";
140 return XRPAmount{kInitialXrp};
141 }
142 // LCOV_EXCL_STOP
143
144 XRPAmount const signerFees = signerCount * view.fees().base;
145
146 // LCOV_EXCL_START
147 if (signerFees > maxAmount - txnFees)
148 {
149 JLOG(debugLog().error()) << "BatchTrace: XRPAmount overflow in signerFees calculation.";
150 return XRPAmount{kInitialXrp};
151 }
152 if (txnFees + signerFees > maxAmount - batchBase)
153 {
154 JLOG(debugLog().error()) << "BatchTrace: XRPAmount overflow in total fee calculation.";
155 return XRPAmount{kInitialXrp};
156 }
157 // LCOV_EXCL_STOP
158
159 // 10 drops per batch signature + sum of inner tx fees + batchBase
160 return signerFees + txnFees + batchBase;
161}
162
165{
166 return tfBatchMask;
167}
168
202NotTEC
204{
205 auto const parentBatchId = ctx.tx.getTransactionID();
206 auto const flags = ctx.tx.getFlags();
207
208 if (std::popcount(flags & (tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent)) != 1)
209 {
210 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
211 << "too many flags.";
212 return temINVALID_FLAG;
213 }
214
215 auto const& rawTxns = ctx.tx.getFieldArray(sfRawTransactions);
216 if (rawTxns.size() <= 1)
217 {
218 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
219 << "txns array must have at least 2 entries.";
220 return temARRAY_EMPTY;
221 }
222
223 if (rawTxns.size() > kMaxBatchTxCount)
224 {
225 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
226 << "txns array exceeds 8 entries.";
227 return temARRAY_TOO_LARGE;
228 }
229
230 // Validation Inner Batch Txns
231 std::unordered_set<uint256> uniqueHashes;
233 auto checkSignatureFields =
234 [&parentBatchId, &j = ctx.j](
235 STObject const& sig, uint256 const& hash, char const* label = "") -> NotTEC {
236 if (sig.isFieldPresent(sfTxnSignature))
237 {
238 JLOG(j.debug()) << "BatchTrace[" << parentBatchId << "]: "
239 << "inner txn " << label << "cannot include TxnSignature. "
240 << "txID: " << hash;
241 return temBAD_SIGNATURE;
242 }
243
244 if (sig.isFieldPresent(sfSigners))
245 {
246 JLOG(j.debug()) << "BatchTrace[" << parentBatchId << "]: "
247 << "inner txn " << label << " cannot include Signers. "
248 << "txID: " << hash;
249 return temBAD_SIGNER;
250 }
251
252 if (!sig.getFieldVL(sfSigningPubKey).empty())
253 {
254 JLOG(j.debug()) << "BatchTrace[" << parentBatchId << "]: "
255 << "inner txn " << label << " SigningPubKey must be empty. "
256 << "txID: " << hash;
257 return temBAD_REGKEY;
258 }
259
260 return tesSUCCESS;
261 };
262 for (STObject rb : rawTxns)
263 {
264 STTx const stx = STTx{std::move(rb)};
265 auto const hash = stx.getTransactionID();
266 if (!uniqueHashes.emplace(hash).second)
267 {
268 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
269 << "duplicate Txn found. "
270 << "txID: " << hash;
271 return temREDUNDANT;
272 }
273
274 auto const txType = stx.getFieldU16(sfTransactionType);
275 if (txType == ttBATCH)
276 {
277 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
278 << "batch cannot have an inner batch txn. "
279 << "txID: " << hash;
280 return temINVALID;
281 }
282
284 kDisabledTxTypes, [txType](auto const& disabled) { return txType == disabled; }))
285 {
287 }
288
289 if (!stx.isFlag(tfInnerBatchTxn))
290 {
291 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
292 << "inner txn must have the tfInnerBatchTxn flag. "
293 << "txID: " << hash;
294 return temINVALID_FLAG;
295 }
296
297 if (auto const ret = checkSignatureFields(stx, hash))
298 return ret;
299
300 // Note that the CounterpartySignature is optional, and should not be
301 // included, but if it is, ensure it doesn't contain a signature.
302 if (stx.isFieldPresent(sfCounterpartySignature))
303 {
304 auto const counterpartySignature = stx.getFieldObject(sfCounterpartySignature);
305 if (auto const ret =
306 checkSignatureFields(counterpartySignature, hash, "counterparty signature "))
307 {
308 return ret;
309 }
310 }
311
312 // Check that the Fee is native asset (XRP) and zero
313 if (auto const fee = stx.getFieldAmount(sfFee); !fee.native() || fee.xrp() != beast::kZero)
314 {
315 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
316 << "inner txn must have a fee of 0. "
317 << "txID: " << hash;
318 return temBAD_FEE;
319 }
320
321 auto const innerAccount = stx.getAccountID(sfAccount);
322 if (auto const preflightResult =
323 xrpl::preflight(ctx.registry, ctx.rules, parentBatchId, stx, TapBatch, ctx.j);
324 !isTesSuccess(preflightResult.ter))
325 {
326 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
327 << "inner txn preflight failed: " << transHuman(preflightResult.ter)
328 << " "
329 << "txID: " << hash;
331 }
332
333 // Check that Sequence and TicketSequence are not both present
334 if (stx.isFieldPresent(sfTicketSequence) && stx.getFieldU32(sfSequence) != 0)
335 {
336 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
337 << "inner txn must have exactly one of Sequence and "
338 "TicketSequence. "
339 << "txID: " << hash;
340 return temSEQ_AND_TICKET;
341 }
342
343 // Verify that either Sequence or TicketSequence is present
344 if (!stx.isFieldPresent(sfTicketSequence) && stx.getFieldU32(sfSequence) == 0)
345 {
346 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
347 << "inner txn must have either Sequence or "
348 "TicketSequence. "
349 << "txID: " << hash;
350 return temSEQ_AND_TICKET;
351 }
352
353 // Duplicate sequence and ticket checks
354 if ((flags & (tfAllOrNothing | tfUntilFailure)) != 0u)
355 {
356 if (auto const seq = stx.getFieldU32(sfSequence); seq != 0)
357 {
358 if (!accountSeqTicket[innerAccount].insert(seq).second)
359 {
360 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
361 << "duplicate sequence found: "
362 << "txID: " << hash;
363 return temREDUNDANT;
364 }
365 }
366
367 if (stx.isFieldPresent(sfTicketSequence))
368 {
369 if (auto const ticket = stx.getFieldU32(sfTicketSequence);
370 !accountSeqTicket[innerAccount].insert(ticket).second)
371 {
372 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
373 << "duplicate ticket found: "
374 << "txID: " << hash;
375 return temREDUNDANT;
376 }
377 }
378 }
379 }
380
381 return tesSUCCESS;
382}
383
384NotTEC
386{
387 auto const parentBatchId = ctx.tx.getTransactionID();
388 auto const outerAccount = ctx.tx.getAccountID(sfAccount);
389 auto const& rawTxns = ctx.tx.getFieldArray(sfRawTransactions);
390
391 // Build the signers list
392 std::unordered_set<AccountID> requiredSigners;
393 for (STObject const& rb : rawTxns)
394 {
395 auto const innerAccount = rb.getAccountID(sfAccount);
396
397 // If the inner account is the same as the outer account, do not add the
398 // inner account to the required signers set.
399 if (innerAccount != outerAccount)
400 requiredSigners.insert(innerAccount);
401 // Some transactions have a Counterparty, who must also sign the
402 // transaction if they are not the outer account
403 if (auto const counterparty = rb.at(~sfCounterparty);
404 counterparty && counterparty != outerAccount)
405 requiredSigners.insert(*counterparty);
406 }
407
408 // Validation Batch Signers
410 if (ctx.tx.isFieldPresent(sfBatchSigners))
411 {
412 STArray const& signers = ctx.tx.getFieldArray(sfBatchSigners);
413
414 // Check that the batch signers array is not too large.
415 if (signers.size() > kMaxBatchTxCount)
416 {
417 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
418 << "signers array exceeds 8 entries.";
419 return temARRAY_TOO_LARGE;
420 }
421
422 // Add batch signers to the set to ensure all signer accounts are
423 // unique. Meanwhile, remove signer accounts from the set of inner
424 // transaction accounts (`requiredSigners`). By the end of the loop,
425 // `requiredSigners` should be empty, indicating that all inner
426 // accounts are matched with signers.
427 for (auto const& signer : signers)
428 {
429 AccountID const signerAccount = signer.getAccountID(sfAccount);
430 if (signerAccount == outerAccount)
431 {
432 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
433 << "signer cannot be the outer account: " << signerAccount;
434 return temBAD_SIGNER;
435 }
436
437 if (!batchSigners.insert(signerAccount).second)
438 {
439 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
440 << "duplicate signer found: " << signerAccount;
441 return temREDUNDANT;
442 }
443
444 // Check that the batch signer is in the required signers set.
445 // Remove it if it does, as it can be crossed off the list.
446 if (requiredSigners.erase(signerAccount) == 0)
447 {
448 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
449 << "no account signature for inner txn.";
450 return temBAD_SIGNER;
451 }
452 }
453
454 // Check the batch signers signatures.
455 auto const sigResult = ctx.tx.checkBatchSign(ctx.rules);
456
457 if (!sigResult)
458 {
459 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
460 << "invalid batch txn signature: " << sigResult.error();
461 return temBAD_SIGNATURE;
462 }
463 }
464
465 if (!requiredSigners.empty())
466 {
467 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
468 << "invalid batch signers.";
469 return temBAD_SIGNER;
470 }
471 return tesSUCCESS;
472}
473
491NotTEC
493{
494 if (auto ret = Transactor::checkSign(ctx); !isTesSuccess(ret))
495 return ret;
496
497 if (auto ret = Transactor::checkBatchSign(ctx); !isTesSuccess(ret))
498 return ret;
499
500 return tesSUCCESS;
501}
502
513TER
515{
516 return tesSUCCESS;
517}
518
519void
521{
522 // No transaction-specific invariants yet (future work).
523}
524
525bool
527{
528 // No transaction-specific invariants yet (future work).
529 return true;
530}
531
532} // namespace xrpl
T any_of(T... args)
A generic endpoint for log messages.
Definition Journal.h:38
Stream debug() const
Definition Journal.h:297
UInt size() const
Number of values in array or object.
void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override
Inspect a single ledger entry modified by this transaction.
Definition Batch.cpp:520
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Calculates the total base fee for a batch transaction.
Definition Batch.cpp:53
static NotTEC preflight(PreflightContext const &ctx)
Performs preflight validation checks for a Batch transaction.
Definition Batch.cpp:203
static NotTEC checkSign(PreclaimContext const &ctx)
Checks the validity of signatures for a batch transaction.
Definition Batch.cpp:492
TER doApply() override
Applies the outer batch transaction.
Definition Batch.cpp:514
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
Definition Batch.cpp:526
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
Definition Batch.cpp:164
static constexpr auto kDisabledTxTypes
static NotTEC preflightSigValidated(PreflightContext const &ctx)
Definition Batch.cpp:385
A view into a ledger.
Definition ReadView.h:31
bool native() const noexcept
Definition STAmount.h:453
std::shared_ptr< STLedgerEntry const > const & const_ref
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
STObject getFieldObject(SField const &field) const
Definition STObject.cpp:668
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:633
std::uint16_t getFieldU16(SField const &field) const
Definition STObject.cpp:585
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:647
std::uint32_t getFlags() const
Definition STObject.cpp:507
std::expected< void, std::string > checkBatchSign(Rules const &rules) const
Definition STTx.cpp:286
TxType getTxnType() const
Definition STTx.h:188
uint256 getTransactionID() const
Definition STTx.h:200
static NotTEC checkSign(PreclaimContext const &ctx)
static NotTEC checkBatchSign(PreclaimContext const &ctx)
ApplyView & view()
Definition Transactor.h:136
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
T emplace(T... args)
T empty(T... args)
T erase(T... args)
T insert(T... args)
T max(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr FlagValue tfInnerBatchTxn
Definition TxFlags.h:43
PreflightResult preflight(ServiceRegistry &registry, Rules const &rules, STTx const &tx, ApplyFlags flags, beast::Journal j)
Gate a transaction based on static information.
beast::Journal debugLog()
Returns a debug journal.
Definition Log.cpp:399
constexpr std::size_t kMaxBatchTxCount
The maximum number of transactions that can be in a batch.
Definition Protocol.h:312
std::string transHuman(TER code)
Definition TER.cpp:256
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:594
@ TapBatch
Definition ApplyView.h:27
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
@ temBAD_REGKEY
Definition TER.h:84
@ temARRAY_TOO_LARGE
Definition TER.h:127
@ temBAD_FEE
Definition TER.h:78
@ temINVALID
Definition TER.h:96
@ temINVALID_FLAG
Definition TER.h:97
@ temARRAY_EMPTY
Definition TER.h:126
@ temSEQ_AND_TICKET
Definition TER.h:112
@ temBAD_SIGNATURE
Definition TER.h:91
@ temINVALID_INNER_BATCH
Definition TER.h:129
@ temREDUNDANT
Definition TER.h:98
@ temBAD_SIGNER
Definition TER.h:101
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
TERSubset< CanCvtToTER > TER
Definition TER.h:634
XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Compute only the expected base fee for a transaction.
constexpr XRPAmount kInitialXrp
Configure the native currency.
BaseUInt< 256 > uint256
Definition base_uint.h:562
@ tesSUCCESS
Definition TER.h:240
T popcount(T... args)
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
State information when preflighting a tx.
Definition Transactor.h:18
beast::Journal const j
Definition Transactor.h:25
std::reference_wrapper< ServiceRegistry > registry
Definition Transactor.h:20