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