rippled
Loading...
Searching...
No Matches
Batch.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2024 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/tx/apply.h>
21#include <xrpld/app/tx/detail/Batch.h>
22
23#include <xrpl/basics/Log.h>
24#include <xrpl/ledger/Sandbox.h>
25#include <xrpl/ledger/View.h>
26#include <xrpl/protocol/Feature.h>
27#include <xrpl/protocol/Indexes.h>
28#include <xrpl/protocol/TER.h>
29#include <xrpl/protocol/TxFlags.h>
30
31namespace ripple {
32
53XRPAmount
54Batch::calculateBaseFee(ReadView const& view, STTx const& tx)
55{
56 XRPAmount const maxAmount{
58
59 // batchBase: view.fees().base for batch processing + default base fee
60 XRPAmount const baseFee = Transactor::calculateBaseFee(view, tx);
61
62 // LCOV_EXCL_START
63 if (baseFee > maxAmount - view.fees().base)
64 {
65 JLOG(debugLog().error()) << "BatchTrace: Base fee overflow detected.";
66 return XRPAmount{INITIAL_XRP};
67 }
68 // LCOV_EXCL_STOP
69
70 XRPAmount const batchBase = view.fees().base + baseFee;
71
72 // Calculate the Inner Txn Fees
73 XRPAmount txnFees{0};
74 if (tx.isFieldPresent(sfRawTransactions))
75 {
76 auto const& txns = tx.getFieldArray(sfRawTransactions);
77
78 // LCOV_EXCL_START
79 if (txns.size() > maxBatchTxCount)
80 {
81 JLOG(debugLog().error())
82 << "BatchTrace: Raw Transactions array exceeds max entries.";
83 return XRPAmount{INITIAL_XRP};
84 }
85 // LCOV_EXCL_STOP
86
87 for (STObject txn : txns)
88 {
89 STTx const stx = STTx{std::move(txn)};
90
91 // LCOV_EXCL_START
92 if (stx.getTxnType() == ttBATCH)
93 {
94 JLOG(debugLog().error())
95 << "BatchTrace: Inner Batch transaction found.";
96 return XRPAmount{INITIAL_XRP};
97 }
98 // LCOV_EXCL_STOP
99
100 auto const fee = ripple::calculateBaseFee(view, stx);
101 // LCOV_EXCL_START
102 if (txnFees > maxAmount - fee)
103 {
104 JLOG(debugLog().error())
105 << "BatchTrace: XRPAmount overflow in txnFees calculation.";
106 return XRPAmount{INITIAL_XRP};
107 }
108 // LCOV_EXCL_STOP
109 txnFees += fee;
110 }
111 }
112
113 // Calculate the Signers/BatchSigners Fees
114 std::int32_t signerCount = 0;
115 if (tx.isFieldPresent(sfBatchSigners))
116 {
117 auto const& signers = tx.getFieldArray(sfBatchSigners);
118
119 // LCOV_EXCL_START
120 if (signers.size() > maxBatchTxCount)
121 {
122 JLOG(debugLog().error())
123 << "BatchTrace: Batch Signers array exceeds max entries.";
124 return XRPAmount{INITIAL_XRP};
125 }
126 // LCOV_EXCL_STOP
127
128 for (STObject const& signer : signers)
129 {
130 if (signer.isFieldPresent(sfTxnSignature))
131 signerCount += 1;
132 else if (signer.isFieldPresent(sfSigners))
133 signerCount += signer.getFieldArray(sfSigners).size();
134 }
135 }
136
137 // LCOV_EXCL_START
138 if (signerCount > 0 && view.fees().base > maxAmount / signerCount)
139 {
140 JLOG(debugLog().error())
141 << "BatchTrace: XRPAmount overflow in signerCount calculation.";
142 return XRPAmount{INITIAL_XRP};
143 }
144 // LCOV_EXCL_STOP
145
146 XRPAmount signerFees = signerCount * view.fees().base;
147
148 // LCOV_EXCL_START
149 if (signerFees > maxAmount - txnFees)
150 {
151 JLOG(debugLog().error())
152 << "BatchTrace: XRPAmount overflow in signerFees calculation.";
153 return XRPAmount{INITIAL_XRP};
154 }
155 if (txnFees + signerFees > maxAmount - batchBase)
156 {
157 JLOG(debugLog().error())
158 << "BatchTrace: XRPAmount overflow in total fee calculation.";
159 return XRPAmount{INITIAL_XRP};
160 }
161 // LCOV_EXCL_STOP
162
163 // 10 drops per batch signature + sum of inner tx fees + batchBase
164 return signerFees + txnFees + batchBase;
165}
166
169{
170 return tfBatchMask;
171}
172
206NotTEC
208{
209 auto const parentBatchId = ctx.tx.getTransactionID();
210 auto const flags = ctx.tx.getFlags();
211
212 if (std::popcount(
213 flags &
215 {
216 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
217 << "too many flags.";
218 return temINVALID_FLAG;
219 }
220
221 auto const& rawTxns = ctx.tx.getFieldArray(sfRawTransactions);
222 if (rawTxns.size() <= 1)
223 {
224 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
225 << "txns array must have at least 2 entries.";
226 return temARRAY_EMPTY;
227 }
228
229 if (rawTxns.size() > maxBatchTxCount)
230 {
231 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
232 << "txns array exceeds 8 entries.";
233 return temARRAY_TOO_LARGE;
234 }
235
236 // Validation Inner Batch Txns
237 std::unordered_set<uint256> uniqueHashes;
239 accountSeqTicket;
240 auto checkSignatureFields = [&parentBatchId, &j = ctx.j](
241 STObject const& sig,
242 uint256 const& hash,
243 char const* label = "") -> NotTEC {
244 if (sig.isFieldPresent(sfTxnSignature))
245 {
246 JLOG(j.debug())
247 << "BatchTrace[" << parentBatchId << "]: "
248 << "inner txn " << label << "cannot include TxnSignature. "
249 << "txID: " << hash;
250 return temBAD_SIGNATURE;
251 }
252
253 if (sig.isFieldPresent(sfSigners))
254 {
255 JLOG(j.debug())
256 << "BatchTrace[" << parentBatchId << "]: "
257 << "inner txn " << label << " cannot include Signers. "
258 << "txID: " << hash;
259 return temBAD_SIGNER;
260 }
261
262 if (!sig.getFieldVL(sfSigningPubKey).empty())
263 {
264 JLOG(j.debug())
265 << "BatchTrace[" << parentBatchId << "]: "
266 << "inner txn " << label << " SigningPubKey must be empty. "
267 << "txID: " << hash;
268 return temBAD_REGKEY;
269 }
270
271 return tesSUCCESS;
272 };
273 for (STObject rb : rawTxns)
274 {
275 STTx const stx = STTx{std::move(rb)};
276 auto const hash = stx.getTransactionID();
277 if (!uniqueHashes.emplace(hash).second)
278 {
279 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
280 << "duplicate Txn found. "
281 << "txID: " << hash;
282 return temREDUNDANT;
283 }
284
285 if (stx.getFieldU16(sfTransactionType) == ttBATCH)
286 {
287 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
288 << "batch cannot have an inner batch txn. "
289 << "txID: " << hash;
290 return temINVALID;
291 }
292
293 if (!(stx.getFlags() & tfInnerBatchTxn))
294 {
295 JLOG(ctx.j.debug())
296 << "BatchTrace[" << parentBatchId << "]: "
297 << "inner txn must have the tfInnerBatchTxn flag. "
298 << "txID: " << hash;
299 return temINVALID_FLAG;
300 }
301
302 if (auto const ret = checkSignatureFields(stx, hash))
303 return ret;
304
305 /* Placeholder for field that will be added by Lending Protocol
306 // Note that the CounterpartySignature is optional, and should not be
307 // included, but if it is, ensure it doesn't contain a signature.
308 if (stx.isFieldPresent(sfCounterpartySignature))
309 {
310 auto const counterpartySignature =
311 stx.getFieldObject(sfCounterpartySignature);
312 if (auto const ret = checkSignatureFields(
313 counterpartySignature, hash, "counterparty signature "))
314 {
315 return ret;
316 }
317 }
318 */
319
320 auto const innerAccount = stx.getAccountID(sfAccount);
321 if (auto const preflightResult = ripple::preflight(
322 ctx.app, ctx.rules, parentBatchId, stx, tapBATCH, ctx.j);
323 preflightResult.ter != tesSUCCESS)
324 {
325 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
326 << "inner txn preflight failed: "
327 << transHuman(preflightResult.ter) << " "
328 << "txID: " << hash;
330 }
331
332 // Check that the fee is zero
333 if (auto const fee = stx.getFieldAmount(sfFee);
334 !fee.native() || fee.xrp() != beast::zero)
335 {
336 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
337 << "inner txn must have a fee of 0. "
338 << "txID: " << hash;
339 return temBAD_FEE;
340 }
341
342 // Check that Sequence and TicketSequence are not both present
343 if (stx.isFieldPresent(sfTicketSequence) &&
344 stx.getFieldU32(sfSequence) != 0)
345 {
346 JLOG(ctx.j.debug())
347 << "BatchTrace[" << parentBatchId << "]: "
348 << "inner txn must have exactly one of Sequence and "
349 "TicketSequence. "
350 << "txID: " << hash;
351 return temSEQ_AND_TICKET;
352 }
353
354 // Verify that either Sequence or TicketSequence is present
355 if (!stx.isFieldPresent(sfTicketSequence) &&
356 stx.getFieldU32(sfSequence) == 0)
357 {
358 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
359 << "inner txn must have either Sequence or "
360 "TicketSequence. "
361 << "txID: " << hash;
362 return temSEQ_AND_TICKET;
363 }
364
365 // Duplicate sequence and ticket checks
366 if (flags & (tfAllOrNothing | tfUntilFailure))
367 {
368 if (auto const seq = stx.getFieldU32(sfSequence); seq != 0)
369 {
370 if (!accountSeqTicket[innerAccount].insert(seq).second)
371 {
372 JLOG(ctx.j.debug())
373 << "BatchTrace[" << parentBatchId << "]: "
374 << "duplicate sequence found: "
375 << "txID: " << hash;
376 return temREDUNDANT;
377 }
378 }
379
380 if (stx.isFieldPresent(sfTicketSequence))
381 {
382 if (auto const ticket = stx.getFieldU32(sfTicketSequence);
383 !accountSeqTicket[innerAccount].insert(ticket).second)
384 {
385 JLOG(ctx.j.debug())
386 << "BatchTrace[" << parentBatchId << "]: "
387 << "duplicate ticket found: "
388 << "txID: " << hash;
389 return temREDUNDANT;
390 }
391 }
392 }
393 }
394
395 return tesSUCCESS;
396}
397
398NotTEC
400{
401 auto const parentBatchId = ctx.tx.getTransactionID();
402 auto const outerAccount = ctx.tx.getAccountID(sfAccount);
403 auto const& rawTxns = ctx.tx.getFieldArray(sfRawTransactions);
404
405 // Build the signers list
406 std::unordered_set<AccountID> requiredSigners;
407 for (STObject const& rb : rawTxns)
408 {
409 auto const innerAccount = rb.getAccountID(sfAccount);
410
411 // If the inner account is the same as the outer account, do not add the
412 // inner account to the required signers set.
413 if (innerAccount != outerAccount)
414 requiredSigners.insert(innerAccount);
415 /* Placeholder for field that will be added by Lending Protocol
416 // Some transactions have a Counterparty, who must also sign the
417 // transaction if they are not the outer account
418 if (auto const counterparty = rb.at(~sfCounterparty);
419 counterparty && counterparty != outerAccount)
420 requiredSigners.insert(*counterparty);
421 */
422 }
423
424 // Validation Batch Signers
426 if (ctx.tx.isFieldPresent(sfBatchSigners))
427 {
428 STArray const& signers = ctx.tx.getFieldArray(sfBatchSigners);
429
430 // Check that the batch signers array is not too large.
431 if (signers.size() > maxBatchTxCount)
432 {
433 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
434 << "signers array exceeds 8 entries.";
435 return temARRAY_TOO_LARGE;
436 }
437
438 // Add batch signers to the set to ensure all signer accounts are
439 // unique. Meanwhile, remove signer accounts from the set of inner
440 // transaction accounts (`requiredSigners`). By the end of the loop,
441 // `requiredSigners` should be empty, indicating that all inner
442 // accounts are matched with signers.
443 for (auto const& signer : signers)
444 {
445 AccountID const signerAccount = signer.getAccountID(sfAccount);
446 if (signerAccount == outerAccount)
447 {
448 JLOG(ctx.j.debug())
449 << "BatchTrace[" << parentBatchId << "]: "
450 << "signer cannot be the outer account: " << signerAccount;
451 return temBAD_SIGNER;
452 }
453
454 if (!batchSigners.insert(signerAccount).second)
455 {
456 JLOG(ctx.j.debug())
457 << "BatchTrace[" << parentBatchId << "]: "
458 << "duplicate signer found: " << signerAccount;
459 return temREDUNDANT;
460 }
461
462 // Check that the batch signer is in the required signers set.
463 // Remove it if it does, as it can be crossed off the list.
464 if (requiredSigners.erase(signerAccount) == 0)
465 {
466 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
467 << "no account signature for inner txn.";
468 return temBAD_SIGNER;
469 }
470 }
471
472 // Check the batch signers signatures.
473 auto const sigResult = ctx.tx.checkBatchSign(
475
476 if (!sigResult)
477 {
478 JLOG(ctx.j.debug())
479 << "BatchTrace[" << parentBatchId << "]: "
480 << "invalid batch txn signature: " << sigResult.error();
481 return temBAD_SIGNATURE;
482 }
483 }
484
485 if (!requiredSigners.empty())
486 {
487 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
488 << "invalid batch signers.";
489 return temBAD_SIGNER;
490 }
491 return tesSUCCESS;
492}
493
511NotTEC
513{
514 if (auto ret = Transactor::checkSign(ctx); !isTesSuccess(ret))
515 return ret;
516
517 if (auto ret = Transactor::checkBatchSign(ctx); !isTesSuccess(ret))
518 return ret;
519
520 return tesSUCCESS;
521}
522
533TER
535{
536 return tesSUCCESS;
537}
538
539} // namespace ripple
Stream debug() const
Definition Journal.h:328
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
Definition Batch.cpp:168
TER doApply() override
Applies the outer batch transaction.
Definition Batch.cpp:534
static NotTEC preflight(PreflightContext const &ctx)
Performs preflight validation checks for a Batch transaction.
Definition Batch.cpp:207
static NotTEC preflightSigValidated(PreflightContext const &ctx)
Definition Batch.cpp:399
static NotTEC checkSign(PreclaimContext const &ctx)
Checks the validity of signatures for a batch transaction.
Definition Batch.cpp:512
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Calculates the total base fee for a batch transaction.
Definition Batch.cpp:54
A view into a ledger.
Definition ReadView.h:51
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:657
STArray const & getFieldArray(SField const &field) const
Definition STObject.cpp:702
std::uint16_t getFieldU16(SField const &field) const
Definition STObject.cpp:609
std::uint32_t getFieldU32(SField const &field) const
Definition STObject.cpp:615
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:671
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:484
std::uint32_t getFlags() const
Definition STObject.cpp:537
TxType getTxnType() const
Definition STTx.h:237
uint256 getTransactionID() const
Definition STTx.h:249
Expected< void, std::string > checkBatchSign(RequireFullyCanonicalSig requireCanonicalSig, Rules const &rules) const
Definition STTx.cpp:302
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
static NotTEC checkSign(PreclaimContext const &ctx)
ApplyView & view()
Definition Transactor.h:163
static NotTEC checkBatchSign(PreclaimContext const &ctx)
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:25
std::string transHuman(TER code)
Definition TER.cpp:273
constexpr std::uint32_t tfAllOrNothing
Definition TxFlags.h:276
constexpr std::uint32_t const tfBatchMask
Definition TxFlags.h:285
PreflightResult preflight(Application &app, Rules const &rules, STTx const &tx, ApplyFlags flags, beast::Journal j)
Gate a transaction based on static information.
constexpr std::uint32_t tfOnlyOne
Definition TxFlags.h:277
constexpr std::uint32_t tfIndependent
Definition TxFlags.h:279
XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Compute only the expected base fee for a transaction.
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
constexpr std::uint32_t tfUntilFailure
Definition TxFlags.h:278
beast::Journal debugLog()
Returns a debug journal.
Definition Log.cpp:476
@ tesSUCCESS
Definition TER.h:244
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
@ tapBATCH
Definition ApplyView.h:45
std::size_t constexpr maxBatchTxCount
The maximum number of transactions that can be in a batch.
Definition Protocol.h:182
constexpr std::uint32_t tfInnerBatchTxn
Definition TxFlags.h:61
@ temREDUNDANT
Definition TER.h:112
@ temBAD_FEE
Definition TER.h:92
@ temBAD_SIGNER
Definition TER.h:115
@ temSEQ_AND_TICKET
Definition TER.h:126
@ temINVALID_INNER_BATCH
Definition TER.h:143
@ temBAD_REGKEY
Definition TER.h:98
@ temINVALID
Definition TER.h:110
@ temINVALID_FLAG
Definition TER.h:111
@ temARRAY_EMPTY
Definition TER.h:140
@ temARRAY_TOO_LARGE
Definition TER.h:141
@ temBAD_SIGNATURE
Definition TER.h:105
T popcount(T... args)
XRPAmount base
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:80
State information when preflighting a tx.
Definition Transactor.h:35
beast::Journal const j
Definition Transactor.h:42