rippled
Loading...
Searching...
No Matches
Batch.cpp
1#include <xrpl/basics/Log.h>
2#include <xrpl/ledger/Sandbox.h>
3#include <xrpl/ledger/View.h>
4#include <xrpl/protocol/Feature.h>
5#include <xrpl/protocol/Indexes.h>
6#include <xrpl/protocol/SystemParameters.h>
7#include <xrpl/protocol/TER.h>
8#include <xrpl/protocol/TxFlags.h>
9#include <xrpl/tx/apply.h>
10#include <xrpl/tx/transactors/system/Batch.h>
11
12namespace xrpl {
13
34XRPAmount
35Batch::calculateBaseFee(ReadView const& view, STTx const& tx)
36{
38
39 // batchBase: view.fees().base for batch processing + default base fee
40 XRPAmount const baseFee = Transactor::calculateBaseFee(view, tx);
41
42 // LCOV_EXCL_START
43 if (baseFee > maxAmount - view.fees().base)
44 {
45 JLOG(debugLog().error()) << "BatchTrace: Base fee overflow detected.";
46 return XRPAmount{INITIAL_XRP};
47 }
48 // LCOV_EXCL_STOP
49
50 XRPAmount const batchBase = view.fees().base + baseFee;
51
52 // Calculate the Inner Txn Fees
53 XRPAmount txnFees{0};
54 if (tx.isFieldPresent(sfRawTransactions))
55 {
56 auto const& txns = tx.getFieldArray(sfRawTransactions);
57
58 // LCOV_EXCL_START
59 if (txns.size() > maxBatchTxCount)
60 {
61 JLOG(debugLog().error()) << "BatchTrace: Raw Transactions array exceeds max entries.";
62 return XRPAmount{INITIAL_XRP};
63 }
64 // LCOV_EXCL_STOP
65
66 for (STObject txn : txns)
67 {
68 STTx const stx = STTx{std::move(txn)};
69
70 // LCOV_EXCL_START
71 if (stx.getTxnType() == ttBATCH)
72 {
73 JLOG(debugLog().error()) << "BatchTrace: Inner Batch transaction found.";
74 return XRPAmount{INITIAL_XRP};
75 }
76 // LCOV_EXCL_STOP
77
78 auto const fee = xrpl::calculateBaseFee(view, stx);
79 // LCOV_EXCL_START
80 if (txnFees > maxAmount - fee)
81 {
82 JLOG(debugLog().error())
83 << "BatchTrace: XRPAmount overflow in txnFees calculation.";
84 return XRPAmount{INITIAL_XRP};
85 }
86 // LCOV_EXCL_STOP
87 txnFees += fee;
88 }
89 }
90
91 // Calculate the Signers/BatchSigners Fees
92 std::int32_t signerCount = 0;
93 if (tx.isFieldPresent(sfBatchSigners))
94 {
95 auto const& signers = tx.getFieldArray(sfBatchSigners);
96
97 // LCOV_EXCL_START
98 if (signers.size() > maxBatchTxCount)
99 {
100 JLOG(debugLog().error()) << "BatchTrace: Batch Signers array exceeds max entries.";
101 return XRPAmount{INITIAL_XRP};
102 }
103 // LCOV_EXCL_STOP
104
105 for (STObject const& signer : signers)
106 {
107 if (signer.isFieldPresent(sfTxnSignature))
108 {
109 signerCount += 1;
110 }
111 else if (signer.isFieldPresent(sfSigners))
112 {
113 signerCount += signer.getFieldArray(sfSigners).size();
114 }
115 }
116 }
117
118 // LCOV_EXCL_START
119 if (signerCount > 0 && view.fees().base > maxAmount / signerCount)
120 {
121 JLOG(debugLog().error()) << "BatchTrace: XRPAmount overflow in signerCount calculation.";
122 return XRPAmount{INITIAL_XRP};
123 }
124 // LCOV_EXCL_STOP
125
126 XRPAmount const signerFees = signerCount * view.fees().base;
127
128 // LCOV_EXCL_START
129 if (signerFees > maxAmount - txnFees)
130 {
131 JLOG(debugLog().error()) << "BatchTrace: XRPAmount overflow in signerFees calculation.";
132 return XRPAmount{INITIAL_XRP};
133 }
134 if (txnFees + signerFees > maxAmount - batchBase)
135 {
136 JLOG(debugLog().error()) << "BatchTrace: XRPAmount overflow in total fee calculation.";
137 return XRPAmount{INITIAL_XRP};
138 }
139 // LCOV_EXCL_STOP
140
141 // 10 drops per batch signature + sum of inner tx fees + batchBase
142 return signerFees + txnFees + batchBase;
143}
144
147{
148 return tfBatchMask;
149}
150
184NotTEC
186{
187 auto const parentBatchId = ctx.tx.getTransactionID();
188 auto const flags = ctx.tx.getFlags();
189
190 if (std::popcount(flags & (tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent)) != 1)
191 {
192 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
193 << "too many flags.";
194 return temINVALID_FLAG;
195 }
196
197 auto const& rawTxns = ctx.tx.getFieldArray(sfRawTransactions);
198 if (rawTxns.size() <= 1)
199 {
200 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
201 << "txns array must have at least 2 entries.";
202 return temARRAY_EMPTY;
203 }
204
205 if (rawTxns.size() > maxBatchTxCount)
206 {
207 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
208 << "txns array exceeds 8 entries.";
209 return temARRAY_TOO_LARGE;
210 }
211
212 // Validation Inner Batch Txns
213 std::unordered_set<uint256> uniqueHashes;
215 auto checkSignatureFields =
216 [&parentBatchId, &j = ctx.j](
217 STObject const& sig, uint256 const& hash, char const* label = "") -> NotTEC {
218 if (sig.isFieldPresent(sfTxnSignature))
219 {
220 JLOG(j.debug()) << "BatchTrace[" << parentBatchId << "]: "
221 << "inner txn " << label << "cannot include TxnSignature. "
222 << "txID: " << hash;
223 return temBAD_SIGNATURE;
224 }
225
226 if (sig.isFieldPresent(sfSigners))
227 {
228 JLOG(j.debug()) << "BatchTrace[" << parentBatchId << "]: "
229 << "inner txn " << label << " cannot include Signers. "
230 << "txID: " << hash;
231 return temBAD_SIGNER;
232 }
233
234 if (!sig.getFieldVL(sfSigningPubKey).empty())
235 {
236 JLOG(j.debug()) << "BatchTrace[" << parentBatchId << "]: "
237 << "inner txn " << label << " SigningPubKey must be empty. "
238 << "txID: " << hash;
239 return temBAD_REGKEY;
240 }
241
242 return tesSUCCESS;
243 };
244 for (STObject rb : rawTxns)
245 {
246 STTx const stx = STTx{std::move(rb)};
247 auto const hash = stx.getTransactionID();
248 if (!uniqueHashes.emplace(hash).second)
249 {
250 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
251 << "duplicate Txn found. "
252 << "txID: " << hash;
253 return temREDUNDANT;
254 }
255
256 auto const txType = stx.getFieldU16(sfTransactionType);
257 if (txType == ttBATCH)
258 {
259 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
260 << "batch cannot have an inner batch txn. "
261 << "txID: " << hash;
262 return temINVALID;
263 }
264
265 if (std::any_of(
266 disabledTxTypes.begin(), disabledTxTypes.end(), [txType](auto const& disabled) {
267 return txType == disabled;
268 }))
269 {
271 }
272
273 if ((stx.getFlags() & tfInnerBatchTxn) == 0u)
274 {
275 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
276 << "inner txn must have the tfInnerBatchTxn flag. "
277 << "txID: " << hash;
278 return temINVALID_FLAG;
279 }
280
281 if (auto const ret = checkSignatureFields(stx, hash))
282 return ret;
283
284 // Note that the CounterpartySignature is optional, and should not be
285 // included, but if it is, ensure it doesn't contain a signature.
286 if (stx.isFieldPresent(sfCounterpartySignature))
287 {
288 auto const counterpartySignature = stx.getFieldObject(sfCounterpartySignature);
289 if (auto const ret =
290 checkSignatureFields(counterpartySignature, hash, "counterparty signature "))
291 {
292 return ret;
293 }
294 }
295
296 // Check that the Fee is native asset (XRP) and zero
297 if (auto const fee = stx.getFieldAmount(sfFee); !fee.native() || fee.xrp() != beast::zero)
298 {
299 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
300 << "inner txn must have a fee of 0. "
301 << "txID: " << hash;
302 return temBAD_FEE;
303 }
304
305 auto const innerAccount = stx.getAccountID(sfAccount);
306 if (auto const preflightResult =
307 xrpl::preflight(ctx.registry, ctx.rules, parentBatchId, stx, tapBATCH, ctx.j);
308 !isTesSuccess(preflightResult.ter))
309 {
310 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
311 << "inner txn preflight failed: " << transHuman(preflightResult.ter)
312 << " "
313 << "txID: " << hash;
315 }
316
317 // Check that Sequence and TicketSequence are not both present
318 if (stx.isFieldPresent(sfTicketSequence) && stx.getFieldU32(sfSequence) != 0)
319 {
320 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
321 << "inner txn must have exactly one of Sequence and "
322 "TicketSequence. "
323 << "txID: " << hash;
324 return temSEQ_AND_TICKET;
325 }
326
327 // Verify that either Sequence or TicketSequence is present
328 if (!stx.isFieldPresent(sfTicketSequence) && stx.getFieldU32(sfSequence) == 0)
329 {
330 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
331 << "inner txn must have either Sequence or "
332 "TicketSequence. "
333 << "txID: " << hash;
334 return temSEQ_AND_TICKET;
335 }
336
337 // Duplicate sequence and ticket checks
338 if ((flags & (tfAllOrNothing | tfUntilFailure)) != 0u)
339 {
340 if (auto const seq = stx.getFieldU32(sfSequence); seq != 0)
341 {
342 if (!accountSeqTicket[innerAccount].insert(seq).second)
343 {
344 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
345 << "duplicate sequence found: "
346 << "txID: " << hash;
347 return temREDUNDANT;
348 }
349 }
350
351 if (stx.isFieldPresent(sfTicketSequence))
352 {
353 if (auto const ticket = stx.getFieldU32(sfTicketSequence);
354 !accountSeqTicket[innerAccount].insert(ticket).second)
355 {
356 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
357 << "duplicate ticket found: "
358 << "txID: " << hash;
359 return temREDUNDANT;
360 }
361 }
362 }
363 }
364
365 return tesSUCCESS;
366}
367
368NotTEC
370{
371 auto const parentBatchId = ctx.tx.getTransactionID();
372 auto const outerAccount = ctx.tx.getAccountID(sfAccount);
373 auto const& rawTxns = ctx.tx.getFieldArray(sfRawTransactions);
374
375 // Build the signers list
376 std::unordered_set<AccountID> requiredSigners;
377 for (STObject const& rb : rawTxns)
378 {
379 auto const innerAccount = rb.getAccountID(sfAccount);
380
381 // If the inner account is the same as the outer account, do not add the
382 // inner account to the required signers set.
383 if (innerAccount != outerAccount)
384 requiredSigners.insert(innerAccount);
385 // Some transactions have a Counterparty, who must also sign the
386 // transaction if they are not the outer account
387 if (auto const counterparty = rb.at(~sfCounterparty);
388 counterparty && counterparty != outerAccount)
389 requiredSigners.insert(*counterparty);
390 }
391
392 // Validation Batch Signers
394 if (ctx.tx.isFieldPresent(sfBatchSigners))
395 {
396 STArray const& signers = ctx.tx.getFieldArray(sfBatchSigners);
397
398 // Check that the batch signers array is not too large.
399 if (signers.size() > maxBatchTxCount)
400 {
401 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
402 << "signers array exceeds 8 entries.";
403 return temARRAY_TOO_LARGE;
404 }
405
406 // Add batch signers to the set to ensure all signer accounts are
407 // unique. Meanwhile, remove signer accounts from the set of inner
408 // transaction accounts (`requiredSigners`). By the end of the loop,
409 // `requiredSigners` should be empty, indicating that all inner
410 // accounts are matched with signers.
411 for (auto const& signer : signers)
412 {
413 AccountID const signerAccount = signer.getAccountID(sfAccount);
414 if (signerAccount == outerAccount)
415 {
416 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
417 << "signer cannot be the outer account: " << signerAccount;
418 return temBAD_SIGNER;
419 }
420
421 if (!batchSigners.insert(signerAccount).second)
422 {
423 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
424 << "duplicate signer found: " << signerAccount;
425 return temREDUNDANT;
426 }
427
428 // Check that the batch signer is in the required signers set.
429 // Remove it if it does, as it can be crossed off the list.
430 if (requiredSigners.erase(signerAccount) == 0)
431 {
432 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
433 << "no account signature for inner txn.";
434 return temBAD_SIGNER;
435 }
436 }
437
438 // Check the batch signers signatures.
439 auto const sigResult = ctx.tx.checkBatchSign(ctx.rules);
440
441 if (!sigResult)
442 {
443 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
444 << "invalid batch txn signature: " << sigResult.error();
445 return temBAD_SIGNATURE;
446 }
447 }
448
449 if (!requiredSigners.empty())
450 {
451 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
452 << "invalid batch signers.";
453 return temBAD_SIGNER;
454 }
455 return tesSUCCESS;
456}
457
475NotTEC
477{
478 if (auto ret = Transactor::checkSign(ctx); !isTesSuccess(ret))
479 return ret;
480
481 if (auto ret = Transactor::checkBatchSign(ctx); !isTesSuccess(ret))
482 return ret;
483
484 return tesSUCCESS;
485}
486
497TER
499{
500 return tesSUCCESS;
501}
502
503} // namespace xrpl
T any_of(T... args)
Stream debug() const
Definition Journal.h:301
static constexpr auto disabledTxTypes
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Calculates the total base fee for a batch transaction.
Definition Batch.cpp:35
static NotTEC preflight(PreflightContext const &ctx)
Performs preflight validation checks for a Batch transaction.
Definition Batch.cpp:185
static NotTEC checkSign(PreclaimContext const &ctx)
Checks the validity of signatures for a batch transaction.
Definition Batch.cpp:476
TER doApply() override
Applies the outer batch transaction.
Definition Batch.cpp:498
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
Definition Batch.cpp:146
static NotTEC preflightSigValidated(PreflightContext const &ctx)
Definition Batch.cpp:369
A view into a ledger.
Definition ReadView.h:31
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
std::uint32_t getFieldU32(SField const &field) const
Definition STObject.cpp:593
STArray const & getFieldArray(SField const &field) const
Definition STObject.cpp:680
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:456
STObject getFieldObject(SField const &field) const
Definition STObject.cpp:670
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:635
std::uint16_t getFieldU16(SField const &field) const
Definition STObject.cpp:587
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:649
std::uint32_t getFlags() const
Definition STObject.cpp:509
Expected< void, std::string > checkBatchSign(Rules const &rules) const
Definition STTx.cpp:287
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:132
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:41
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:453
std::string transHuman(TER code)
Definition TER.cpp:252
std::size_t constexpr maxBatchTxCount
The maximum number of transactions that can be in a batch.
Definition Protocol.h:308
@ tapBATCH
Definition ApplyView.h:25
@ temBAD_REGKEY
Definition TER.h:78
@ temARRAY_TOO_LARGE
Definition TER.h:121
@ temBAD_FEE
Definition TER.h:72
@ temINVALID
Definition TER.h:90
@ temINVALID_FLAG
Definition TER.h:91
@ temARRAY_EMPTY
Definition TER.h:120
@ temSEQ_AND_TICKET
Definition TER.h:106
@ temBAD_SIGNATURE
Definition TER.h:85
@ temINVALID_INNER_BATCH
Definition TER.h:123
@ temREDUNDANT
Definition TER.h:92
@ temBAD_SIGNER
Definition TER.h:95
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Compute only the expected base fee for a transaction.
@ tesSUCCESS
Definition TER.h:225
T popcount(T... args)
XRPAmount base
Cost of a reference transaction in drops.
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:57
State information when preflighting a tx.
Definition Transactor.h:14
beast::Journal const j
Definition Transactor.h:21
std::reference_wrapper< ServiceRegistry > registry
Definition Transactor.h:16