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/SystemParameters.h>
10#include <xrpl/protocol/TER.h>
11#include <xrpl/protocol/TxFlags.h>
12
13namespace xrpl {
14
35XRPAmount
36Batch::calculateBaseFee(ReadView const& view, STTx const& tx)
37{
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()) << "BatchTrace: Raw Transactions array exceeds max entries.";
63 return XRPAmount{INITIAL_XRP};
64 }
65 // LCOV_EXCL_STOP
66
67 for (STObject txn : txns)
68 {
69 STTx const stx = STTx{std::move(txn)};
70
71 // LCOV_EXCL_START
72 if (stx.getTxnType() == ttBATCH)
73 {
74 JLOG(debugLog().error()) << "BatchTrace: Inner Batch transaction found.";
75 return XRPAmount{INITIAL_XRP};
76 }
77 // LCOV_EXCL_STOP
78
79 auto const fee = xrpl::calculateBaseFee(view, stx);
80 // LCOV_EXCL_START
81 if (txnFees > maxAmount - fee)
82 {
83 JLOG(debugLog().error()) << "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 signerCount += 1;
109 else if (signer.isFieldPresent(sfSigners))
110 signerCount += signer.getFieldArray(sfSigners).size();
111 }
112 }
113
114 // LCOV_EXCL_START
115 if (signerCount > 0 && view.fees().base > maxAmount / signerCount)
116 {
117 JLOG(debugLog().error()) << "BatchTrace: XRPAmount overflow in signerCount calculation.";
118 return XRPAmount{INITIAL_XRP};
119 }
120 // LCOV_EXCL_STOP
121
122 XRPAmount signerFees = signerCount * view.fees().base;
123
124 // LCOV_EXCL_START
125 if (signerFees > maxAmount - txnFees)
126 {
127 JLOG(debugLog().error()) << "BatchTrace: XRPAmount overflow in signerFees calculation.";
128 return XRPAmount{INITIAL_XRP};
129 }
130 if (txnFees + signerFees > maxAmount - batchBase)
131 {
132 JLOG(debugLog().error()) << "BatchTrace: XRPAmount overflow in total fee calculation.";
133 return XRPAmount{INITIAL_XRP};
134 }
135 // LCOV_EXCL_STOP
136
137 // 10 drops per batch signature + sum of inner tx fees + batchBase
138 return signerFees + txnFees + batchBase;
139}
140
143{
144 return tfBatchMask;
145}
146
180NotTEC
182{
183 auto const parentBatchId = ctx.tx.getTransactionID();
184 auto const flags = ctx.tx.getFlags();
185
187 {
188 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
189 << "too many flags.";
190 return temINVALID_FLAG;
191 }
192
193 auto const& rawTxns = ctx.tx.getFieldArray(sfRawTransactions);
194 if (rawTxns.size() <= 1)
195 {
196 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
197 << "txns array must have at least 2 entries.";
198 return temARRAY_EMPTY;
199 }
200
201 if (rawTxns.size() > maxBatchTxCount)
202 {
203 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
204 << "txns array exceeds 8 entries.";
205 return temARRAY_TOO_LARGE;
206 }
207
208 // Validation Inner Batch Txns
209 std::unordered_set<uint256> uniqueHashes;
211 auto checkSignatureFields = [&parentBatchId, &j = ctx.j](
212 STObject const& sig, uint256 const& hash, char const* label = "") -> NotTEC {
213 if (sig.isFieldPresent(sfTxnSignature))
214 {
215 JLOG(j.debug()) << "BatchTrace[" << parentBatchId << "]: "
216 << "inner txn " << label << "cannot include TxnSignature. "
217 << "txID: " << hash;
218 return temBAD_SIGNATURE;
219 }
220
221 if (sig.isFieldPresent(sfSigners))
222 {
223 JLOG(j.debug()) << "BatchTrace[" << parentBatchId << "]: "
224 << "inner txn " << label << " cannot include Signers. "
225 << "txID: " << hash;
226 return temBAD_SIGNER;
227 }
228
229 if (!sig.getFieldVL(sfSigningPubKey).empty())
230 {
231 JLOG(j.debug()) << "BatchTrace[" << parentBatchId << "]: "
232 << "inner txn " << label << " SigningPubKey must be empty. "
233 << "txID: " << hash;
234 return temBAD_REGKEY;
235 }
236
237 return tesSUCCESS;
238 };
239 for (STObject rb : rawTxns)
240 {
241 STTx const stx = STTx{std::move(rb)};
242 auto const hash = stx.getTransactionID();
243 if (!uniqueHashes.emplace(hash).second)
244 {
245 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
246 << "duplicate Txn found. "
247 << "txID: " << hash;
248 return temREDUNDANT;
249 }
250
251 auto const txType = stx.getFieldU16(sfTransactionType);
252 if (txType == ttBATCH)
253 {
254 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
255 << "batch cannot have an inner batch txn. "
256 << "txID: " << hash;
257 return temINVALID;
258 }
259
260 if (std::any_of(disabledTxTypes.begin(), disabledTxTypes.end(), [txType](auto const& disabled) {
261 return txType == disabled;
262 }))
263 {
265 }
266
267 if (!(stx.getFlags() & tfInnerBatchTxn))
268 {
269 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
270 << "inner txn must have the tfInnerBatchTxn flag. "
271 << "txID: " << hash;
272 return temINVALID_FLAG;
273 }
274
275 if (auto const ret = checkSignatureFields(stx, hash))
276 return ret;
277
278 // Note that the CounterpartySignature is optional, and should not be
279 // included, but if it is, ensure it doesn't contain a signature.
280 if (stx.isFieldPresent(sfCounterpartySignature))
281 {
282 auto const counterpartySignature = stx.getFieldObject(sfCounterpartySignature);
283 if (auto const ret = checkSignatureFields(counterpartySignature, hash, "counterparty signature "))
284 {
285 return ret;
286 }
287 }
288
289 // Check that the Fee is native asset (XRP) and zero
290 if (auto const fee = stx.getFieldAmount(sfFee); !fee.native() || fee.xrp() != beast::zero)
291 {
292 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
293 << "inner txn must have a fee of 0. "
294 << "txID: " << hash;
295 return temBAD_FEE;
296 }
297
298 auto const innerAccount = stx.getAccountID(sfAccount);
299 if (auto const preflightResult = xrpl::preflight(ctx.app, ctx.rules, parentBatchId, stx, tapBATCH, ctx.j);
300 preflightResult.ter != tesSUCCESS)
301 {
302 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
303 << "inner txn preflight failed: " << transHuman(preflightResult.ter) << " "
304 << "txID: " << hash;
306 }
307
308 // Check that Sequence and TicketSequence are not both present
309 if (stx.isFieldPresent(sfTicketSequence) && stx.getFieldU32(sfSequence) != 0)
310 {
311 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
312 << "inner txn must have exactly one of Sequence and "
313 "TicketSequence. "
314 << "txID: " << hash;
315 return temSEQ_AND_TICKET;
316 }
317
318 // Verify that either Sequence or TicketSequence is present
319 if (!stx.isFieldPresent(sfTicketSequence) && stx.getFieldU32(sfSequence) == 0)
320 {
321 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
322 << "inner txn must have either Sequence or "
323 "TicketSequence. "
324 << "txID: " << hash;
325 return temSEQ_AND_TICKET;
326 }
327
328 // Duplicate sequence and ticket checks
329 if (flags & (tfAllOrNothing | tfUntilFailure))
330 {
331 if (auto const seq = stx.getFieldU32(sfSequence); seq != 0)
332 {
333 if (!accountSeqTicket[innerAccount].insert(seq).second)
334 {
335 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
336 << "duplicate sequence found: "
337 << "txID: " << hash;
338 return temREDUNDANT;
339 }
340 }
341
342 if (stx.isFieldPresent(sfTicketSequence))
343 {
344 if (auto const ticket = stx.getFieldU32(sfTicketSequence);
345 !accountSeqTicket[innerAccount].insert(ticket).second)
346 {
347 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
348 << "duplicate ticket found: "
349 << "txID: " << hash;
350 return temREDUNDANT;
351 }
352 }
353 }
354 }
355
356 return tesSUCCESS;
357}
358
359NotTEC
361{
362 auto const parentBatchId = ctx.tx.getTransactionID();
363 auto const outerAccount = ctx.tx.getAccountID(sfAccount);
364 auto const& rawTxns = ctx.tx.getFieldArray(sfRawTransactions);
365
366 // Build the signers list
367 std::unordered_set<AccountID> requiredSigners;
368 for (STObject const& rb : rawTxns)
369 {
370 auto const innerAccount = rb.getAccountID(sfAccount);
371
372 // If the inner account is the same as the outer account, do not add the
373 // inner account to the required signers set.
374 if (innerAccount != outerAccount)
375 requiredSigners.insert(innerAccount);
376 // Some transactions have a Counterparty, who must also sign the
377 // transaction if they are not the outer account
378 if (auto const counterparty = rb.at(~sfCounterparty); counterparty && counterparty != outerAccount)
379 requiredSigners.insert(*counterparty);
380 }
381
382 // Validation Batch Signers
384 if (ctx.tx.isFieldPresent(sfBatchSigners))
385 {
386 STArray const& signers = ctx.tx.getFieldArray(sfBatchSigners);
387
388 // Check that the batch signers array is not too large.
389 if (signers.size() > maxBatchTxCount)
390 {
391 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
392 << "signers array exceeds 8 entries.";
393 return temARRAY_TOO_LARGE;
394 }
395
396 // Add batch signers to the set to ensure all signer accounts are
397 // unique. Meanwhile, remove signer accounts from the set of inner
398 // transaction accounts (`requiredSigners`). By the end of the loop,
399 // `requiredSigners` should be empty, indicating that all inner
400 // accounts are matched with signers.
401 for (auto const& signer : signers)
402 {
403 AccountID const signerAccount = signer.getAccountID(sfAccount);
404 if (signerAccount == outerAccount)
405 {
406 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
407 << "signer cannot be the outer account: " << signerAccount;
408 return temBAD_SIGNER;
409 }
410
411 if (!batchSigners.insert(signerAccount).second)
412 {
413 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
414 << "duplicate signer found: " << signerAccount;
415 return temREDUNDANT;
416 }
417
418 // Check that the batch signer is in the required signers set.
419 // Remove it if it does, as it can be crossed off the list.
420 if (requiredSigners.erase(signerAccount) == 0)
421 {
422 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
423 << "no account signature for inner txn.";
424 return temBAD_SIGNER;
425 }
426 }
427
428 // Check the batch signers signatures.
429 auto const sigResult = ctx.tx.checkBatchSign(ctx.rules);
430
431 if (!sigResult)
432 {
433 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
434 << "invalid batch txn signature: " << sigResult.error();
435 return temBAD_SIGNATURE;
436 }
437 }
438
439 if (!requiredSigners.empty())
440 {
441 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
442 << "invalid batch signers.";
443 return temBAD_SIGNER;
444 }
445 return tesSUCCESS;
446}
447
465NotTEC
467{
468 if (auto ret = Transactor::checkSign(ctx); !isTesSuccess(ret))
469 return ret;
470
471 if (auto ret = Transactor::checkBatchSign(ctx); !isTesSuccess(ret))
472 return ret;
473
474 return tesSUCCESS;
475}
476
487TER
489{
490 return tesSUCCESS;
491}
492
493} // namespace xrpl
T any_of(T... args)
Stream debug() const
Definition Journal.h:300
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:36
static NotTEC preflight(PreflightContext const &ctx)
Performs preflight validation checks for a Batch transaction.
Definition Batch.cpp:181
static NotTEC checkSign(PreclaimContext const &ctx)
Checks the validity of signatures for a batch transaction.
Definition Batch.cpp:466
TER doApply() override
Applies the outer batch transaction.
Definition Batch.cpp:488
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
Definition Batch.cpp:142
static NotTEC preflightSigValidated(PreflightContext const &ctx)
Definition Batch.cpp:360
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:576
STArray const & getFieldArray(SField const &field) const
Definition STObject.cpp:663
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:439
STObject getFieldObject(SField const &field) const
Definition STObject.cpp:653
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:618
std::uint16_t getFieldU16(SField const &field) const
Definition STObject.cpp:570
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:632
std::uint32_t getFlags() const
Definition STObject.cpp:492
Expected< void, std::string > checkBatchSign(Rules const &rules) const
Definition STTx.cpp:269
TxType getTxnType() const
Definition STTx.h:180
uint256 getTransactionID() const
Definition STTx.h:192
static NotTEC checkSign(PreclaimContext const &ctx)
static NotTEC checkBatchSign(PreclaimContext const &ctx)
ApplyView & view()
Definition Transactor.h:128
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
beast::Journal debugLog()
Returns a debug journal.
Definition Log.cpp:445
constexpr std::uint32_t tfInnerBatchTxn
Definition TxFlags.h:41
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:252
constexpr std::uint32_t tfAllOrNothing
Definition TxFlags.h:256
std::size_t constexpr maxBatchTxCount
The maximum number of transactions that can be in a batch.
Definition Protocol.h:297
constexpr std::uint32_t tfOnlyOne
Definition TxFlags.h:257
constexpr std::uint32_t const tfBatchMask
Definition TxFlags.h:265
@ 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:649
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
constexpr std::uint32_t tfUntilFailure
Definition TxFlags.h:258
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:259
@ tesSUCCESS
Definition TER.h:225
T popcount(T... args)
XRPAmount base
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:53
State information when preflighting a tx.
Definition Transactor.h:15
beast::Journal const j
Definition Transactor.h:22
Application & app
Definition Transactor.h:17