diff --git a/include/xrpl/protocol/Batch.h b/include/xrpl/protocol/Batch.h new file mode 100644 index 00000000000..ca830dd4f8f --- /dev/null +++ b/include/xrpl/protocol/Batch.h @@ -0,0 +1,37 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include + +namespace ripple { + +inline void +serializeBatch( + Serializer& msg, + std::uint32_t const& flags, + STVector256 const& txids) +{ + msg.add32(HashPrefix::batch); + msg.add32(flags); + msg.add32(txids.size()); + for (auto const& txid : txids) + msg.addBitString(txid); +} + +} // namespace ripple \ No newline at end of file diff --git a/include/xrpl/protocol/HashPrefix.h b/include/xrpl/protocol/HashPrefix.h index bc9c23d1910..577c8edcf74 100644 --- a/include/xrpl/protocol/HashPrefix.h +++ b/include/xrpl/protocol/HashPrefix.h @@ -84,6 +84,9 @@ enum class HashPrefix : std::uint32_t { /** Payment Channel Claim */ paymentChannelClaim = detail::make_hash_prefix('C', 'L', 'M'), + + /** Batch */ + batch = detail::make_hash_prefix('B', 'C', 'H'), }; template diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index 7bfc2b27d70..1540d181af2 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -621,6 +621,7 @@ extern SF_VECTOR256 const sfIndexes; extern SF_VECTOR256 const sfHashes; extern SF_VECTOR256 const sfAmendments; extern SF_VECTOR256 const sfNFTokenOffers; +extern SF_VECTOR256 const sfTxIDs; // inner object // OBJECT/1 is reserved for end of object diff --git a/src/libxrpl/protocol/SField.cpp b/src/libxrpl/protocol/SField.cpp index 3b1e1ca7b9e..f75d2d051f0 100644 --- a/src/libxrpl/protocol/SField.cpp +++ b/src/libxrpl/protocol/SField.cpp @@ -337,6 +337,7 @@ CONSTRUCT_TYPED_SFIELD(sfIndexes, "Indexes", VECTOR25 CONSTRUCT_TYPED_SFIELD(sfHashes, "Hashes", VECTOR256, 2); CONSTRUCT_TYPED_SFIELD(sfAmendments, "Amendments", VECTOR256, 3); CONSTRUCT_TYPED_SFIELD(sfNFTokenOffers, "NFTokenOffers", VECTOR256, 4); +CONSTRUCT_TYPED_SFIELD(sfTxIDs, "TxIDs", VECTOR256, 5); // path set CONSTRUCT_UNTYPED_SFIELD(sfPaths, "Paths", PATHSET, 1); diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 305c1d74fc7..786b2dc2037 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -22,12 +22,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -400,16 +402,11 @@ STTx::checkBatchSingleSign( if (batchSigner.isFieldPresent(sfSigners)) return Unexpected("Cannot both single- and multi-sign."); - Serializer const dataStart{startMultiSigningData(*this)}; - - auto const accountID = batchSigner.getAccountID(sfAccount); - bool validSig = false; try { - Serializer s = dataStart; - finishMultiSigningData(accountID, s); - + Serializer msg; + serializeBatch(msg, getFlags(), getFieldV256(sfTxIDs)); bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) || (requireCanonicalSig == RequireFullyCanonicalSig::yes); @@ -417,10 +414,9 @@ STTx::checkBatchSingleSign( if (publicKeyType(makeSlice(spk))) { Blob const signature = batchSigner.getFieldVL(sfTxnSignature); - validSig = verify( PublicKey(makeSlice(spk)), - s.slice(), + msg.slice(), makeSlice(signature), fullyCanonical); } @@ -459,10 +455,8 @@ STTx::checkBatchMultiSign( signers.size() > maxMultiSigners(&rules)) return Unexpected("Invalid Signers array size."); - // We can ease the computational load inside the loop a bit by - // pre-constructing part of the data that we hash. Fill a Serializer - // with the stuff that stays constant from signature to signature. - Serializer const dataStart{startMultiSigningData(*this)}; + Serializer msg; + serializeBatch(msg, getFlags(), getFieldV256(sfTxIDs)); // We also use the sfAccount field inside the loop. Get it once. auto const txnAccountID = batchSigner.getAccountID(sfAccount); @@ -497,18 +491,14 @@ STTx::checkBatchMultiSign( bool validSig = false; try { - Serializer s = dataStart; - finishMultiSigningData(accountID, s); - auto spk = signer.getFieldVL(sfSigningPubKey); if (publicKeyType(makeSlice(spk))) { Blob const signature = signer.getFieldVL(sfTxnSignature); - validSig = verify( PublicKey(makeSlice(spk)), - s.slice(), + msg.slice(), makeSlice(signature), fullyCanonical); } diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index 2b18cd32aa6..d6c32887d14 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -206,7 +206,7 @@ transResults() MAKE_ERROR(temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT, "Malformed: Bad reward amount."), MAKE_ERROR(temARRAY_EMPTY, "Malformed: Array is empty."), MAKE_ERROR(temARRAY_TOO_LARGE, "Malformed: Array is too large."), - MAKE_ERROR(temINVALID_BATCH, "The transaction cannot contain BatchTxn."), + MAKE_ERROR(temINVALID_BATCH, "Invalid inner batch transaction type."), MAKE_ERROR(terRETRY, "Retry transaction."), MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."), diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index f1e029aad7b..2a5de5ca2b3 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -519,6 +519,7 @@ TxFormats::TxFormats() ttBATCH, { {sfRawTransactions, soeREQUIRED}, + {sfTxIDs, soeREQUIRED}, {sfBatchSigners, soeOPTIONAL}, }, commonFields); diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index e6021b12097..0e3ca760ae5 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -19,7 +19,9 @@ #include #include +#include #include +#include #include #include #include @@ -167,36 +169,6 @@ class Batch_test : public beast::unit_test::suite return jv; } - Json::Value - addBatchMultiSignatures( - Json::Value jv, - int index, - jtx::Account account, - std::vector const& signers) - { - auto const ojv = jv; - Json::Value jvSigners = Json::arrayValue; - for (std::size_t i = 0; i < signers.size(); ++i) - { - Serializer ss{buildMultiSigningData( - jtx::parse(ojv), signers[i].account.id())}; - auto const sig = ripple::sign( - signers[i].account.pk(), signers[i].account.sk(), ss.slice()); - - jvSigners[i][sfSigner.jsonName][sfAccount.jsonName] = - signers[i].account.human(); - jvSigners[i][sfSigner.jsonName][sfSigningPubKey.jsonName] = - strHex(signers[i].account.pk()); - jvSigners[i][sfSigner.jsonName][sfTxnSignature.jsonName] = - strHex(Slice{sig.data(), sig.size()}); - } - jv[sfBatchSigners.jsonName][index][sfBatchSigner.jsonName] - [sfAccount.jsonName] = account.human(); - jv[sfBatchSigners.jsonName][index][sfBatchSigner.jsonName] - [sfSigners.jsonName] = jvSigners; - return jv; - } - void testEnable(FeatureBitset features) { @@ -209,6 +181,7 @@ class Batch_test : public beast::unit_test::suite { auto const amend = withBatch ? features : features - featureBatch; test::jtx::Env env{*this, envconfig(), amend}; + auto const feeDrops = env.current()->fees().base; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -220,21 +193,14 @@ class Batch_test : public beast::unit_test::suite auto const preBob = env.balance(bob); auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 1, seq); + auto const batchFee = feeDrops * 2; auto const txResult = withBatch ? ter(tesSUCCESS) : ter(temDISABLED); - env(jv, txResult); + + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), + txResult); env.close(); } } @@ -262,54 +228,48 @@ class Batch_test : public beast::unit_test::suite // temINVALID_FLAG: Batch: invalid flags. { auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - env(jv, txflags(tfRequireAuth), ter(temINVALID_FLAG)); + auto const batchFee = feeDrops * 2; + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + txflags(tfRequireAuth), + ter(temINVALID_FLAG)); env.close(); } - // temMALFORMED: Batch: txns array empty. + // temARRAY_EMPTY: Batch: txns array empty. { auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - env(jv, ter(temMALFORMED)); + auto const batchFee = feeDrops * 2; + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + ter(temARRAY_EMPTY)); env.close(); } - // temMALFORMED: Batch: txns array exceeds 12 entries. + // temARRAY_TOO_LARGE: Batch: txns array exceeds 12 entries. { auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - for (std::uint8_t i = 1; i < 13; ++i) - { - Json::Value const tx = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx, alice, i, seq); - } - - env(jv, ter(temMALFORMED)); + auto const batchFee = feeDrops * 2; + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 2, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 3, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 4, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 5, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 6, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 7, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 8, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 9, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 10, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 11, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 12, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 13, seq), + ter(temARRAY_TOO_LARGE)); env.close(); } // temBAD_SIGNATURE: Batch: invalid batch txn signature. { std::vector const signers = {{ - {0, carol}, + {0, bob}, }}; Json::Value jv; @@ -324,21 +284,34 @@ class Batch_test : public beast::unit_test::suite // Batch Transactions jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; + jv[sfTxIDs.jsonName] = Json::Value{Json::arrayValue}; // Tx 1 Json::Value tx1 = pay(alice, bob, XRP(10)); jv = addBatchTx(jv, tx1, alice, 1, env.seq(alice)); + auto txn1 = jv[jss::RawTransactions][0u][jss::RawTransaction]; + STParsedJSONObject parsed1(std::string(jss::tx_json), txn1); + STTx const stx1 = STTx{std::move(parsed1.object.value())}; + jv[sfTxIDs.jsonName].append(to_string(stx1.getTransactionID())); // Tx 2 Json::Value const tx2 = pay(bob, alice, XRP(5)); jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob)); + auto txn2 = jv[jss::RawTransactions][1u][jss::RawTransaction]; + STParsedJSONObject parsed2(std::string(jss::tx_json), txn2); + STTx const stx2 = STTx{std::move(parsed2.object.value())}; + jv[sfTxIDs.jsonName].append(to_string(stx2.getTransactionID())); for (auto const& signer : signers) { - Serializer ss{ - buildMultiSigningData(jtx::parse(jv), signer.account.id())}; + Serializer msg; + serializeBatch( + msg, + tfAllOrNothing, + STVector256( + {stx1.getTransactionID(), stx2.getTransactionID()})); auto const sig = ripple::sign( - signer.account.pk(), signer.account.sk(), ss.slice()); + signer.account.pk(), signer.account.sk(), msg.slice()); jv[sfBatchSigners.jsonName][signer.index] [sfBatchSigner.jsonName][sfAccount.jsonName] = signer.account.human(); @@ -349,118 +322,79 @@ class Batch_test : public beast::unit_test::suite [sfBatchSigner.jsonName][sfTxnSignature.jsonName] = strHex(Slice{sig.data(), sig.size()}); } + + jv = addBatchSignatures(jv, signers); + env(jv, ter(temBAD_SIGNATURE)); env.close(); } - // temMALFORMED: Batch: TransactionType missing in array entry. + // TODO: #40 failed: unhandled exception: Field not found: TransactionType + // // temINVALID_BATCH: Batch: TransactionType missing in array entry. // { // auto const seq = env.seq(alice); + // auto const batchFee = feeDrops * 2; // Json::Value jv; // jv[jss::TransactionType] = jss::Batch; // jv[jss::Account] = alice.human(); // jv[jss::Sequence] = seq; // jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; + // jv[sfTxIDs.jsonName] = Json::Value{Json::arrayValue}; // // Tx 1 - // Json::Value tx1 = pay(alice, bob, XRP(10)); - // // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); - // jv[sfRawTransactions.jsonName][0u][jss::RawTransaction].removeMember( - // jss::TransactionType); - - // env(jv, ter(temMALFORMED)); + // Json::Value tx; + // tx[jss::Account] = alice.human(); + // tx[jss::Destination] = bob.human(); + // tx[jss::Amount] = "10000000"; + // jv = addBatchTx(jv, tx, alice, 1, seq); + + // STParsedJSONObject parsed(std::string(jss::tx_json), tx); + // STTx const stx = STTx{std::move(parsed.object.value())}; + // jv[sfTxIDs.jsonName].append(to_string(stx.getTransactionID())); + + // env(jv, + // fee(batchFee), + // txflags(tfAllOrNothing), + // ter(temINVALID_BATCH)); // env.close(); // } - // temMALFORMED: Batch: batch cannot have inner batch txn. + // temINVALID_BATCH: Batch: batch cannot have inner batch txn. { auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value btx; - { - btx[jss::TransactionType] = jss::Batch; - btx[jss::Account] = alice.human(); - btx[jss::Sequence] = seq; - - // Batch Transactions - btx[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // bTx 1 - Json::Value const btx1 = pay(alice, bob, XRP(1)); - btx = addBatchTx(btx, btx1, alice, 0, seq, 0); - } - - jv = addBatchTx(jv, btx, alice, 1, seq); - - // Tx 2 - Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq); - - env(jv, ter(temMALFORMED)); + auto const batchFee = feeDrops * 2; + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add( + batch::batch(alice, seq, batchFee, tfAllOrNothing), + alice, + 1, + seq), + batch::add(pay(alice, bob, XRP(1)), alice, 2, seq), + ter(temINVALID_BATCH)); env.close(); } - // temMALFORMED: Batch: batch cannot have inner account delete txn. + // temINVALID_BATCH: Batch: batch cannot have inner account delete txn. { auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value tx1 = acctdelete(alice, bob); - jv = addBatchTx(jv, tx1, alice, 1, seq); - - // Tx 2 - Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq); - - env(jv, ter(temMALFORMED)); + auto const batchFee = feeDrops * 2; + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add(acctdelete(alice, bob), alice, 1, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 2, seq), + ter(temINVALID_BATCH)); env.close(); } // temBAD_SIGNER: Batch: inner txn not signed by the right user. { - std::vector const signers = {{ - {0, carol}, - }}; - - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = env.seq(alice); - auto const batchFee = - ((signers.size() + 2) * feeDrops) + feeDrops * 2; - jv[jss::Fee] = to_string(batchFee); - jv[jss::Flags] = tfAllOrNothing; - jv[jss::SigningPubKey] = strHex(alice.pk()); - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value tx1 = pay(alice, bob, XRP(10)); - jv = addBatchTx(jv, tx1, alice, 0, env.seq(alice), 0); - - // Tx 2 - Json::Value const tx2 = pay(bob, alice, XRP(5)); - jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob), 1); - - jv = addBatchSignatures(jv, signers); - env(jv, ter(temBAD_SIGNER)); + auto const seq = env.seq(alice); + auto const batchFee = ((1 + 2) * feeDrops) + feeDrops * 2; + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add(pay(alice, bob, XRP(10)), alice, 0, seq), + batch::add(pay(bob, alice, XRP(5)), alice, 0, env.seq(bob)), + batch::sig(carol), + ter(temBAD_SIGNER)); env.close(); } } @@ -498,35 +432,12 @@ class Batch_test : public beast::unit_test::suite auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); - std::vector const signers = {{ - {0, bob}, - }}; - - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = preAliceSeq; - auto const batchFee = ((signers.size() + 2) * feeDrops) + feeDrops * 2; - jv[jss::Fee] = to_string(batchFee); - jv[jss::Flags] = tfAllOrNothing; - jv[jss::SigningPubKey] = strHex(alice.pk()); - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value tx1 = pay(alice, bob, USD(10)); - jv = addBatchTx(jv, tx1, alice, 1, preAliceSeq); - - // Tx 2 - Json::Value const tx2 = pay(bob, alice, USD(5)); - jv = addBatchTx(jv, tx2, alice, 10, preBobSeq); - - jv = addBatchSignatures(jv, signers); - - // Internally telPRE_SEQ - env(jv, ter(tecBATCH_FAILURE)); - env.close(); + auto const batchFee = ((1 + 2) * feeDrops) + feeDrops * 2; + env(batch::batch(alice, preAliceSeq, batchFee, tfAllOrNothing), + batch::add(pay(alice, bob, XRP(10)), alice, 1, preAliceSeq), + batch::add(pay(bob, alice, XRP(5)), alice, 10, preBobSeq), + batch::sig(bob), + ter(tecBATCH_FAILURE)); // Alice pays fee & Bob should not be affected. BEAST_EXPECT(env.seq(alice) == preAliceSeq + 1); @@ -570,32 +481,12 @@ class Batch_test : public beast::unit_test::suite auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); - std::vector const signers = {{ - {0, bob}, - }}; - - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = env.seq(alice); - jv[jss::Fee] = to_string(feeDrops * 2); - jv[jss::Flags] = tfAllOrNothing; - jv[jss::SigningPubKey] = strHex(alice.pk()); - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value tx1 = pay(alice, bob, USD(10)); - jv = addBatchTx(jv, tx1, alice, 1, env.seq(alice)); - - // Tx 2 - Json::Value const tx2 = pay(bob, alice, USD(5)); - jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob)); - - jv = addBatchSignatures(jv, signers); - - env(jv, ter(telINSUF_FEE_P)); + auto const batchFee = feeDrops * 2; + env(batch::batch(alice, preAliceSeq, batchFee, tfAllOrNothing), + batch::add(pay(alice, bob, XRP(10)), alice, 1, preAliceSeq), + batch::add(pay(bob, alice, XRP(5)), alice, 0, preBobSeq), + batch::sig(bob), + ter(telINSUF_FEE_P)); env.close(); // Alice & Bob should not be affected. @@ -626,120 +517,62 @@ class Batch_test : public beast::unit_test::suite // tfAllOrNothing { + auto const batchFee = feeDrops * 3; auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 1, seq); - - // Tx 2 - Json::Value const tx2 = - jtx::trust(alice, alice["USD"](1000), tfSetfAuth); - jv = addBatchTx(jv, tx2, alice, 2, seq); - - // Tx 3 - Json::Value const tx3 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx3, alice, 3, seq); - - // Internally tefNO_AUTH_REQUIRED - env(jv, - fee(feeDrops * 3), - txflags(tfAllOrNothing), + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), + batch::add( + jtx::trust(alice, alice["USD"](1000), tfSetfAuth), + alice, + 2, + seq), + batch::add(pay(alice, bob, XRP(1)), alice, 3, seq), ter(tecBATCH_FAILURE)); env.close(); } // tfUntilFailure { + auto const batchFee = feeDrops * 3; auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 1, seq); - - // Tx 2 - Json::Value const tx2 = - jtx::trust(alice, alice["USD"](1000), tfSetfAuth); - jv = addBatchTx(jv, tx2, alice, 2, seq); - - // Tx 3 - Json::Value const tx3 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx3, alice, 3, seq); - - // Internally tefNO_AUTH_REQUIRED - env(jv, - fee(feeDrops * 3), - txflags(tfUntilFailure), + env(batch::batch(alice, seq, batchFee, tfUntilFailure), + batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), + batch::add( + jtx::trust(alice, alice["USD"](1000), tfSetfAuth), + alice, + 2, + seq), + batch::add(pay(alice, bob, XRP(1)), alice, 3, seq), ter(tecBATCH_FAILURE)); env.close(); } // tfOnlyOne { + auto const batchFee = feeDrops * 2; auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value const tx1 = - jtx::trust(alice, alice["USD"](1000), tfSetfAuth); - jv = addBatchTx(jv, tx1, alice, 1, seq); - - // Tx 2 - Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq); - - // Internally tefNO_AUTH_REQUIRED - env(jv, - fee(feeDrops * 2), - txflags(tfOnlyOne), + env(batch::batch(alice, seq, batchFee, tfOnlyOne), + batch::add( + jtx::trust(alice, alice["USD"](1000), tfSetfAuth), + alice, + 1, + seq), + batch::add(pay(alice, bob, XRP(1)), alice, 2, seq), ter(tecBATCH_FAILURE)); env.close(); } // tfIndependent { + auto const batchFee = feeDrops * 2; auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value const tx1 = - jtx::trust(alice, alice["USD"](1000), tfSetfAuth); - jv = addBatchTx(jv, tx1, alice, 1, seq); - - // Tx 2 - Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq); - - // Internally tefNO_AUTH_REQUIRED - env(jv, - fee(feeDrops * 3), - txflags(tfIndependent), + env(batch::batch(alice, seq, batchFee, tfIndependent), + batch::add( + jtx::trust(alice, alice["USD"](1000), tfSetfAuth), + alice, + 1, + seq), + batch::add(pay(alice, bob, XRP(1)), alice, 2, seq), ter(tecBATCH_FAILURE)); env.close(); } @@ -767,26 +600,11 @@ class Batch_test : public beast::unit_test::suite auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); + auto const batchFee = feeDrops * 2; auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 1, seq); - - // Tx 2 - Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq); - - env(jv, - fee(feeDrops * 2), - txflags(tfAllOrNothing), + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 2, seq), ter(tesSUCCESS)); env.close(); @@ -831,26 +649,11 @@ class Batch_test : public beast::unit_test::suite auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); + auto const batchFee = feeDrops * 2; auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 1, seq); - - // Tx 2 - Json::Value const tx2 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx2, alice, 2, seq); - - env(jv, - fee(feeDrops * 2), - txflags(tfAllOrNothing), + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), + batch::add(pay(alice, bob, XRP(999)), alice, 2, seq), ter(tecBATCH_FAILURE)); env.close(); @@ -901,28 +704,13 @@ class Batch_test : public beast::unit_test::suite auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); + auto const batchFee = feeDrops * 3; auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 2 - Json::Value const tx1 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx1, alice, 1, seq); - - // Tx 2 - Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq); - - // Tx 3 - Json::Value const tx3 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx3, alice, 3, seq); - - env(jv, fee(feeDrops * 3), txflags(tfOnlyOne), ter(tesSUCCESS)); + env(batch::batch(alice, seq, batchFee, tfOnlyOne), + batch::add(pay(alice, bob, XRP(999)), alice, 1, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 2, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 3, seq), + ter(tesSUCCESS)); env.close(); std::vector testCases = {{ @@ -971,32 +759,14 @@ class Batch_test : public beast::unit_test::suite auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); + auto const batchFee = feeDrops * 4; auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 1, seq); - - // Tx 2 - Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq); - - // Tx 3 - Json::Value const tx3 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx3, alice, 3, seq); - - // Tx 4 - Json::Value const tx4 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx4, alice, 4, seq); - - env(jv, fee(feeDrops * 4), txflags(tfUntilFailure), ter(tesSUCCESS)); + env(batch::batch(alice, seq, batchFee, tfUntilFailure), + batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 2, seq), + batch::add(pay(alice, bob, XRP(999)), alice, 3, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 4, seq), + ter(tesSUCCESS)); env.close(); std::vector testCases = {{ @@ -1049,32 +819,14 @@ class Batch_test : public beast::unit_test::suite auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); + auto const batchFee = feeDrops * 4; auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 1, seq); - - // Tx 2 - Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq); - - // Tx 3 - Json::Value const tx3 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx3, alice, 3, seq); - - // Tx 4 - Json::Value const tx4 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx4, alice, 4, seq); - - env(jv, fee(feeDrops * 4), txflags(tfIndependent), ter(tesSUCCESS)); + env(batch::batch(alice, seq, batchFee, tfIndependent), + batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 2, seq), + batch::add(pay(alice, bob, XRP(999)), alice, 3, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 4, seq), + ter(tesSUCCESS)); env.close(); std::vector testCases = {{ @@ -1131,38 +883,15 @@ class Batch_test : public beast::unit_test::suite env(noop(bob), ter(tesSUCCESS)); env.close(); - auto const seq = env.seq(alice); auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); - std::vector const signers = {{ - {0, bob}, - }}; - - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - auto const batchFee = ((signers.size() + 2) * feeDrops) + feeDrops * 2; - jv[jss::Fee] = to_string(batchFee); - jv[jss::Flags] = tfAllOrNothing; - jv[jss::SigningPubKey] = strHex(alice.pk()); - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value tx1 = pay(alice, bob, XRP(10)); - jv = addBatchTx(jv, tx1, alice, 1, seq); - - // Tx 2 - Json::Value const tx2 = pay(bob, alice, XRP(5)); - jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob)); - - jv = addBatchSignatures(jv, signers); - - // env(jv, bsig(alice, bob), ter(tesSUCCESS)); - env(jv, ter(tesSUCCESS)); + auto const seq = env.seq(alice); + auto const batchFee = ((1 + 2) * feeDrops) + feeDrops * 2; + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add(pay(alice, bob, XRP(10)), alice, 1, seq), + batch::add(pay(bob, alice, XRP(5)), alice, 0, env.seq(bob)), + batch::sig(bob)); env.close(); std::vector testCases = {{ @@ -1216,27 +945,11 @@ class Batch_test : public beast::unit_test::suite auto const preBob = env.balance(bob); auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 1, seq); - - // Tx 2 - Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq); - - env(jv, - fee(feeDrops * 2 + (feeDrops * 4)), - txflags(tfAllOrNothing), - msig(bob, carol), - ter(tesSUCCESS)); + auto const batchFee = feeDrops * 2 + (feeDrops * 4); + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 2, seq), + msig(bob, carol)); env.close(); std::vector testCases = {{ @@ -1292,35 +1005,12 @@ class Batch_test : public beast::unit_test::suite auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); - std::vector const signers = {{ - {0, dave}, - {0, carol}, - }}; - - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = env.seq(alice); - auto const batchFee = ((signers.size() + 2) * feeDrops) + feeDrops * 2; - jv[jss::Fee] = to_string(batchFee); - jv[jss::Flags] = tfAllOrNothing; - jv[jss::SigningPubKey] = strHex(alice.pk()); - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value tx1 = pay(alice, bob, XRP(10)); - jv = addBatchTx(jv, tx1, alice, 1, env.seq(alice)); - - // Tx 2 - Json::Value const tx2 = pay(bob, alice, XRP(5)); - jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob)); - - jv = addBatchMultiSignatures(jv, 0, bob, signers); - - // env(jv, bsig(alice, bob), ter(tesSUCCESS)); - env(jv, ter(tesSUCCESS)); + auto const seq = env.seq(alice); + auto const batchFee = ((2 + 2) * feeDrops) + feeDrops * 2; + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add(pay(alice, bob, XRP(10)), alice, 1, seq), + batch::add(pay(bob, alice, XRP(5)), alice, 0, env.seq(bob)), + batch::msig(bob, {dave, carol})); env.close(); std::vector testCases = {{ @@ -1431,26 +1121,16 @@ class Batch_test : public beast::unit_test::suite auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); - auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - // Tx 1 Json::Value tx1 = noop(alice); std::string const domain = "example.com"; tx1[sfDomain.fieldName] = strHex(domain); - jv = addBatchTx(jv, tx1, alice, 1, seq); - - // Tx 2 - Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq); - env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); + auto const seq = env.seq(alice); + auto const batchFee = feeDrops * 2; + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add(tx1, alice, 1, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 2, seq)); env.close(); std::vector testCases = {{ @@ -1519,35 +1199,14 @@ class Batch_test : public beast::unit_test::suite auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBobUSD = env.balance(bob, USD.issue()); - std::vector const signers = {{ - {0, bob}, - }}; - auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - auto const batchFee = ((signers.size() + 2) * feeDrops) + feeDrops * 2; - jv[jss::Fee] = to_string(batchFee); - jv[jss::Flags] = tfAllOrNothing; - jv[jss::SigningPubKey] = strHex(alice.pk()); - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 + auto const batchFee = ((1 + 2) * feeDrops) + feeDrops * 2; uint256 const chkId{getCheckIndex(bob, env.seq(bob))}; - Json::Value tx1 = check::create(bob, alice, USD(10)); - jv = addBatchTx(jv, tx1, alice, 0, env.seq(bob)); - - // Tx 2 - Json::Value const tx2 = check::cash(alice, chkId, USD(10)); - jv = addBatchTx(jv, tx2, alice, 1, env.seq(alice)); - - jv = addBatchSignatures(jv, signers); - - env(jv, ter(tesSUCCESS)); + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add( + check::create(bob, alice, USD(10)), alice, 0, env.seq(bob)), + batch::add(check::cash(alice, chkId, USD(10)), alice, 1, seq), + batch::sig(bob)); env.close(); std::vector testCases = {{ @@ -1612,35 +1271,14 @@ class Batch_test : public beast::unit_test::suite auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBobUSD = env.balance(bob, USD.issue()); - std::vector const signers = {{ - {0, bob}, - }}; - auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - auto const batchFee = ((signers.size() + 2) * feeDrops) + feeDrops * 2; - jv[jss::Fee] = to_string(batchFee); - jv[jss::Flags] = tfAllOrNothing; - jv[jss::SigningPubKey] = strHex(alice.pk()); - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 + auto const batchFee = ((1 + 2) * feeDrops) + feeDrops * 2; uint256 const chkId{getCheckIndex(bob, bobTicketSeq)}; - Json::Value tx1 = check::create(bob, alice, USD(10)); - jv = addBatchTx(jv, tx1, alice, 0, 0, bobTicketSeq); - - // Tx 2 - Json::Value const tx2 = check::cash(alice, chkId, USD(10)); - jv = addBatchTx(jv, tx2, alice, 1, env.seq(alice)); - - jv = addBatchSignatures(jv, signers); - - env(jv, ter(tesSUCCESS)); + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add( + check::create(bob, alice, USD(10)), alice, 0, 0, bobTicketSeq), + batch::add(check::cash(alice, chkId, USD(10)), alice, 1, seq), + batch::sig(bob)); env.close(); std::vector testCases = {{ @@ -1681,9 +1319,6 @@ class Batch_test : public beast::unit_test::suite using namespace std::literals; test::jtx::Env env{*this, envconfig()}; - // Env env{*this, envconfig(), features, nullptr, - // beast::severities::kTrace - // }; auto const feeDrops = env.current()->fees().base; auto const alice = Account("alice"); @@ -1706,36 +1341,15 @@ class Batch_test : public beast::unit_test::suite auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBobUSD = env.balance(bob, USD.issue()); - std::vector const signers = {{ - {0, bob}, - {1, alice}, - }}; - auto const seq = env.seq(carol); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = carol.human(); - jv[jss::Sequence] = seq; - auto const batchFee = ((signers.size() + 2) * feeDrops) + feeDrops * 2; - jv[jss::Fee] = to_string(batchFee); - jv[jss::Flags] = tfAllOrNothing; - jv[jss::SigningPubKey] = strHex(carol.pk()); - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 + auto const batchFee = ((2 + 2) * feeDrops) + feeDrops * 2; uint256 const chkId{getCheckIndex(bob, env.seq(bob))}; - Json::Value tx1 = check::create(bob, alice, USD(10)); - jv = addBatchTx(jv, tx1, carol, 0, env.seq(bob)); - - // Tx 2 - Json::Value const tx2 = check::cash(alice, chkId, USD(10)); - jv = addBatchTx(jv, tx2, carol, 0, env.seq(alice)); - - jv = addBatchSignatures(jv, signers); - - env(jv, ter(tesSUCCESS)); + env(batch::batch(carol, seq, batchFee, tfAllOrNothing), + batch::add( + check::create(bob, alice, USD(10)), carol, 0, env.seq(bob)), + batch::add( + check::cash(alice, chkId, USD(10)), carol, 0, env.seq(alice)), + batch::sig(alice, bob)); env.close(); std::vector testCases = {{ @@ -1794,26 +1408,11 @@ class Batch_test : public beast::unit_test::suite auto const preBob = env.balance(bob); auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 0, seq); - - // Tx 2 - Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 1, seq); - - env(jv, - fee(feeDrops * 2), - txflags(tfAllOrNothing), - ticket::use(aliceTicketSeq++), - ter(tesSUCCESS)); + auto const batchFee = feeDrops * 2; + env(batch::batch(alice, 0, batchFee, tfAllOrNothing), + batch::add(pay(alice, bob, XRP(1)), alice, 0, seq), + batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), + ticket::use(aliceTicketSeq++)); env.close(); std::vector testCases = {{ @@ -1872,23 +1471,10 @@ class Batch_test : public beast::unit_test::suite auto const preBob = env.balance(bob); auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 0, 0, aliceTicketSeq); - - // Tx 2 - Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 1, 0, aliceTicketSeq); - - env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); + auto const batchFee = feeDrops * 2; + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add(pay(alice, bob, XRP(1)), alice, 0, 0, aliceTicketSeq), + batch::add(pay(alice, bob, XRP(1)), alice, 1, 0, aliceTicketSeq)); env.close(); std::vector testCases = {{ @@ -1947,26 +1533,11 @@ class Batch_test : public beast::unit_test::suite auto const preBob = env.balance(bob); auto const seq = env.seq(alice); - Json::Value jv; - jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = alice.human(); - - // Batch Transactions - jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - - // Tx 1 - Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 1, 0, aliceTicketSeq); - - // Tx 2 - Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 0, seq); - - env(jv, - fee(feeDrops * 2), - txflags(tfAllOrNothing), - ticket::use(aliceTicketSeq++), - ter(tesSUCCESS)); + auto const batchFee = feeDrops * 2; + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + batch::add(pay(alice, bob, XRP(1)), alice, 1, 0, aliceTicketSeq), + batch::add(pay(alice, bob, XRP(1)), alice, 0, seq), + ticket::use(aliceTicketSeq++)); env.close(); std::vector testCases = {{ diff --git a/src/test/jtx.h b/src/test/jtx.h index 6de7cd480fa..7518b6f8570 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/jtx/batch.h b/src/test/jtx/batch.h new file mode 100644 index 00000000000..f09f66826a9 --- /dev/null +++ b/src/test/jtx/batch.h @@ -0,0 +1,192 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2018 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_TEST_JTX_BATCH_H_INCLUDED +#define RIPPLE_TEST_JTX_BATCH_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { +namespace jtx { + +/** Batch operations */ +namespace batch { + +/** Batch. */ +Json::Value +batch( + jtx::Account const& account, + uint32_t seq, + STAmount const& fee, + std::uint32_t flags); + +// /** Adds a new Batch Txn on a JTx. */ +class add +{ +private: + Json::Value txn_; + Account acct_; + std::uint8_t bi_; + std::uint32_t seq_; + std::optional ticket_; + +public: + add(Json::Value const& txn, + Account const& outerAccount, + std::uint8_t const& batchIndex, + std::uint32_t const& sequence, + std::optional const& ticket = std::nullopt) + : txn_(txn) + , acct_(outerAccount) + , bi_(batchIndex) + , seq_(sequence) + , ticket_(ticket) + { + } + + void + operator()(Env&, JTx& jtx) const; +}; + +/** Set a batch signature on a JTx. */ +class sig +{ +public: + struct Reg + { + Account acct; + Account sig; + + Reg(Account const& masterSig) : acct(masterSig), sig(masterSig) + { + } + + Reg(Account const& acct_, Account const& regularSig) + : acct(acct_), sig(regularSig) + { + } + + Reg(char const* masterSig) : acct(masterSig), sig(masterSig) + { + } + + Reg(char const* acct_, char const* regularSig) + : acct(acct_), sig(regularSig) + { + } + + bool + operator<(Reg const& rhs) const + { + return acct < rhs.acct; + } + }; + + std::vector signers; + +public: + sig(std::vector signers_); + + template + requires std::convertible_to + explicit sig(AccountType&& a0, Accounts&&... aN) + : sig{std::vector{ + std::forward(a0), + std::forward(aN)...}} + { + } + + void + operator()(Env&, JTx& jt) const; +}; + +/** Set a batch multi signature on a JTx. */ +class msig +{ +public: + struct Reg + { + Account acct; + Account sig; + + Reg(Account const& masterSig) : acct(masterSig), sig(masterSig) + { + } + + Reg(Account const& acct_, Account const& regularSig) + : acct(acct_), sig(regularSig) + { + } + + Reg(char const* masterSig) : acct(masterSig), sig(masterSig) + { + } + + Reg(char const* acct_, char const* regularSig) + : acct(acct_), sig(regularSig) + { + } + + bool + operator<(Reg const& rhs) const + { + return acct < rhs.acct; + } + }; + + Account master; // Add a member to hold the master account + std::vector signers; + +public: + msig(Account const& masterAccount, std::vector signers_); + + template + requires std::convertible_to + explicit msig( + Account const& masterAccount, + AccountType&& a0, + Accounts&&... aN) + : master(masterAccount) + , // Initialize master account + signers{std::vector{ + std::forward(a0), + std::forward(aN)...}} + { + } + + void + operator()(Env&, JTx& jt) const; +}; + +} // namespace batch + +} // namespace jtx + +} // namespace test +} // namespace ripple + +#endif diff --git a/src/test/jtx/impl/batch.cpp b/src/test/jtx/impl/batch.cpp new file mode 100644 index 00000000000..37a324b26a3 --- /dev/null +++ b/src/test/jtx/impl/batch.cpp @@ -0,0 +1,190 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2018 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { +namespace jtx { + +namespace batch { + +// Batch. +Json::Value +batch( + jtx::Account const& account, + uint32_t seq, + STAmount const& fee, + std::uint32_t flags) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::Batch; + jv[jss::Account] = account.human(); + jv[jss::RawTransactions] = Json::Value{Json::arrayValue}; + jv[sfTxIDs.jsonName] = Json::Value{Json::arrayValue}; + jv[jss::Sequence] = seq; + jv[jss::Flags] = flags; + jv[jss::Fee] = to_string(fee); + jv[jss::SigningPubKey] = strHex(account.pk()); + return jv; +} + +void +add::operator()(Env& env, JTx& jt) const +{ + auto const index = jt.jv[jss::RawTransactions].size(); + Json::Value& batchTransaction = jt.jv[jss::RawTransactions][index]; + + // Initialize the batch transaction + batchTransaction = Json::Value{}; + batchTransaction[jss::RawTransaction] = txn_; + batchTransaction[jss::RawTransaction][jss::SigningPubKey] = ""; + batchTransaction[jss::RawTransaction][sfFee.jsonName] = 0; + batchTransaction[jss::RawTransaction][jss::Sequence] = 0; + + // Set batch transaction details + auto& batchTxn = batchTransaction[jss::RawTransaction][sfBatchTxn.jsonName]; + batchTxn = Json::Value{}; + batchTxn[sfOuterAccount.jsonName] = acct_.human(); + batchTxn[sfBatchIndex.jsonName] = bi_; + batchTxn[sfSequence.jsonName] = seq_; + + // Optionally set ticket sequence + if (ticket_.has_value()) + { + batchTxn[sfSequence.jsonName] = 0; + batchTxn[sfTicketSequence.jsonName] = *ticket_; + } + + // Set the hash of the transaction + try + { + std::optional st = + parse(jt.jv[jss::RawTransactions][index][jss::RawTransaction]); + STTx const stx = STTx{std::move(*st)}; + jt.jv[sfTxIDs.jsonName][index] = to_string(stx.getTransactionID()); + } + catch (parse_error const&) + { + env.test.log << pretty(jt.jv) << std::endl; + Rethrow(); + } +} + +sig::sig(std::vector signers_) : signers(std::move(signers_)) +{ + // Signatures must be applied in sorted order. + std::sort( + signers.begin(), + signers.end(), + [](sig::Reg const& lhs, sig::Reg const& rhs) { + return lhs.acct.id() < rhs.acct.id(); + }); +} + +void +sig::operator()(Env& env, JTx& jt) const +{ + auto const mySigners = signers; + std::optional st; + try + { + st = parse(jt.jv); + } + catch (parse_error const&) + { + env.test.log << pretty(jt.jv) << std::endl; + Rethrow(); + } + auto& js = jt[sfBatchSigners.getJsonName()]; + for (std::size_t i = 0; i < mySigners.size(); ++i) + { + auto const& e = mySigners[i]; + auto& jo = js[i][sfBatchSigner.getJsonName()]; + jo[jss::Account] = e.acct.human(); + jo[jss::SigningPubKey] = strHex(e.sig.pk().slice()); + + Serializer msg; + serializeBatch(msg, st->getFlags(), st->getFieldV256(sfTxIDs)); + auto const sig = ripple::sign( + *publicKeyType(e.sig.pk().slice()), e.sig.sk(), msg.slice()); + jo[sfTxnSignature.getJsonName()] = + strHex(Slice{sig.data(), sig.size()}); + } +} + +msig::msig(Account const& masterAccount, std::vector signers_) + : master(masterAccount), signers(std::move(signers_)) +{ + std::sort( + signers.begin(), + signers.end(), + [](msig::Reg const& lhs, msig::Reg const& rhs) { + return lhs.acct.id() < rhs.acct.id(); + }); +} + +void +msig::operator()(Env& env, JTx& jt) const +{ + auto const mySigners = signers; + std::optional st; + try + { + st = parse(jt.jv); + } + catch (parse_error const&) + { + env.test.log << pretty(jt.jv) << std::endl; + Rethrow(); + } + auto& bs = jt[sfBatchSigners.getJsonName()]; + auto const index = jt[sfBatchSigners.jsonName].size(); + auto& bso = bs[index][sfBatchSigner.getJsonName()]; + bso[jss::Account] = master.human(); + bso[jss::SigningPubKey] = ""; + auto& is = bso[sfSigners.getJsonName()]; + for (std::size_t i = 0; i < mySigners.size(); ++i) + { + auto const& e = mySigners[i]; + auto& iso = is[i][sfSigner.getJsonName()]; + iso[jss::Account] = e.acct.human(); + iso[jss::SigningPubKey] = strHex(e.sig.pk().slice()); + + Serializer msg; + serializeBatch(msg, st->getFlags(), st->getFieldV256(sfTxIDs)); + auto const sig = ripple::sign( + *publicKeyType(e.sig.pk().slice()), e.sig.sk(), msg.slice()); + iso[sfTxnSignature.getJsonName()] = + strHex(Slice{sig.data(), sig.size()}); + } +} + +} // namespace batch + +} // namespace jtx +} // namespace test +} // namespace ripple diff --git a/src/test/jtx/impl/multisign.cpp b/src/test/jtx/impl/multisign.cpp index 227db597d21..42c3bfc78bf 100644 --- a/src/test/jtx/impl/multisign.cpp +++ b/src/test/jtx/impl/multisign.cpp @@ -110,50 +110,6 @@ msig::operator()(Env& env, JTx& jt) const }; } -bsig::bsig(std::vector signers_) : signers(std::move(signers_)) -{ - // Signatures must be applied in sorted order. - std::sort( - signers.begin(), - signers.end(), - [](bsig::Reg const& lhs, bsig::Reg const& rhs) { - return lhs.acct.id() < rhs.acct.id(); - }); -} - -void -bsig::operator()(Env& env, JTx& jt) const -{ - auto const mySigners = signers; - jt.signer = [mySigners, &env](Env&, JTx& jtx) { - // jtx[sfSigningPubKey.getJsonName()] = ""; - std::optional st; - try - { - st = parse(jtx.jv); - } - catch (parse_error const&) - { - env.test.log << pretty(jtx.jv) << std::endl; - Rethrow(); - } - auto& js = jtx[sfBatchSigners.getJsonName()]; - for (std::size_t i = 0; i < mySigners.size(); ++i) - { - auto const& e = mySigners[i]; - auto& jo = js[i][sfBatchSigner.getJsonName()]; - jo[jss::Account] = e.acct.human(); - jo[jss::SigningPubKey] = strHex(e.sig.pk().slice()); - - Serializer ss{buildMultiSigningData(*st, e.acct.id())}; - auto const sig = ripple::sign( - *publicKeyType(e.sig.pk().slice()), e.sig.sk(), ss.slice()); - jo[sfTxnSignature.getJsonName()] = - strHex(Slice{sig.data(), sig.size()}); - } - }; -} - } // namespace jtx } // namespace test } // namespace ripple diff --git a/src/test/jtx/multisign.h b/src/test/jtx/multisign.h index ccf359782a1..3946ea14b26 100644 --- a/src/test/jtx/multisign.h +++ b/src/test/jtx/multisign.h @@ -113,59 +113,6 @@ class msig operator()(Env&, JTx& jt) const; }; -/** Set a multisignature on a JTx. */ -class bsig -{ -public: - struct Reg - { - Account acct; - Account sig; - - Reg(Account const& masterSig) : acct(masterSig), sig(masterSig) - { - } - - Reg(Account const& acct_, Account const& regularSig) - : acct(acct_), sig(regularSig) - { - } - - Reg(char const* masterSig) : acct(masterSig), sig(masterSig) - { - } - - Reg(char const* acct_, char const* regularSig) - : acct(acct_), sig(regularSig) - { - } - - bool - operator<(Reg const& rhs) const - { - return acct < rhs.acct; - } - }; - - std::vector signers; - -public: - bsig(std::vector signers_); - - template - requires std::convertible_to explicit bsig( - AccountType&& a0, - Accounts&&... aN) - : bsig{std::vector{ - std::forward(a0), - std::forward(aN)...}} - { - } - - void - operator()(Env&, JTx& jt) const; -}; - //------------------------------------------------------------------------------ /** The number of signer lists matches. */ diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 668daebee6d..42ceef06797 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -55,55 +55,54 @@ Batch::preflight(PreflightContext const& ctx) AccountID const outerAccount = tx.getAccountID(sfAccount); auto const& txns = tx.getFieldArray(sfRawTransactions); + STVector256 const hashes = tx.getFieldV256(sfTxIDs); + if (hashes.size() != txns.size()) + { + JLOG(ctx.j.warn()) << "Batch: Hashes array size does not match txns."; + return temINVALID; + } + if (txns.empty()) { JLOG(ctx.j.warn()) << "Batch: txns array empty."; - return temMALFORMED; + return temARRAY_EMPTY; } if (txns.size() > 8) { JLOG(ctx.j.warn()) << "Batch: txns array exceeds 12 entries."; - return temMALFORMED; + return temARRAY_TOO_LARGE; } - if (tx.isFieldPresent(sfBatchSigners)) + for (int i = 0; i < txns.size(); ++i) { - auto const requireCanonicalSig = - ctx.rules.enabled(featureRequireFullyCanonicalSig) - ? STTx::RequireFullyCanonicalSig::yes - : STTx::RequireFullyCanonicalSig::no; - auto const sigResult = - ctx.tx.checkBatchSign(requireCanonicalSig, ctx.rules); - if (!sigResult) + STTx const stx = STTx{STObject(txns[i])}; + if (stx.getTransactionID() != hashes[i]) { - JLOG(ctx.j.warn()) << "Batch: invalid batch txn signature."; - return temBAD_SIGNATURE; + JLOG(ctx.j.warn()) << "Batch: Hashes array does not match txns."; + return temINVALID; } - } - for (STObject txn : txns) - { - auto const innerAccount = txn.getAccountID(sfAccount); - if (!txn.isFieldPresent(sfTransactionType)) + auto const innerAccount = stx.getAccountID(sfAccount); + if (!stx.isFieldPresent(sfTransactionType)) { JLOG(ctx.j.warn()) << "Batch: TransactionType missing in array entry."; - return temMALFORMED; + return temINVALID_BATCH; } - if (txn.getFieldU16(sfTransactionType) == ttBATCH) + if (stx.getFieldU16(sfTransactionType) == ttBATCH) { JLOG(ctx.j.warn()) << "Batch: batch cannot have inner batch txn."; - return temMALFORMED; + return temINVALID_BATCH; } - if (txn.getFieldU16(sfTransactionType) == ttACCOUNT_DELETE && + if (stx.getFieldU16(sfTransactionType) == ttACCOUNT_DELETE && innerAccount == outerAccount) { JLOG(ctx.j.warn()) << "Batch: inner txn cannot be account delete when inner and " "outer accounts are the same."; - return temMALFORMED; + return temINVALID_BATCH; } if (innerAccount != outerAccount) @@ -122,6 +121,22 @@ Batch::preflight(PreflightContext const& ctx) } } } + + if (tx.isFieldPresent(sfBatchSigners)) + { + auto const requireCanonicalSig = + ctx.rules.enabled(featureRequireFullyCanonicalSig) + ? STTx::RequireFullyCanonicalSig::yes + : STTx::RequireFullyCanonicalSig::no; + auto const sigResult = + ctx.tx.checkBatchSign(requireCanonicalSig, ctx.rules); + if (!sigResult) + { + JLOG(ctx.j.warn()) << "Batch: invalid batch txn signature."; + return temBAD_SIGNATURE; + } + } + return preflight2(ctx); }