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