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#include <xrpld/ledger/Sandbox.h>
23#include <xrpld/ledger/View.h>
24
25#include <xrpl/basics/Log.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 throw std::overflow_error("XRPAmount overflow");
65 // LCOV_EXCL_STOP
66
67 XRPAmount const batchBase = view.fees().base + baseFee;
68
69 // Calculate the Inner Txn Fees
70 XRPAmount txnFees{0};
71 if (tx.isFieldPresent(sfRawTransactions))
72 {
73 auto const& txns = tx.getFieldArray(sfRawTransactions);
74
75 XRPL_ASSERT(
76 txns.size() <= maxBatchTxCount,
77 "Raw Transactions array exceeds max entries.");
78
79 // LCOV_EXCL_START
80 if (txns.size() > maxBatchTxCount)
82 "Raw Transactions array exceeds max entries");
83 // LCOV_EXCL_STOP
84
85 for (STObject txn : txns)
86 {
87 STTx const stx = STTx{std::move(txn)};
88
89 XRPL_ASSERT(
90 stx.getTxnType() != ttBATCH, "Inner Batch transaction found.");
91
92 // LCOV_EXCL_START
93 if (stx.getTxnType() == ttBATCH)
94 throw std::invalid_argument("Inner Batch transaction found");
95 // LCOV_EXCL_STOP
96
97 auto const fee = ripple::calculateBaseFee(view, stx);
98 // LCOV_EXCL_START
99 if (txnFees > maxAmount - fee)
100 throw std::overflow_error("XRPAmount overflow");
101 // LCOV_EXCL_STOP
102 txnFees += fee;
103 }
104 }
105
106 // Calculate the Signers/BatchSigners Fees
107 std::int32_t signerCount = 0;
108 if (tx.isFieldPresent(sfBatchSigners))
109 {
110 auto const& signers = tx.getFieldArray(sfBatchSigners);
111 XRPL_ASSERT(
113 "Batch Signers array exceeds max entries.");
114
115 // LCOV_EXCL_START
117 throw std::length_error("Batch Signers array exceeds max entries");
118 // LCOV_EXCL_STOP
119
120 for (STObject const& signer : signers)
121 {
122 if (signer.isFieldPresent(sfTxnSignature))
123 signerCount += 1;
124 else if (signer.isFieldPresent(sfSigners))
125 signerCount += signer.getFieldArray(sfSigners).size();
126 }
127 }
128
129 // LCOV_EXCL_START
130 if (signerCount > 0 && view.fees().base > maxAmount / signerCount)
131 throw std::overflow_error("XRPAmount overflow");
132 // LCOV_EXCL_STOP
133
134 XRPAmount signerFees = signerCount * view.fees().base;
135
136 // LCOV_EXCL_START
137 if (signerFees > maxAmount - txnFees)
138 throw std::overflow_error("XRPAmount overflow");
139 if (txnFees + signerFees > maxAmount - batchBase)
140 throw std::overflow_error("XRPAmount overflow");
141 // LCOV_EXCL_STOP
142
143 // 10 drops per batch signature + sum of inner tx fees + batchBase
144 return signerFees + txnFees + batchBase;
145}
146
180NotTEC
182{
183 if (!ctx.rules.enabled(featureBatch))
184 return temDISABLED;
185
186 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
187 return ret;
188
189 auto const parentBatchId = ctx.tx.getTransactionID();
190 auto const outerAccount = ctx.tx.getAccountID(sfAccount);
191 auto const flags = ctx.tx.getFlags();
192
193 if (flags & tfBatchMask)
194 {
195 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
196 << "invalid flags.";
197 return temINVALID_FLAG;
198 }
199
200 if (std::popcount(
201 flags &
203 {
204 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
205 << "too many flags.";
206 return temINVALID_FLAG;
207 }
208
209 auto const& rawTxns = ctx.tx.getFieldArray(sfRawTransactions);
210 if (rawTxns.size() <= 1)
211 {
212 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
213 << "txns array must have at least 2 entries.";
214 return temARRAY_EMPTY;
215 }
216
217 if (rawTxns.size() > maxBatchTxCount)
218 {
219 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
220 << "txns array exceeds 8 entries.";
221 return temARRAY_TOO_LARGE;
222 }
223
224 // Validation Inner Batch Txns
225 std::unordered_set<AccountID> requiredSigners;
226 std::unordered_set<uint256> uniqueHashes;
228 accountSeqTicket;
229 for (STObject rb : rawTxns)
230 {
231 STTx const stx = STTx{std::move(rb)};
232 auto const hash = stx.getTransactionID();
233 if (!uniqueHashes.emplace(hash).second)
234 {
235 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
236 << "duplicate Txn found. "
237 << "txID: " << hash;
238 return temREDUNDANT;
239 }
240
241 if (stx.getFieldU16(sfTransactionType) == ttBATCH)
242 {
243 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
244 << "batch cannot have an inner batch txn. "
245 << "txID: " << hash;
246 return temINVALID;
247 }
248
249 if (!(stx.getFlags() & tfInnerBatchTxn))
250 {
251 JLOG(ctx.j.debug())
252 << "BatchTrace[" << parentBatchId << "]: "
253 << "inner txn must have the tfInnerBatchTxn flag. "
254 << "txID: " << hash;
255 return temINVALID_FLAG;
256 }
257
258 if (stx.isFieldPresent(sfTxnSignature))
259 {
260 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
261 << "inner txn cannot include TxnSignature. "
262 << "txID: " << hash;
263 return temBAD_SIGNATURE;
264 }
265
266 if (stx.isFieldPresent(sfSigners))
267 {
268 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
269 << "inner txn cannot include Signers. "
270 << "txID: " << hash;
271 return temBAD_SIGNER;
272 }
273
274 if (!stx.getSigningPubKey().empty())
275 {
276 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
277 << "inner txn SigningPubKey must be empty. "
278 << "txID: " << hash;
279 return temBAD_REGKEY;
280 }
281
282 auto const innerAccount = stx.getAccountID(sfAccount);
283 if (auto const preflightResult = ripple::preflight(
284 ctx.app, ctx.rules, parentBatchId, stx, tapBATCH, ctx.j);
285 preflightResult.ter != tesSUCCESS)
286 {
287 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
288 << "inner txn preflight failed: "
289 << transHuman(preflightResult.ter) << " "
290 << "txID: " << hash;
292 }
293
294 // Check that the fee is zero
295 if (auto const fee = stx.getFieldAmount(sfFee);
296 !fee.native() || fee.xrp() != beast::zero)
297 {
298 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
299 << "inner txn must have a fee of 0. "
300 << "txID: " << hash;
301 return temBAD_FEE;
302 }
303
304 // Check that Sequence and TicketSequence are not both present
305 if (stx.isFieldPresent(sfTicketSequence) &&
306 stx.getFieldU32(sfSequence) != 0)
307 {
308 JLOG(ctx.j.debug())
309 << "BatchTrace[" << parentBatchId << "]: "
310 << "inner txn must have exactly one of Sequence and "
311 "TicketSequence. "
312 << "txID: " << hash;
313 return temSEQ_AND_TICKET;
314 }
315
316 // Verify that either Sequence or TicketSequence is present
317 if (!stx.isFieldPresent(sfTicketSequence) &&
318 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
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())
335 << "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())
348 << "BatchTrace[" << parentBatchId << "]: "
349 << "duplicate ticket found: "
350 << "txID: " << hash;
351 return temREDUNDANT;
352 }
353 }
354 }
355
356 // If the inner account is the same as the outer account, do not add the
357 // inner account to the required signers set.
358 if (innerAccount != outerAccount)
359 requiredSigners.insert(innerAccount);
360 }
361
362 // LCOV_EXCL_START
363 if (auto const ret = preflight2(ctx); !isTesSuccess(ret))
364 return ret;
365 // LCOV_EXCL_STOP
366
367 // Validation Batch Signers
369 if (ctx.tx.isFieldPresent(sfBatchSigners))
370 {
371 STArray const& signers = ctx.tx.getFieldArray(sfBatchSigners);
372
373 // Check that the batch signers array is not too large.
375 {
376 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
377 << "signers array exceeds 8 entries.";
378 return temARRAY_TOO_LARGE;
379 }
380
381 // Add batch signers to the set to ensure all signer accounts are
382 // unique. Meanwhile, remove signer accounts from the set of inner
383 // transaction accounts (`requiredSigners`). By the end of the loop,
384 // `requiredSigners` should be empty, indicating that all inner
385 // accounts are matched with signers.
386 for (auto const& signer : signers)
387 {
388 AccountID const signerAccount = signer.getAccountID(sfAccount);
389 if (signerAccount == outerAccount)
390 {
391 JLOG(ctx.j.debug())
392 << "BatchTrace[" << parentBatchId << "]: "
393 << "signer cannot be the outer account: " << signerAccount;
394 return temBAD_SIGNER;
395 }
396
397 if (!batchSigners.insert(signerAccount).second)
398 {
399 JLOG(ctx.j.debug())
400 << "BatchTrace[" << parentBatchId << "]: "
401 << "duplicate signer found: " << signerAccount;
402 return temREDUNDANT;
403 }
404
405 // Check that the batch signer is in the required signers set.
406 // Remove it if it does, as it can be crossed off the list.
407 if (requiredSigners.erase(signerAccount) == 0)
408 {
409 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
410 << "no account signature for inner txn.";
411 return temBAD_SIGNER;
412 }
413 }
414
415 // Check the batch signers signatures.
416 auto const sigResult = ctx.tx.checkBatchSign(
418
419 if (!sigResult)
420 {
421 JLOG(ctx.j.debug())
422 << "BatchTrace[" << parentBatchId << "]: "
423 << "invalid batch txn signature: " << sigResult.error();
424 return temBAD_SIGNATURE;
425 }
426 }
427
428 if (!requiredSigners.empty())
429 {
430 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
431 << "invalid batch signers.";
432 return temBAD_SIGNER;
433 }
434 return tesSUCCESS;
435}
436
454NotTEC
456{
457 if (auto ret = Transactor::checkSign(ctx); !isTesSuccess(ret))
458 return ret;
459
460 if (auto ret = Transactor::checkBatchSign(ctx); !isTesSuccess(ret))
461 return ret;
462
463 return tesSUCCESS;
464}
465
476TER
478{
479 return tesSUCCESS;
480}
481
482} // namespace ripple
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:719
Stream debug() const
Definition: Journal.h:328
TER doApply() override
Applies the outer batch transaction.
Definition: Batch.cpp:477
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:455
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:52
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
AccountID getAccountID(SField const &field) const
Definition: STObject.cpp:651
STArray const & getFieldArray(SField const &field) const
Definition: STObject.cpp:686
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:665
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:484
std::uint32_t getFlags() const
Definition: STObject.cpp:537
Blob getSigningPubKey() const
Definition: STTx.h:213
TxType getTxnType() const
Definition: STTx.h:207
uint256 getTransactionID() const
Definition: STTx.h:219
Expected< void, std::string > checkBatchSign(RequireFullyCanonicalSig requireCanonicalSig, Rules const &rules) const
Definition: STTx.cpp:269
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition: Transactor.cpp:224
static NotTEC checkSign(PreclaimContext const &ctx)
Definition: Transactor.cpp:572
ApplyView & view()
Definition: Transactor.h:159
static NotTEC checkBatchSign(PreclaimContext const &ctx)
Definition: Transactor.cpp:624
Set the fee on a JTx.
Definition: fee.h:37
Match set account flags.
Definition: flags.h:125
T emplace(T... args)
T empty(T... args)
T erase(T... args)
T insert(T... args)
T max(T... args)
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition: multisign.cpp:34
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::string transHuman(TER code)
Definition: TER.cpp:271
constexpr std::uint32_t tfAllOrNothing
Definition: TxFlags.h:246
constexpr std::uint32_t const tfBatchMask
Definition: TxFlags.h:255
PreflightResult preflight(Application &app, Rules const &rules, STTx const &tx, ApplyFlags flags, beast::Journal j)
Gate a transaction based on static information.
Definition: applySteps.cpp:325
constexpr std::uint32_t tfOnlyOne
Definition: TxFlags.h:247
constexpr std::uint32_t tfIndependent
Definition: TxFlags.h:249
XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Compute only the expected base fee for a transaction.
Definition: applySteps.cpp:427
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:91
constexpr std::uint32_t tfUntilFailure
Definition: TxFlags.h:248
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:160
@ tesSUCCESS
Definition: TER.h:244
bool isTesSuccess(TER x) noexcept
Definition: TER.h:672
@ tapBATCH
Definition: ApplyView.h:46
std::size_t constexpr maxBatchTxCount
The maximum number of transactions that can be in a batch.
Definition: Protocol.h:173
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
@ temDISABLED
Definition: TER.h:114
@ temBAD_SIGNATURE
Definition: TER.h:105
T popcount(T... args)
XRPAmount base
Definition: protocol/Fees.h:34
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:79
State information when preflighting a tx.
Definition: Transactor.h:34
beast::Journal const j
Definition: Transactor.h:41
NotTEC const ter
Intermediate transaction result.
Definition: applySteps.h:180
Set the sequence number on a JTx.
Definition: seq.h:34
A signer in a SignerList.
Definition: multisign.h:39