From 879aba5ee3f08fe6b5e804e39e1dad6da7337840 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 10 Jul 2024 17:44:52 +0200 Subject: [PATCH 01/71] featureBatch rough draft --- include/xrpl/protocol/Feature.h | 3 +- include/xrpl/protocol/SField.h | 7 + include/xrpl/protocol/TER.h | 3 +- include/xrpl/protocol/TxFlags.h | 8 + include/xrpl/protocol/TxFormats.h | 3 + include/xrpl/protocol/TxMeta.h | 18 + include/xrpl/protocol/jss.h | 2 + src/libxrpl/protocol/Feature.cpp | 1 + src/libxrpl/protocol/InnerObjectFormats.cpp | 13 + src/libxrpl/protocol/SField.cpp | 7 + src/libxrpl/protocol/STTx.cpp | 11 + src/libxrpl/protocol/TER.cpp | 1 + src/libxrpl/protocol/TxFormats.cpp | 8 + src/libxrpl/protocol/TxMeta.cpp | 8 + src/test/app/Batch_test.cpp | 520 ++++++++++++++++++ src/xrpld/app/ledger/detail/BuildLedger.cpp | 8 +- src/xrpld/app/ledger/detail/OpenLedger.cpp | 5 + src/xrpld/app/misc/NetworkOPs.cpp | 18 +- src/xrpld/app/tx/apply.h | 3 +- src/xrpld/app/tx/detail/ApplyContext.h | 3 +- src/xrpld/app/tx/detail/Batch.cpp | 556 ++++++++++++++++++++ src/xrpld/app/tx/detail/Batch.h | 57 ++ src/xrpld/app/tx/detail/InvariantCheck.cpp | 9 +- src/xrpld/app/tx/detail/Transactor.cpp | 63 ++- src/xrpld/app/tx/detail/apply.cpp | 18 +- src/xrpld/app/tx/detail/applySteps.cpp | 3 + src/xrpld/ledger/ApplyView.h | 3 + src/xrpld/ledger/ApplyViewImpl.h | 22 + src/xrpld/ledger/detail/ApplyStateTable.cpp | 10 + src/xrpld/ledger/detail/ApplyStateTable.h | 1 + src/xrpld/ledger/detail/ApplyViewImpl.cpp | 2 +- src/xrpld/overlay/detail/PeerImp.cpp | 18 + 32 files changed, 1397 insertions(+), 15 deletions(-) create mode 100644 src/test/app/Batch_test.cpp create mode 100644 src/xrpld/app/tx/detail/Batch.cpp create mode 100644 src/xrpld/app/tx/detail/Batch.h diff --git a/include/xrpl/protocol/Feature.h b/include/xrpl/protocol/Feature.h index 7eec46e89eb..938fc3408d1 100644 --- a/include/xrpl/protocol/Feature.h +++ b/include/xrpl/protocol/Feature.h @@ -80,7 +80,7 @@ namespace detail { // Feature.cpp. Because it's only used to reserve storage, and determine how // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // the actual number of amendments. A LogicError on startup will verify this. -static constexpr std::size_t numFeatures = 78; +static constexpr std::size_t numFeatures = 79; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -371,6 +371,7 @@ extern uint256 const fixReducedOffersV2; extern uint256 const fixEnforceNFTokenTrustline; extern uint256 const fixInnerObjTemplate2; extern uint256 const featureInvariantsV1_1; +extern uint256 const featureBatch; } // namespace ripple diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index 15aa2272d75..2e5482b0ef6 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -373,6 +373,7 @@ extern SF_UINT8 const sfScale; extern SF_UINT8 const sfTickSize; extern SF_UINT8 const sfUNLModifyDisabling; extern SF_UINT8 const sfHookResult; +extern SF_UINT8 const sfBatchIndex; // 16-bit integers (common) extern SF_UINT16 const sfLedgerEntryType; @@ -441,6 +442,7 @@ extern SF_UINT32 const sfEmitGeneration; extern SF_UINT32 const sfVoteWeight; extern SF_UINT32 const sfFirstNFTokenSequence; extern SF_UINT32 const sfOracleDocumentID; +extern SF_UINT32 const sfOuterSequence; // 64-bit integers (common) extern SF_UINT64 const sfIndexNext; @@ -650,6 +652,9 @@ extern SField const sfXChainClaimProofSig; extern SField const sfXChainCreateAccountProofSig; extern SField const sfXChainClaimAttestationCollectionElement; extern SField const sfXChainCreateAccountAttestationCollectionElement; +extern SField const sfRawTransaction; +extern SField const sfBatchExecution; +extern SField const sfBatchTxn; // array of objects (common) // ARRAY/1 is reserved for end of array @@ -675,6 +680,8 @@ extern SField const sfHookParameters; extern SField const sfHookGrants; extern SField const sfXChainClaimAttestations; extern SField const sfXChainCreateAccountAttestations; +extern SField const sfBatchExecutions; +extern SField const sfRawTransactions; //------------------------------------------------------------------------------ diff --git a/include/xrpl/protocol/TER.h b/include/xrpl/protocol/TER.h index 335ef8de39a..b96b09267f4 100644 --- a/include/xrpl/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -338,7 +338,8 @@ enum TECcodes : TERUnderlyingType { tecINVALID_UPDATE_TIME = 188, tecTOKEN_PAIR_NOT_FOUND = 189, tecARRAY_EMPTY = 190, - tecARRAY_TOO_LARGE = 191 + tecARRAY_TOO_LARGE = 191, + tecBATCH_FAILURE = 192 }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index ba2b97562db..3db4406bf13 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -185,6 +185,14 @@ constexpr std::uint32_t tfDepositMask = ~(tfUniversal | tfDepositSubTx); constexpr std::uint32_t tfClearAccountCreateAmount = 0x00010000; constexpr std::uint32_t tfBridgeModifyMask = ~(tfUniversal | tfClearAccountCreateAmount); +// Batch Flags +constexpr std::uint32_t tfAllOrNothing = 0x00000001; +constexpr std::uint32_t tfOnlyOne = 0x00000002; +constexpr std::uint32_t tfUntilFailure = 0x00000004; +constexpr std::uint32_t tfIndependent = 0x00000008; +constexpr std::uint32_t const tfBatchMask = + ~(tfUniversal | tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent); + // clang-format on } // namespace ripple diff --git a/include/xrpl/protocol/TxFormats.h b/include/xrpl/protocol/TxFormats.h index bd5dffd94e9..98f36892961 100644 --- a/include/xrpl/protocol/TxFormats.h +++ b/include/xrpl/protocol/TxFormats.h @@ -197,6 +197,9 @@ enum TxType : std::uint16_t /** This transaction type deletes an Oracle instance */ ttORACLE_DELETE = 52, + /** This transaction type creates a Batch instance */ + ttBATCH = 53, + /** This system-generated transaction type is used to update the status of the various amendments. For details, see: https://xrpl.org/amendments.html diff --git a/include/xrpl/protocol/TxMeta.h b/include/xrpl/protocol/TxMeta.h index 7932a4c55a3..98d60d85d96 100644 --- a/include/xrpl/protocol/TxMeta.h +++ b/include/xrpl/protocol/TxMeta.h @@ -126,6 +126,23 @@ class TxMeta return static_cast(mDelivered); } + STArray const& + getBatchExecutions() const + { + return *mBatchExecutions; + } + + void + setBatchExecutions(STArray const& batchExecutions) + { + mBatchExecutions = batchExecutions; + } + bool + hasBatchExecutions() const + { + return static_cast(mBatchExecutions); + } + private: uint256 mTransactionID; std::uint32_t mLedger; @@ -133,6 +150,7 @@ class TxMeta int mResult; std::optional mDelivered; + std::optional mBatchExecutions; STArray mNodes; }; diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index a46e15f39ef..5aab8f08044 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -65,6 +65,7 @@ JSS(AssetPrice); // in: Oracle JSS(AuthAccount); // in: AMM Auction Slot JSS(AuthAccounts); // in: AMM Auction Slot JSS(BaseAsset); // in: Oracle +JSS(Batch); // transaction type JSS(Bridge); // ledger type. JSS(Check); // ledger type. JSS(CheckCancel); // transaction type. @@ -128,6 +129,7 @@ JSS(PriceData); // field. JSS(Provider); // field. JSS(QuoteAsset); // in: Oracle. JSS(RippleState); // ledger type. +JSS(RawTransaction); // in: Batch JSS(SLE_hit_rate); // out: GetCounts. JSS(SetFee); // transaction type. JSS(UNLModify); // transaction type. diff --git a/src/libxrpl/protocol/Feature.cpp b/src/libxrpl/protocol/Feature.cpp index 87395b7e189..098a741dd1b 100644 --- a/src/libxrpl/protocol/Feature.cpp +++ b/src/libxrpl/protocol/Feature.cpp @@ -500,6 +500,7 @@ REGISTER_FIX (fixInnerObjTemplate2, Supported::yes, VoteBehavior::De // InvariantsV1_1 will be changes to Supported::yes when all the // invariants expected to be included under it are complete. REGISTER_FEATURE(InvariantsV1_1, Supported::no, VoteBehavior::DefaultNo); +REGISTER_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo); // The following amendments are obsolete, but must remain supported // because they could potentially get enabled. diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 6d7b855d199..5748189ad5c 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -147,6 +147,19 @@ InnerObjectFormats::InnerObjectFormats() {sfAssetPrice, soeOPTIONAL}, {sfScale, soeDEFAULT}, }); + + add(sfBatchExecution.jsonName.c_str(), + sfBatchExecution.getCode(), + {{sfTransactionType, soeREQUIRED}, + {sfTransactionResult, soeREQUIRED}, + {sfTransactionHash, soeOPTIONAL}}); + + add(sfBatchTxn.jsonName.c_str(), + sfBatchTxn.getCode(), + {{sfAccount, soeREQUIRED}, + {sfOuterSequence, soeREQUIRED}, + {sfSequence, soeOPTIONAL}, + {sfBatchIndex, soeREQUIRED}}); } InnerObjectFormats const& diff --git a/src/libxrpl/protocol/SField.cpp b/src/libxrpl/protocol/SField.cpp index d56f3983352..8148b15670e 100644 --- a/src/libxrpl/protocol/SField.cpp +++ b/src/libxrpl/protocol/SField.cpp @@ -98,6 +98,7 @@ CONSTRUCT_TYPED_SFIELD(sfTickSize, "TickSize", UINT8, CONSTRUCT_TYPED_SFIELD(sfUNLModifyDisabling, "UNLModifyDisabling", UINT8, 17); CONSTRUCT_TYPED_SFIELD(sfHookResult, "HookResult", UINT8, 18); CONSTRUCT_TYPED_SFIELD(sfWasLockingChainSend, "WasLockingChainSend", UINT8, 19); +CONSTRUCT_TYPED_SFIELD(sfBatchIndex, "BatchIndex", UINT8, 20); // 16-bit integers CONSTRUCT_TYPED_SFIELD(sfLedgerEntryType, "LedgerEntryType", UINT16, 1, SField::sMD_Never); @@ -167,6 +168,7 @@ CONSTRUCT_TYPED_SFIELD(sfEmitGeneration, "EmitGeneration", UINT32, CONSTRUCT_TYPED_SFIELD(sfVoteWeight, "VoteWeight", UINT32, 48); CONSTRUCT_TYPED_SFIELD(sfFirstNFTokenSequence, "FirstNFTokenSequence", UINT32, 50); CONSTRUCT_TYPED_SFIELD(sfOracleDocumentID, "OracleDocumentID", UINT32, 51); +CONSTRUCT_TYPED_SFIELD(sfOuterSequence, "OuterSequence", UINT32, 52); // 64-bit integers (common) CONSTRUCT_TYPED_SFIELD(sfIndexNext, "IndexNext", UINT64, 1); @@ -390,6 +392,9 @@ CONSTRUCT_UNTYPED_SFIELD(sfXChainCreateAccountAttestationCollectionElement, "XChainCreateAccountAttestationCollectionElement", OBJECT, 31); CONSTRUCT_UNTYPED_SFIELD(sfPriceData, "PriceData", OBJECT, 32); +CONSTRUCT_UNTYPED_SFIELD(sfRawTransaction, "RawTransaction", OBJECT, 33); +CONSTRUCT_UNTYPED_SFIELD(sfBatchExecution, "BatchExecution", OBJECT, 34); +CONSTRUCT_UNTYPED_SFIELD(sfBatchTxn, "BatchTxn", OBJECT, 35); // array of objects // ARRAY/1 is reserved for end of array @@ -420,6 +425,8 @@ CONSTRUCT_UNTYPED_SFIELD(sfXChainCreateAccountAttestations, // 23 is unused and available for use CONSTRUCT_UNTYPED_SFIELD(sfPriceDataSeries, "PriceDataSeries", ARRAY, 24); CONSTRUCT_UNTYPED_SFIELD(sfAuthAccounts, "AuthAccounts", ARRAY, 25); +CONSTRUCT_UNTYPED_SFIELD(sfBatchExecutions, "BatchExecutions", ARRAY, 26); +CONSTRUCT_UNTYPED_SFIELD(sfRawTransactions, "RawTransactions", ARRAY, 27); // clang-format on diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 149186d43ce..54f14741915 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -187,6 +187,17 @@ STTx::getSeqProxy() const if (seq != 0) return SeqProxy::sequence(seq); + if (isFieldPresent(sfBatchTxn)) + { + STObject const batchTxn = const_cast(*this) + .getField(sfBatchTxn) + .downcast(); + std::uint32_t const startSequence{ + batchTxn.getFieldU32(sfOuterSequence)}; + std::uint32_t const batchIndex{batchTxn.getFieldU8(sfBatchIndex)}; + return SeqProxy::sequence(startSequence + batchIndex + 1); + } + std::optional const ticketSeq{operator[](~sfTicketSequence)}; if (!ticketSeq) // No TicketSequence specified. Return the Sequence, whatever it is. diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index f452b05464e..a6f82ec7e8b 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -115,6 +115,7 @@ transResults() MAKE_ERROR(tecTOKEN_PAIR_NOT_FOUND, "Token pair is not found in Oracle object."), MAKE_ERROR(tecARRAY_EMPTY, "Array is empty."), MAKE_ERROR(tecARRAY_TOO_LARGE, "Array is too large."), + MAKE_ERROR(tecBATCH_FAILURE, "Tx Batch Failure."), MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."), MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."), diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index 71c333dc497..d35cfc31508 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -45,6 +45,7 @@ TxFormats::TxFormats() {sfTxnSignature, soeOPTIONAL}, {sfSigners, soeOPTIONAL}, // submit_multisigned {sfNetworkID, soeOPTIONAL}, + {sfBatchTxn, soeOPTIONAL}, }; add(jss::AccountSet, @@ -505,6 +506,13 @@ TxFormats::TxFormats() {sfOracleDocumentID, soeREQUIRED}, }, commonFields); + + add(jss::Batch, + ttBATCH, + { + {sfRawTransactions, soeOPTIONAL}, + }, + commonFields); } TxFormats const& diff --git a/src/libxrpl/protocol/TxMeta.cpp b/src/libxrpl/protocol/TxMeta.cpp index 253d00e8414..ecec9e38b4d 100644 --- a/src/libxrpl/protocol/TxMeta.cpp +++ b/src/libxrpl/protocol/TxMeta.cpp @@ -43,6 +43,9 @@ TxMeta::TxMeta( if (obj.isFieldPresent(sfDeliveredAmount)) setDeliveredAmount(obj.getFieldAmount(sfDeliveredAmount)); + + if (obj.isFieldPresent(sfBatchExecutions)) + setBatchExecutions(obj.getFieldArray(sfBatchExecutions)); } TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj) @@ -61,6 +64,9 @@ TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj) if (obj.isFieldPresent(sfDeliveredAmount)) setDeliveredAmount(obj.getFieldAmount(sfDeliveredAmount)); + + if (obj.isFieldPresent(sfBatchExecutions)) + setBatchExecutions(obj.getFieldArray(sfBatchExecutions)); } TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, Blob const& vec) @@ -205,6 +211,8 @@ TxMeta::getAsObject() const metaData.emplace_back(mNodes); if (hasDeliveredAmount()) metaData.setFieldAmount(sfDeliveredAmount, getDeliveredAmount()); + if (hasBatchExecutions()) + metaData.setFieldArray(sfBatchExecutions, getBatchExecutions()); return metaData; } diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp new file mode 100644 index 00000000000..f8d7a485d7f --- /dev/null +++ b/src/test/app/Batch_test.cpp @@ -0,0 +1,520 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include + +namespace ripple { +namespace test { + +class Batch_test : public beast::unit_test::suite +{ + struct TestBatchData + { + std::string result; + std::string txType; + std::string hash; + }; + + void + validateBatchTxns( + Json::Value meta, + std::vector const& batchResults) + { + size_t index = 0; + for (auto const& _batchTxn : meta[sfBatchExecutions.jsonName]) + { + auto const b = _batchTxn[sfBatchExecution.jsonName]; + BEAST_EXPECT( + b[sfTransactionResult.jsonName] == batchResults[index].result); + BEAST_EXPECT( + b[sfTransactionType.jsonName] == batchResults[index].txType); + if (batchResults[index].hash != "") + BEAST_EXPECT( + b[sfTransactionHash.jsonName] == batchResults[index].hash); + ++index; + } + } + + Json::Value + addBatchTx( + Json::Value jv, + Json::Value const& tx, + jtx::Account const& account, + XRPAmount feeDrops, + std::uint8_t index, + std::uint32_t outerSequence) + { + jv[sfRawTransactions.jsonName][index] = Json::Value{}; + jv[sfRawTransactions.jsonName][index][jss::RawTransaction] = tx; + jv[sfRawTransactions.jsonName][index][jss::RawTransaction] + [jss::SigningPubKey] = strHex(account.pk()); + jv[sfRawTransactions.jsonName][index][jss::RawTransaction] + [sfFee.jsonName] = 0; + jv[sfRawTransactions.jsonName][index][jss::RawTransaction] + [jss::Sequence] = 0; + jv[sfRawTransactions.jsonName][index][jss::RawTransaction] + [sfBatchTxn.jsonName] = Json::Value{}; + jv[sfRawTransactions.jsonName][index][jss::RawTransaction] + [sfBatchTxn.jsonName][jss::Account] = account.human(); + jv[sfRawTransactions.jsonName][index][jss::RawTransaction] + [sfBatchTxn.jsonName][sfOuterSequence.jsonName] = outerSequence; + jv[sfRawTransactions.jsonName][index][jss::RawTransaction] + [sfBatchTxn.jsonName][sfBatchIndex.jsonName] = index; + return jv; + } + + void + testTemplate(FeatureBitset features) + { + testcase("template"); + + using namespace test::jtx; + using namespace std::literals; + + // test::jtx::Env env{*this, envconfig()}; + Env env{ + *this, + envconfig(), + features, + nullptr, + // beast::severities::kWarning + beast::severities::kTrace}; + + auto const feeDrops = env.current()->fees().base; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + auto const seq = env.seq("alice"); + std::cout << "seq: " << seq << "\n"; + 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, feeDrops, 0, seq); + + // Tx 2 + Json::Value const tx2 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx2, alice, feeDrops, 1, seq); + + env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", "Payment", ""}, + {"tesSUCCESS", "Payment", ""}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + std::cout << "jrr: " << jrr << "\n"; + auto const meta = jrr[jss::result][jss::meta]; + validateBatchTxns(meta, testCases); + + std::cout << "seq: " << env.seq(alice) << "\n"; + std::cout << "alice: " << env.balance(alice) << "\n"; + std::cout << "bob: " << env.balance(bob) << "\n"; + + BEAST_EXPECT(env.seq(alice) == 7); + BEAST_EXPECT(env.balance(alice) == XRP(1000) - XRP(2) - (feeDrops * 2)); + BEAST_EXPECT(env.balance(bob) == XRP(1000) + XRP(2)); + } + + void + testAllOrNothing(FeatureBitset features) + { + testcase("all or nothing"); + + using namespace test::jtx; + using namespace std::literals; + + // all + { + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + 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 const tx1 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx1, alice, feeDrops, 0, seq); + + // Tx 2 + Json::Value const tx2 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx2, alice, feeDrops, 1, seq); + + env(jv, + fee(feeDrops * 2), + txflags(tfAllOrNothing), + ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "Payment", + "FE01269C9BABCE17758CEF4DA45BDB529DDA0105FD2360BE00316345637E1" + "88D"}, + {"tesSUCCESS", + "Payment", + "591CF8801EA7B0465DBF309D2B6D103D5E5926203A10F5A433A704C29C1D3" + "26F"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + auto const meta = jrr[jss::result][jss::meta]; + validateBatchTxns(meta, testCases); + + BEAST_EXPECT(env.seq(alice) == 7); + BEAST_EXPECT( + env.balance(alice) == preAlice - XRP(2) - (feeDrops * 2)); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(2)); + } + + // nothing + { + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + 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 const tx1 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx1, alice, feeDrops, 0, seq); + + // Tx 2 + Json::Value const tx2 = pay(alice, bob, XRP(999)); + jv = addBatchTx(jv, tx2, alice, feeDrops, 1, seq); + + env(jv, + fee(feeDrops * 2), + txflags(tfAllOrNothing), + ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", "Payment", ""}, + {"tecUNFUNDED_PAYMENT", "Payment", ""}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + auto const meta = jrr[jss::result][jss::meta]; + validateBatchTxns(meta, testCases); + + BEAST_EXPECT(env.seq(alice) == 7); + BEAST_EXPECT(env.balance(alice) == preAlice - (feeDrops * 2)); + BEAST_EXPECT(env.balance(bob) == preBob); + } + } + + void + testOnlyOne(FeatureBitset features) + { + testcase("only one"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + 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 2 + Json::Value const tx1 = pay(alice, bob, XRP(999)); + jv = addBatchTx(jv, tx1, alice, feeDrops, 0, seq); + + // Tx 2 + Json::Value const tx2 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx2, alice, feeDrops, 1, seq); + + // Tx 3 + Json::Value const tx3 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx3, alice, feeDrops, 2, seq); + + env(jv, fee(feeDrops * 3), txflags(tfOnlyOne), ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tecUNFUNDED_PAYMENT", "Payment", ""}, + {"tesSUCCESS", + "Payment", + "591CF8801EA7B0465DBF309D2B6D103D5E5926203A10F5A433A704C29C1D326" + "F"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + auto const meta = jrr[jss::result][jss::meta]; + validateBatchTxns(meta, testCases); + + BEAST_EXPECT(env.seq(alice) == 8); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - (feeDrops * 3)); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(1)); + } + + void + testUntilFailure(FeatureBitset features) + { + testcase("until failure"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + 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 const tx1 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx1, alice, feeDrops, 0, seq); + + // Tx 2 + Json::Value const tx2 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx2, alice, feeDrops, 1, seq); + + // Tx 3 + Json::Value const tx3 = pay(alice, bob, XRP(999)); + jv = addBatchTx(jv, tx3, alice, feeDrops, 2, seq); + + // Tx 4 + Json::Value const tx4 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx4, alice, feeDrops, 3, seq); + + env(jv, fee(feeDrops * 4), txflags(tfUntilFailure), ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "Payment", + "FE01269C9BABCE17758CEF4DA45BDB529DDA0105FD2360BE00316345637E188" + "D"}, + {"tesSUCCESS", + "Payment", + "591CF8801EA7B0465DBF309D2B6D103D5E5926203A10F5A433A704C29C1D326" + "F"}, + {"tecUNFUNDED_PAYMENT", "Payment", ""}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + auto const meta = jrr[jss::result][jss::meta]; + validateBatchTxns(meta, testCases); + + BEAST_EXPECT(env.seq(alice) == 9); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - (feeDrops * 4)); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(2)); + } + + void + testIndependent(FeatureBitset features) + { + testcase("independent"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + 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 const tx1 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx1, alice, feeDrops, 0, seq); + + // Tx 2 + Json::Value const tx2 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx2, alice, feeDrops, 1, seq); + + // Tx 3 + Json::Value const tx3 = pay(alice, bob, XRP(999)); + jv = addBatchTx(jv, tx3, alice, feeDrops, 2, seq); + + // Tx 4 + Json::Value const tx4 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx4, alice, feeDrops, 3, seq); + + env(jv, fee(feeDrops * 4), txflags(tfIndependent), ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "Payment", + "FE01269C9BABCE17758CEF4DA45BDB529DDA0105FD2360BE00316345637E188" + "D"}, + {"tesSUCCESS", + "Payment", + "591CF8801EA7B0465DBF309D2B6D103D5E5926203A10F5A433A704C29C1D326" + "F"}, + {"tecUNFUNDED_PAYMENT", "Payment", ""}, + {"tesSUCCESS", + "Payment", + "963BCD15F8CC7D6FB3D3154324CDF6CFBEF6A230496676D58DB92109E4A9F1C" + "8"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + auto const meta = jrr[jss::result][jss::meta]; + validateBatchTxns(meta, testCases); + + BEAST_EXPECT(env.seq(alice) == 9); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - (feeDrops * 4)); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(3)); + } + + void + testWithFeats(FeatureBitset features) + { + // testTemplate(features); + testAllOrNothing(features); + testOnlyOne(features); + testUntilFailure(features); + testIndependent(features); + + // Test Fork From one node having 1 extra txn + } + +public: + void + run() override + { + using namespace test::jtx; + auto const sa = supported_amendments(); + testWithFeats(sa); + } +}; + +BEAST_DEFINE_TESTSUITE(Batch, app, ripple); + +} // namespace test +} // namespace ripple \ No newline at end of file diff --git a/src/xrpld/app/ledger/detail/BuildLedger.cpp b/src/xrpld/app/ledger/detail/BuildLedger.cpp index 8c4a7a3f41d..55070889120 100644 --- a/src/xrpld/app/ledger/detail/BuildLedger.cpp +++ b/src/xrpld/app/ledger/detail/BuildLedger.cpp @@ -117,9 +117,15 @@ applyTransactions( while (it != txns.end()) { auto const txid = it->first.getTXID(); - + auto const isBatch = it->second->isFieldPresent(sfBatchTxn); try { + if (isBatch && view.txExists(txid)) + { + it = txns.erase(it); + continue; + } + if (pass == 0 && built->txExists(txid)) { it = txns.erase(it); diff --git a/src/xrpld/app/ledger/detail/OpenLedger.cpp b/src/xrpld/app/ledger/detail/OpenLedger.cpp index 461d98ae4ac..030b055cfc3 100644 --- a/src/xrpld/app/ledger/detail/OpenLedger.cpp +++ b/src/xrpld/app/ledger/detail/OpenLedger.cpp @@ -121,6 +121,11 @@ OpenLedger::accept( { auto const& tx = txpair.first; auto const txId = tx->getTransactionID(); + + // skip emitted txns + if (tx->isFieldPresent(sfBatchTxn)) + continue; + if (auto const toSkip = app.getHashRouter().shouldRelay(txId)) { JLOG(j_.debug()) << "Relaying recovered tx " << txId; diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 9cf5d097099..5ec98554c96 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -1210,9 +1210,20 @@ NetworkOPsImp::processTransaction( // but I'm not 100% sure yet. // If so, only cost is looking up HashRouter flags. auto const view = m_ledgerMaster.getCurrentLedger(); + + // This function is called by several different parts of the codebase + // under no circumstances will we ever accept a batch txn from the + // network. + auto const tx = *transaction->getSTransaction(); + if (view->rules().enabled(featureBatch) && + tx.isFieldPresent(ripple::sfBatchTxn)) + { + return; + } + auto const [validity, reason] = checkValidity( app_.getHashRouter(), - *transaction->getSTransaction(), + tx, view->rules(), app_.config()); assert(validity == Validity::Valid); @@ -2755,6 +2766,11 @@ NetworkOPsImp::pubProposedTransaction( std::shared_ptr const& transaction, TER result) { + + // never publish batch txns + if (transaction->isFieldPresent(ripple::sfBatchTxn)) + return; + MultiApiJson jvObj = transJson(transaction, result, false, ledger, std::nullopt); diff --git a/src/xrpld/app/tx/apply.h b/src/xrpld/app/tx/apply.h index 6a0401e2b55..eaf79e21278 100644 --- a/src/xrpld/app/tx/apply.h +++ b/src/xrpld/app/tx/apply.h @@ -64,7 +64,8 @@ checkValidity( HashRouter& router, STTx const& tx, Rules const& rules, - Config const& config); + Config const& config, + ApplyFlags const& applyFlags = tapNONE); /** Sets the validity of a given transaction in the cache. diff --git a/src/xrpld/app/tx/detail/ApplyContext.h b/src/xrpld/app/tx/detail/ApplyContext.h index 45de05a73db..bca0a40bcae 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.h +++ b/src/xrpld/app/tx/detail/ApplyContext.h @@ -49,6 +49,7 @@ class ApplyContext TER const preclaimResult; XRPAmount const baseFee; beast::Journal const journal; + OpenView& base_; ApplyView& view() @@ -121,7 +122,7 @@ class ApplyContext XRPAmount const fee, std::index_sequence); - OpenView& base_; + // OpenView& base_; ApplyFlags flags_; std::optional view_; }; diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp new file mode 100644 index 00000000000..9085d6812a1 --- /dev/null +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -0,0 +1,556 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +namespace { + +struct UnknownTxnType : std::exception +{ + TxType txnType; + UnknownTxnType(TxType t) : txnType{t} + { + } +}; + +// Call a lambda with the concrete transaction type as a template parameter +// throw an "UnknownTxnType" exception on error +template +auto +with_txn_type(TxType txnType, F&& f) +{ + switch (txnType) + { + case ttACCOUNT_DELETE: + return f.template operator()(); + case ttACCOUNT_SET: + return f.template operator()(); + case ttCHECK_CANCEL: + return f.template operator()(); + case ttCHECK_CASH: + return f.template operator()(); + case ttCHECK_CREATE: + return f.template operator()(); + case ttDEPOSIT_PREAUTH: + return f.template operator()(); + case ttOFFER_CANCEL: + return f.template operator()(); + case ttOFFER_CREATE: + return f.template operator()(); + case ttESCROW_CREATE: + return f.template operator()(); + case ttESCROW_FINISH: + return f.template operator()(); + case ttESCROW_CANCEL: + return f.template operator()(); + case ttPAYCHAN_CLAIM: + return f.template operator()(); + case ttPAYCHAN_CREATE: + return f.template operator()(); + case ttPAYCHAN_FUND: + return f.template operator()(); + case ttPAYMENT: + return f.template operator()(); + case ttREGULAR_KEY_SET: + return f.template operator()(); + case ttSIGNER_LIST_SET: + return f.template operator()(); + case ttTICKET_CREATE: + return f.template operator()(); + case ttTRUST_SET: + return f.template operator()(); + case ttAMENDMENT: + case ttFEE: + case ttUNL_MODIFY: + return f.template operator()(); + case ttNFTOKEN_MINT: + return f.template operator()(); + case ttNFTOKEN_BURN: + return f.template operator()(); + case ttNFTOKEN_CREATE_OFFER: + return f.template operator()(); + case ttNFTOKEN_CANCEL_OFFER: + return f.template operator()(); + case ttNFTOKEN_ACCEPT_OFFER: + return f.template operator()(); + case ttCLAWBACK: + return f.template operator()(); + case ttAMM_CREATE: + return f.template operator()(); + case ttAMM_DEPOSIT: + return f.template operator()(); + case ttAMM_WITHDRAW: + return f.template operator()(); + case ttAMM_VOTE: + return f.template operator()(); + case ttAMM_BID: + return f.template operator()(); + case ttAMM_DELETE: + return f.template operator()(); + case ttXCHAIN_CREATE_BRIDGE: + return f.template operator()(); + case ttXCHAIN_MODIFY_BRIDGE: + return f.template operator()(); + case ttXCHAIN_CREATE_CLAIM_ID: + return f.template operator()(); + case ttXCHAIN_COMMIT: + return f.template operator()(); + case ttXCHAIN_CLAIM: + return f.template operator()(); + case ttXCHAIN_ADD_CLAIM_ATTESTATION: + return f.template operator()(); + case ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION: + return f.template operator()(); + case ttXCHAIN_ACCOUNT_CREATE_COMMIT: + return f.template operator()(); + case ttDID_SET: + return f.template operator()(); + case ttDID_DELETE: + return f.template operator()(); + case ttBATCH: + return f.template operator()(); + default: + throw UnknownTxnType(txnType); + } +} +} // namespace + +// clang-format off +// Current formatter for rippled is based on clang-10, which does not handle `requires` clauses +template +requires(T::ConsequencesFactory == Transactor::Normal) +TxConsequences + consequences_helper(PreflightContext const& ctx) +{ + return TxConsequences(ctx.tx); +}; + +// For Transactor::Blocker +template +requires(T::ConsequencesFactory == Transactor::Blocker) +TxConsequences + consequences_helper(PreflightContext const& ctx) +{ + return TxConsequences(ctx.tx, TxConsequences::blocker); +}; + +// For Transactor::Custom +template +requires(T::ConsequencesFactory == Transactor::Custom) +TxConsequences + consequences_helper(PreflightContext const& ctx) +{ + return T::makeTxConsequences(ctx); +}; +// clang-format on + +static std::pair +invoke_preflight(PreflightContext const& ctx) +{ + try + { + return with_txn_type(ctx.tx.getTxnType(), [&]() { + auto const tec = T::preflight(ctx); + return std::make_pair( + tec, + isTesSuccess(tec) ? consequences_helper(ctx) + : TxConsequences{tec}); + }); + } + catch (UnknownTxnType const& e) + { + // Should never happen + JLOG(ctx.j.fatal()) + << "Unknown transaction type in preflight: " << e.txnType; + assert(false); + return {temUNKNOWN, TxConsequences{temUNKNOWN}}; + } +} + +static TER +invoke_preclaim(PreclaimContext const& ctx) +{ + try + { + // use name hiding to accomplish compile-time polymorphism of static + // class functions for Transactor and derived classes. + return with_txn_type(ctx.tx.getTxnType(), [&]() { + // If the transactor requires a valid account and the transaction + // doesn't list one, preflight will have already a flagged a + // failure. + auto const id = ctx.tx.getAccountID(sfAccount); + + if (id != beast::zero) + { + // TER result = T::checkSeqProxy(ctx.view, ctx.tx, ctx.j); + + // if (result != tesSUCCESS) + // return result; + + // Ignore Sequence Validation on ttBATCH txns + TER result = tesSUCCESS; + + JLOG(ctx.j.trace()) << "invoke_preclaim.Batch: " << "\n"; + result = T::checkPriorTxAndLastLedger(ctx); + + if (result != tesSUCCESS) + return result; + + // result = T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx)); + + // if (result != tesSUCCESS) + // return result; + + result = T::checkSign(ctx); + + if (result != tesSUCCESS) + return result; + } + + return T::preclaim(ctx); + }); + } + catch (UnknownTxnType const& e) + { + // Should never happen + JLOG(ctx.j.fatal()) + << "Unknown transaction type in preclaim: " << e.txnType; + assert(false); + return temUNKNOWN; + } +} + +static std::pair +invoke_apply(ApplyContext& ctx) +{ + try + { + return with_txn_type(ctx.tx.getTxnType(), [&]() { + T p(ctx); + return p(); + }); + } + catch (UnknownTxnType const& e) + { + // Should never happen + JLOG(ctx.journal.fatal()) + << "Unknown transaction type in apply: " << e.txnType; + assert(false); + return {temUNKNOWN, false}; + } +} + +TxConsequences +Batch::makeTxConsequences(PreflightContext const& ctx) +{ + return TxConsequences{ctx.tx, TxConsequences::normal}; +} + +std::vector preflightResponses; + +NotTEC +Batch::preflight(PreflightContext const& ctx) +{ + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + auto& tx = ctx.tx; + + auto const& txns = tx.getFieldArray(sfRawTransactions); + if (txns.empty()) + { + JLOG(ctx.j.error()) << "Batch: txns array empty."; + return temMALFORMED; + } + + if (txns.size() > 8) + { + JLOG(ctx.j.error()) << "Batch: txns array exceeds 12 entries."; + return temMALFORMED; + } + + for (auto const& txn : txns) + { + if (!txn.isFieldPresent(sfTransactionType)) + { + JLOG(ctx.j.error()) + << "Batch: TransactionType missing in array entry."; + return temMALFORMED; + } + + auto const tt = txn.getFieldU16(sfTransactionType); + auto const txtype = safe_cast(tt); + auto const account = txn.getAccountID(sfAccount); + auto const stx = + STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); + PreflightContext const pfctx( + ctx.app, + stx, + ctx.rules, + tapPREFLIGHT_BATCH, + ctx.j); + auto const response = invoke_preflight(pfctx); + preflightResponses.push_back(response.first); + } + + return preflight2(ctx); +} + +std::vector preclaimResponses; + +TER +Batch::preclaim(PreclaimContext const& ctx) +{ + if (!ctx.view.rules().enabled(featureBatch)) + return temDISABLED; + + auto const& txns = ctx.tx.getFieldArray(sfRawTransactions); + for (std::size_t i = 0; i < txns.size(); ++i) + { + // Cannot continue on failed txns + if (preflightResponses[i] != tesSUCCESS) + { + JLOG(ctx.j.error()) << "Batch: Failed Preflight Response: " << preflightResponses[i]; + preclaimResponses.push_back(TER(preflightResponses[i])); + continue; + } + + auto const& txn = txns[i]; + if (!txn.isFieldPresent(sfTransactionType)) + { + JLOG(ctx.j.error()) + << "Batch: TransactionType missing in array entry."; + return temMALFORMED; + } + + auto const tt = txn.getFieldU16(sfTransactionType); + auto const txtype = safe_cast(tt); + auto const stx = + STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); + PreclaimContext const pcctx( + ctx.app, ctx.view, preflightResponses[i], stx, ctx.flags, ctx.j); + auto const response = invoke_preclaim(pcctx); + preclaimResponses.push_back(response); + } + + for (auto const& response : preclaimResponses) + { + if (response != tesSUCCESS) + { + return response; + } + } + + return tesSUCCESS; +} + +TER +Batch::doApply() +{ + Sandbox sb(&ctx_.view(), tapRETRY); + + uint32_t flags = ctx_.tx.getFlags(); + if (flags & tfBatchMask) + return temINVALID_FLAG; + + // SANITIZE + std::vector stxTxns; + auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions); + for (std::size_t i = 0; i < txns.size(); ++i) + { + auto const& txn = txns[i]; + if (!txn.isFieldPresent(sfTransactionType)) + { + JLOG(ctx_.journal.error()) + << "Batch: TransactionType missing in array entry."; + return temMALFORMED; + } + + auto const tt = txn.getFieldU16(sfTransactionType); + auto const txtype = safe_cast(tt); + auto const stx = STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); + stxTxns.push_back(stx); + } + + // DRY RUN + std::vector> dryVector; + for (std::size_t i = 0; i < stxTxns.size(); ++i) + { + auto const& stx = stxTxns[i]; + ApplyContext actx( + ctx_.app, + ctx_.base_, + stx, + preclaimResponses[i], + ctx_.view().fees().base, + tapPREFLIGHT_BATCH, + ctx_.journal); + auto const result = invoke_apply(actx); + dryVector.emplace_back(stx.getTxnType(), result.first); + actx.discard(); + } + + TER preResult = tesSUCCESS; + ApplyViewImpl& avi = dynamic_cast(ctx_.view()); + for (auto const& dryRun : dryVector) + { + STObject meta{sfBatchExecution}; + meta.setFieldU8(sfTransactionResult, TERtoInt(dryRun.second)); + meta.setFieldU16(sfTransactionType, dryRun.first); + avi.addBatchExecutionMetaData(std::move(meta)); + + // tfAllOrNothing + if (dryRun.second != tesSUCCESS && flags & tfAllOrNothing) + { + preResult = tecBATCH_FAILURE; + } + } + + // WET RUN + TER result = tesSUCCESS; + if (preResult == tesSUCCESS) + { + std::vector batch; + avi.setHookMetaData(std::move(batch)); + for (std::size_t i = 0; i < stxTxns.size(); ++i) + { + auto const& stx = stxTxns[i]; + ApplyContext actx( + ctx_.app, + ctx_.base_, + stx, + preclaimResponses[i], + ctx_.view().fees().base, + ctx_.base_.open() == 1 ? tapPREFLIGHT_BATCH : ctx_.view().flags(), + ctx_.journal); + auto const _result = invoke_apply(actx); + + STObject meta{sfBatchExecution}; + meta.setFieldU8(sfTransactionResult, TERtoInt(_result.first)); + meta.setFieldU16(sfTransactionType, stx.getTxnType()); + if (_result.first == tesSUCCESS) + meta.setFieldH256(sfTransactionHash, stx.getTransactionID()); + + avi.addBatchExecutionMetaData(std::move(meta)); + + if (_result.first != tesSUCCESS) + { + if (flags & tfUntilFailure) + { + actx.discard(); + result = tesSUCCESS; + break; + } + if (flags & tfOnlyOne) + { + actx.discard(); + continue; + } + } + + if (_result.first == tesSUCCESS && flags & tfOnlyOne) + { + result = tesSUCCESS; + break; + } + } + } + + auto const sleBase = ctx_.base_.read(keylet::account(account_)); + if (!sleBase) + return tefINTERNAL; + + auto const sleSrcAcc = sb.peek(keylet::account(account_)); + if (!sleSrcAcc) + return tefINTERNAL; + + // std::cout << "ACCOUNT BASE SEQ: " << sleBase->getFieldU32(sfSequence) << "\n"; + // std::cout << "ACCOUNT BASE BALANCE: " << sleBase->getFieldAmount(sfBalance) << "\n"; + // std::cout << "ACCOUNT SEQ: " << sleSrcAcc->getFieldU32(sfSequence) << "\n"; + // std::cout << "ACCOUNT BALANCE: " << sleSrcAcc->getFieldAmount(sfBalance) << "\n"; + + auto const feePaid = ctx_.tx[sfFee].xrp(); + // auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions); + sleSrcAcc->setFieldU32(sfSequence, ctx_.tx.getFieldU32(sfSequence) + txns.size() + 1); + sleSrcAcc->setFieldAmount(sfBalance, sleBase->getFieldAmount(sfBalance).xrp() - feePaid); + sb.update(sleSrcAcc); + sb.apply(ctx_.rawView()); + return result; +} + +XRPAmount +Batch::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + XRPAmount extraFee{0}; + if (tx.isFieldPresent(sfRawTransactions)) + { + XRPAmount txFees{0}; + auto const& txns = tx.getFieldArray(sfRawTransactions); + for (auto const& txn : txns) + { + auto const tt = txn.getFieldU16(sfTransactionType); + auto const txtype = safe_cast(tt); + auto const stx = STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); + txFees += Transactor::calculateBaseFee(view, tx); + } + extraFee += txFees; + } + return extraFee; +} + +} // namespace ripple \ No newline at end of file diff --git a/src/xrpld/app/tx/detail/Batch.h b/src/xrpld/app/tx/detail/Batch.h new file mode 100644 index 00000000000..3bd1d3e0182 --- /dev/null +++ b/src/xrpld/app/tx/detail/Batch.h @@ -0,0 +1,57 @@ +//------------------------------------------------------------------------------ +/* + 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. +*/ +//============================================================================== + +#ifndef RIPPLE_TX_BATCH_H_INCLUDED +#define RIPPLE_TX_BATCH_H_INCLUDED + +#include +#include +#include +#include + +namespace ripple { + +class Batch : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Custom}; + + explicit Batch(ApplyContext& ctx) : Transactor(ctx) + { + } + + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + + static TxConsequences + makeTxConsequences(PreflightContext const& ctx); + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +} // namespace ripple + +#endif \ No newline at end of file diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index 70210b90d75..219dd352289 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -138,13 +138,20 @@ XRPNotCreated::visitEntry( bool XRPNotCreated::finalize( STTx const& tx, - TER const, + TER const res, XRPAmount const fee, ReadView const&, beast::Journal const& j) { // The net change should never be positive, as this would mean that the // transaction created XRP out of thin air. That's not possible. + + // DA: TODO + auto const tt = tx.getTxnType(); + if (tt == ttBATCH && res == tesSUCCESS) + { + drops_ = -fee.drops(); + } if (drops_ > 0) { JLOG(j.fatal()) << "Invariant failed: XRP net change was positive: " diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 42e9f0677ab..30e756fc322 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -133,7 +133,11 @@ NotTEC preflight2(PreflightContext const& ctx) { auto const sigValid = checkValidity( - ctx.app.getHashRouter(), ctx.tx, ctx.rules, ctx.app.config()); + ctx.app.getHashRouter(), + ctx.tx, + ctx.rules, + ctx.app.config(), + ctx.flags); if (sigValid.first == Validity::SigBad) { JLOG(ctx.j.debug()) << "preflight2: bad signature. " << sigValid.second; @@ -200,7 +204,39 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) return temBAD_FEE; // Only check fee is sufficient when the ledger is open. - if (ctx.view.open()) + if (ctx.view.open() && ctx.tx.getTxnType() == ttBATCH) + { + XRPAmount feeDue = XRPAmount{0}; + auto const& txns = ctx.tx.getFieldArray(sfRawTransactions); + for (std::size_t i = 0; i < txns.size(); ++i) + { + auto const& txn = txns[i]; + if (!txn.isFieldPresent(sfFee)) + { + JLOG(ctx.j.warn()) + << "Batch: sfFee missing in array entry."; + return telINSUF_FEE_P; + } + auto const _fee = txn.getFieldAmount(sfFee); + feeDue += _fee.xrp(); + + // auto const tt = txn.getFieldU16(sfTransactionType); + // auto const txtype = safe_cast(tt); + // auto const stx = STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); + // auto const _fee = Transactor::calculateBaseFee(ctx.view, stx); + // feeDue += _fee; + } + + if (feePaid < feeDue) + { + JLOG(ctx.j.trace()) + << "Insufficient fee paid: " << to_string(feePaid) << "/" + << to_string(feeDue); + return telINSUF_FEE_P; + } + } + + if (ctx.view.open() && ctx.tx.getTxnType() != ttBATCH) { auto const feeDue = minimumFee(ctx.app, baseFee, ctx.view.fees(), ctx.flags); @@ -281,7 +317,17 @@ Transactor::checkSeqProxy( } SeqProxy const t_seqProx = tx.getSeqProxy(); - SeqProxy const a_seq = SeqProxy::sequence((*sle)[sfSequence]); + SeqProxy a_seq = SeqProxy::sequence((*sle)[sfSequence]); + + if (tx.isFieldPresent(sfBatchTxn)) + { + STObject const batchTxn = const_cast(tx) + .getField(sfBatchTxn) + .downcast(); + std::uint32_t const batchIndex{batchTxn.getFieldU8(sfBatchIndex)}; + a_seq = SeqProxy::sequence(a_seq.value() - (batchIndex + 1)); + a_seq = t_seqProx; + } if (t_seqProx.isSeq()) { @@ -358,7 +404,8 @@ Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx) (ctx.view.seq() > ctx.tx.getFieldU32(sfLastLedgerSequence))) return tefMAX_LEDGER; - if (ctx.view.txExists(ctx.tx.getTransactionID())) + if (ctx.view.txExists(ctx.tx.getTransactionID()) && + ctx.tx.getTxnType() != ttBATCH && !ctx.tx.isFieldPresent(sfBatchTxn)) return tefALREADY; return tesSUCCESS; @@ -371,6 +418,10 @@ Transactor::consumeSeqProxy(SLE::pointer const& sleAccount) SeqProxy const seqProx = ctx_.tx.getSeqProxy(); if (seqProx.isSeq()) { + // do not update sequence of sfAccountTxnID for batch tx + if (ctx_.tx.isFieldPresent(sfBatchTxn)) + return tesSUCCESS; + // Note that if this transaction is a TicketCreate, then // the transaction will modify the account root sfSequence // yet again. @@ -877,11 +928,11 @@ Transactor::operator()() if (ctx_.size() > oversizeMetaDataCap) result = tecOVERSIZE; - if (isTecClaim(result) && (view().flags() & tapFAIL_HARD)) + if ((isTecClaim(result) && (view().flags() & tapFAIL_HARD)) || + view().flags() & tapPREFLIGHT_BATCH) { // If the tapFAIL_HARD flag is set, a tec result // must not do anything - ctx_.discard(); applied = false; } diff --git a/src/xrpld/app/tx/detail/apply.cpp b/src/xrpld/app/tx/detail/apply.cpp index 103ec041074..3c1398264da 100644 --- a/src/xrpld/app/tx/detail/apply.cpp +++ b/src/xrpld/app/tx/detail/apply.cpp @@ -38,10 +38,26 @@ checkValidity( HashRouter& router, STTx const& tx, Rules const& rules, - Config const& config) + Config const& config, + ApplyFlags const& applyFlags) { auto const id = tx.getTransactionID(); auto const flags = router.getFlags(id); + + if (rules.enabled(featureBatch) && applyFlags & tapPREFLIGHT_BATCH) + { + // batched transactions do not contain signatures + if (tx.isFieldPresent(sfTxnSignature)) + return {Validity::SigBad, "Batch txn contains signature."}; + + std::string reason; + if (!passesLocalChecks(tx, reason)) + return {Validity::SigGoodOnly, reason}; + + router.setFlags(id, SF_SIGGOOD); + return {Validity::Valid, ""}; + } + if (flags & SF_SIGBAD) // Signature is known bad return {Validity::SigBad, "Transaction has bad signature."}; diff --git a/src/xrpld/app/tx/detail/applySteps.cpp b/src/xrpld/app/tx/detail/applySteps.cpp index 9ddaa3051c4..80a32f9ead3 100644 --- a/src/xrpld/app/tx/detail/applySteps.cpp +++ b/src/xrpld/app/tx/detail/applySteps.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -161,6 +162,8 @@ with_txn_type(TxType txnType, F&& f) return f.template operator()(); case ttDID_DELETE: return f.template operator()(); + case ttBATCH: + return f.template operator()(); case ttORACLE_SET: return f.template operator()(); case ttORACLE_DELETE: diff --git a/src/xrpld/ledger/ApplyView.h b/src/xrpld/ledger/ApplyView.h index f0166cd0b38..7cc0504476d 100644 --- a/src/xrpld/ledger/ApplyView.h +++ b/src/xrpld/ledger/ApplyView.h @@ -39,6 +39,9 @@ enum ApplyFlags : std::uint32_t { // Transaction came from a privileged source tapUNLIMITED = 0x400, + + // Transaction is being tested against preflight before emission + tapPREFLIGHT_BATCH = 0x800, }; constexpr ApplyFlags diff --git a/src/xrpld/ledger/ApplyViewImpl.h b/src/xrpld/ledger/ApplyViewImpl.h index 65678c5a6c4..ba98e29518b 100644 --- a/src/xrpld/ledger/ApplyViewImpl.h +++ b/src/xrpld/ledger/ApplyViewImpl.h @@ -68,6 +68,27 @@ class ApplyViewImpl final : public detail::ApplyViewBase deliver_ = amount; } + TxMeta + generateProvisionalMeta( + OpenView const& to, + STTx const& tx, + beast::Journal j); + + /* Set hook metadata for a hook execution + * Takes ownership / use std::move + */ + void + addBatchExecutionMetaData(STObject&& batchExecution) + { + batchExecution_.push_back(std::move(batchExecution)); + } + + void + setHookMetaData(std::vector&& batch) + { + batchExecution_ = std::move(batch); + } + /** Get the number of modified entries */ std::size_t @@ -86,6 +107,7 @@ class ApplyViewImpl final : public detail::ApplyViewBase private: std::optional deliver_; + std::vector batchExecution_; }; } // namespace ripple diff --git a/src/xrpld/ledger/detail/ApplyStateTable.cpp b/src/xrpld/ledger/detail/ApplyStateTable.cpp index b849365c83f..fabcc74ff79 100644 --- a/src/xrpld/ledger/detail/ApplyStateTable.cpp +++ b/src/xrpld/ledger/detail/ApplyStateTable.cpp @@ -115,6 +115,7 @@ ApplyStateTable::apply( STTx const& tx, TER ter, std::optional const& deliver, + std::vector const& batchExecution, beast::Journal j) { // Build metadata and insert @@ -126,6 +127,15 @@ ApplyStateTable::apply( TxMeta meta(tx.getTransactionID(), to.seq()); if (deliver) meta.setDeliveredAmount(*deliver); + + if (!batchExecution.empty()) + { + auto array = STArray{sfBatchExecutions}; + for (auto element : batchExecution) + array.push_back(element); + meta.setBatchExecutions(array); + } + Mods newMod; for (auto& item : items_) { diff --git a/src/xrpld/ledger/detail/ApplyStateTable.h b/src/xrpld/ledger/detail/ApplyStateTable.h index d1616d095e5..b42f590831c 100644 --- a/src/xrpld/ledger/detail/ApplyStateTable.h +++ b/src/xrpld/ledger/detail/ApplyStateTable.h @@ -70,6 +70,7 @@ class ApplyStateTable STTx const& tx, TER ter, std::optional const& deliver, + std::vector const& batchExecution, beast::Journal j); bool diff --git a/src/xrpld/ledger/detail/ApplyViewImpl.cpp b/src/xrpld/ledger/detail/ApplyViewImpl.cpp index ffbf681cd20..528e6715d14 100644 --- a/src/xrpld/ledger/detail/ApplyViewImpl.cpp +++ b/src/xrpld/ledger/detail/ApplyViewImpl.cpp @@ -31,7 +31,7 @@ ApplyViewImpl::ApplyViewImpl(ReadView const* base, ApplyFlags flags) void ApplyViewImpl::apply(OpenView& to, STTx const& tx, TER ter, beast::Journal j) { - items_.apply(to, tx, ter, deliver_, j); + items_.apply(to, tx, ter, deliver_, batchExecution_, j); } std::size_t diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 96f793b8d80..6054f40d80b 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -1540,6 +1540,15 @@ PeerImp::handleTransaction( auto stx = std::make_shared(sit); uint256 txID = stx->getTransactionID(); + // Charge strongly for attempting to relay a txn with sfBatchTxn + if (stx->isFieldPresent(sfBatchTxn)) + { + JLOG(p_journal_.warn()) << "Ignoring Network relayed Tx containing " + "sfBatchTxn (handleTransaction)."; + fee_ = Resource::feeHighBurdenPeer; + return; + } + int flags; constexpr std::chrono::seconds tx_interval = 10s; @@ -3054,6 +3063,15 @@ PeerImp::checkTransaction( // VFALCO TODO Rewrite to not use exceptions try { + // charge strongly for relaying Hook emitted txns + if (stx->isFieldPresent(sfBatchTxn)) + { + JLOG(p_journal_.warn()) << "Ignoring Network relayed Tx containing " + "sfBatchTxn (checkSignature)."; + charge(Resource::feeHighBurdenPeer); + return; + } + // Expired? if (stx->isFieldPresent(sfLastLedgerSequence) && (stx->getFieldU32(sfLastLedgerSequence) < From 46fd68f12c551f7d25dcc9c4f932cfde98f4ac33 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 10 Jul 2024 17:49:30 +0200 Subject: [PATCH 02/71] clang-format --- src/libxrpl/protocol/InnerObjectFormats.cpp | 2 +- src/libxrpl/protocol/TxMeta.cpp | 4 +-- src/xrpld/app/misc/NetworkOPs.cpp | 8 ++--- src/xrpld/app/tx/detail/Batch.cpp | 40 ++++++++++----------- src/xrpld/app/tx/detail/InvariantCheck.cpp | 2 +- src/xrpld/app/tx/detail/Transactor.cpp | 9 +---- 6 files changed, 26 insertions(+), 39 deletions(-) diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 5748189ad5c..407155d32b6 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -147,7 +147,7 @@ InnerObjectFormats::InnerObjectFormats() {sfAssetPrice, soeOPTIONAL}, {sfScale, soeDEFAULT}, }); - + add(sfBatchExecution.jsonName.c_str(), sfBatchExecution.getCode(), {{sfTransactionType, soeREQUIRED}, diff --git a/src/libxrpl/protocol/TxMeta.cpp b/src/libxrpl/protocol/TxMeta.cpp index ecec9e38b4d..f97b0746823 100644 --- a/src/libxrpl/protocol/TxMeta.cpp +++ b/src/libxrpl/protocol/TxMeta.cpp @@ -43,7 +43,7 @@ TxMeta::TxMeta( if (obj.isFieldPresent(sfDeliveredAmount)) setDeliveredAmount(obj.getFieldAmount(sfDeliveredAmount)); - + if (obj.isFieldPresent(sfBatchExecutions)) setBatchExecutions(obj.getFieldArray(sfBatchExecutions)); } @@ -64,7 +64,7 @@ TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj) if (obj.isFieldPresent(sfDeliveredAmount)) setDeliveredAmount(obj.getFieldAmount(sfDeliveredAmount)); - + if (obj.isFieldPresent(sfBatchExecutions)) setBatchExecutions(obj.getFieldArray(sfBatchExecutions)); } diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 5ec98554c96..cf99c20d683 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -1221,11 +1221,8 @@ NetworkOPsImp::processTransaction( return; } - auto const [validity, reason] = checkValidity( - app_.getHashRouter(), - tx, - view->rules(), - app_.config()); + auto const [validity, reason] = + checkValidity(app_.getHashRouter(), tx, view->rules(), app_.config()); assert(validity == Validity::Valid); // Not concerned with local checks at this point. @@ -2766,7 +2763,6 @@ NetworkOPsImp::pubProposedTransaction( std::shared_ptr const& transaction, TER result) { - // never publish batch txns if (transaction->isFieldPresent(ripple::sfBatchTxn)) return; diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 9085d6812a1..2af1cc99b0e 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -245,13 +245,15 @@ invoke_preclaim(PreclaimContext const& ctx) // Ignore Sequence Validation on ttBATCH txns TER result = tesSUCCESS; - JLOG(ctx.j.trace()) << "invoke_preclaim.Batch: " << "\n"; + JLOG(ctx.j.trace()) << "invoke_preclaim.Batch: " + << "\n"; result = T::checkPriorTxAndLastLedger(ctx); if (result != tesSUCCESS) return result; - // result = T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx)); + // result = T::checkFee(ctx, calculateBaseFee(ctx.view, + // ctx.tx)); // if (result != tesSUCCESS) // return result; @@ -339,11 +341,7 @@ Batch::preflight(PreflightContext const& ctx) auto const stx = STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); PreflightContext const pfctx( - ctx.app, - stx, - ctx.rules, - tapPREFLIGHT_BATCH, - ctx.j); + ctx.app, stx, ctx.rules, tapPREFLIGHT_BATCH, ctx.j); auto const response = invoke_preflight(pfctx); preflightResponses.push_back(response.first); } @@ -365,7 +363,8 @@ Batch::preclaim(PreclaimContext const& ctx) // Cannot continue on failed txns if (preflightResponses[i] != tesSUCCESS) { - JLOG(ctx.j.error()) << "Batch: Failed Preflight Response: " << preflightResponses[i]; + JLOG(ctx.j.error()) << "Batch: Failed Preflight Response: " + << preflightResponses[i]; preclaimResponses.push_back(TER(preflightResponses[i])); continue; } @@ -406,7 +405,7 @@ Batch::doApply() uint32_t flags = ctx_.tx.getFlags(); if (flags & tfBatchMask) - return temINVALID_FLAG; + return temINVALID_FLAG; // SANITIZE std::vector stxTxns; @@ -423,7 +422,8 @@ Batch::doApply() auto const tt = txn.getFieldU16(sfTransactionType); auto const txtype = safe_cast(tt); - auto const stx = STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); + auto const stx = + STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); stxTxns.push_back(stx); } @@ -460,7 +460,7 @@ Batch::doApply() preResult = tecBATCH_FAILURE; } } - + // WET RUN TER result = tesSUCCESS; if (preResult == tesSUCCESS) @@ -476,7 +476,8 @@ Batch::doApply() stx, preclaimResponses[i], ctx_.view().fees().base, - ctx_.base_.open() == 1 ? tapPREFLIGHT_BATCH : ctx_.view().flags(), + ctx_.base_.open() == 1 ? tapPREFLIGHT_BATCH + : ctx_.view().flags(), ctx_.journal); auto const _result = invoke_apply(actx); @@ -519,15 +520,11 @@ Batch::doApply() if (!sleSrcAcc) return tefINTERNAL; - // std::cout << "ACCOUNT BASE SEQ: " << sleBase->getFieldU32(sfSequence) << "\n"; - // std::cout << "ACCOUNT BASE BALANCE: " << sleBase->getFieldAmount(sfBalance) << "\n"; - // std::cout << "ACCOUNT SEQ: " << sleSrcAcc->getFieldU32(sfSequence) << "\n"; - // std::cout << "ACCOUNT BALANCE: " << sleSrcAcc->getFieldAmount(sfBalance) << "\n"; - auto const feePaid = ctx_.tx[sfFee].xrp(); - // auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions); - sleSrcAcc->setFieldU32(sfSequence, ctx_.tx.getFieldU32(sfSequence) + txns.size() + 1); - sleSrcAcc->setFieldAmount(sfBalance, sleBase->getFieldAmount(sfBalance).xrp() - feePaid); + sleSrcAcc->setFieldU32( + sfSequence, ctx_.tx.getFieldU32(sfSequence) + txns.size() + 1); + sleSrcAcc->setFieldAmount( + sfBalance, sleBase->getFieldAmount(sfBalance).xrp() - feePaid); sb.update(sleSrcAcc); sb.apply(ctx_.rawView()); return result; @@ -545,7 +542,8 @@ Batch::calculateBaseFee(ReadView const& view, STTx const& tx) { auto const tt = txn.getFieldU16(sfTransactionType); auto const txtype = safe_cast(tt); - auto const stx = STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); + auto const stx = + STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); txFees += Transactor::calculateBaseFee(view, tx); } extraFee += txFees; diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index 219dd352289..cefdd02b778 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -145,7 +145,7 @@ XRPNotCreated::finalize( { // The net change should never be positive, as this would mean that the // transaction created XRP out of thin air. That's not possible. - + // DA: TODO auto const tt = tx.getTxnType(); if (tt == ttBATCH && res == tesSUCCESS) diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 30e756fc322..1deb050ad4e 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -213,18 +213,11 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) auto const& txn = txns[i]; if (!txn.isFieldPresent(sfFee)) { - JLOG(ctx.j.warn()) - << "Batch: sfFee missing in array entry."; + JLOG(ctx.j.warn()) << "Batch: sfFee missing in array entry."; return telINSUF_FEE_P; } auto const _fee = txn.getFieldAmount(sfFee); feeDue += _fee.xrp(); - - // auto const tt = txn.getFieldU16(sfTransactionType); - // auto const txtype = safe_cast(tt); - // auto const stx = STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); - // auto const _fee = Transactor::calculateBaseFee(ctx.view, stx); - // feeDue += _fee; } if (feePaid < feeDue) From c57ace71fde3667134222ca79794bb335d76904b Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 17 Jul 2024 20:33:56 +0200 Subject: [PATCH 03/71] add atomic swap --- include/xrpl/protocol/SField.h | 2 + include/xrpl/protocol/STTx.h | 3 + src/libxrpl/protocol/InnerObjectFormats.cpp | 6 + src/libxrpl/protocol/SField.cpp | 2 + src/libxrpl/protocol/STTx.cpp | 76 +++++ src/libxrpl/protocol/TxFormats.cpp | 3 +- src/test/app/Batch_test.cpp | 352 ++++++++++++++++++-- src/xrpld/app/tx/detail/Batch.cpp | 50 +-- 8 files changed, 453 insertions(+), 41 deletions(-) diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index 2e5482b0ef6..4c40e6d2f77 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -655,6 +655,7 @@ extern SField const sfXChainCreateAccountAttestationCollectionElement; extern SField const sfRawTransaction; extern SField const sfBatchExecution; extern SField const sfBatchTxn; +extern SField const sfBatchSigner; // array of objects (common) // ARRAY/1 is reserved for end of array @@ -682,6 +683,7 @@ extern SField const sfXChainClaimAttestations; extern SField const sfXChainCreateAccountAttestations; extern SField const sfBatchExecutions; extern SField const sfRawTransactions; +extern SField const sfBatchSigners; //------------------------------------------------------------------------------ diff --git a/include/xrpl/protocol/STTx.h b/include/xrpl/protocol/STTx.h index 08b9a1bad10..5fd28f89d26 100644 --- a/include/xrpl/protocol/STTx.h +++ b/include/xrpl/protocol/STTx.h @@ -123,6 +123,9 @@ class STTx final : public STObject, public CountedObject Expected checkSign(RequireFullyCanonicalSig requireCanonicalSig, Rules const& rules) const; + + Expected + checkBatchSign() const; // SQL Functions with metadata. static std::string const& diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 407155d32b6..f74689162dc 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -160,6 +160,12 @@ InnerObjectFormats::InnerObjectFormats() {sfOuterSequence, soeREQUIRED}, {sfSequence, soeOPTIONAL}, {sfBatchIndex, soeREQUIRED}}); + + add(sfBatchSigner.jsonName.c_str(), + sfBatchSigner.getCode(), + {{sfAccount, soeREQUIRED}, + {sfSigningPubKey, soeREQUIRED}, + {sfTxnSignature, soeREQUIRED}}); } InnerObjectFormats const& diff --git a/src/libxrpl/protocol/SField.cpp b/src/libxrpl/protocol/SField.cpp index 8148b15670e..959c16614ca 100644 --- a/src/libxrpl/protocol/SField.cpp +++ b/src/libxrpl/protocol/SField.cpp @@ -395,6 +395,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfPriceData, "PriceData", OBJECT, CONSTRUCT_UNTYPED_SFIELD(sfRawTransaction, "RawTransaction", OBJECT, 33); CONSTRUCT_UNTYPED_SFIELD(sfBatchExecution, "BatchExecution", OBJECT, 34); CONSTRUCT_UNTYPED_SFIELD(sfBatchTxn, "BatchTxn", OBJECT, 35); +CONSTRUCT_UNTYPED_SFIELD(sfBatchSigner, "BatchSigner", OBJECT, 36); // array of objects // ARRAY/1 is reserved for end of array @@ -427,6 +428,7 @@ CONSTRUCT_UNTYPED_SFIELD(sfPriceDataSeries, "PriceDataSeries", ARRAY, CONSTRUCT_UNTYPED_SFIELD(sfAuthAccounts, "AuthAccounts", ARRAY, 25); CONSTRUCT_UNTYPED_SFIELD(sfBatchExecutions, "BatchExecutions", ARRAY, 26); CONSTRUCT_UNTYPED_SFIELD(sfRawTransactions, "RawTransactions", ARRAY, 27); +CONSTRUCT_UNTYPED_SFIELD(sfBatchSigners, "BatchSigners", ARRAY, 28, SField::sMD_Default, SField::notSigning); // clang-format on diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 54f14741915..f42ebd62f27 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -357,6 +357,82 @@ STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const return {}; } +Expected +STTx::checkBatchSign() const +{ + STArray const& signers{getFieldArray(sfBatchSigners)}; + + // There are well known bounds that the number of signers must be within. + if (signers.size() == 0 || signers.size() > 8) + return Unexpected("Invalid Batch 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)}; + + // We also use the sfAccount field inside the loop. Get it once. + // auto const txnAccountID = getAccountID(sfAccount); + + // Determine whether signatures must be full canonical. + bool const fullyCanonical = true; + + // Signers must be in sorted order by AccountID. + AccountID lastAccountID(beast::zero); + + for (auto const& signer : signers) + { + auto const accountID = signer.getAccountID(sfAccount); + + // // The account owner may not multisign for themselves. + // if (accountID == txnAccountID) + // return Unexpected("Invalid multisigner."); + + // No duplicate signers allowed. + if (lastAccountID == accountID) + return Unexpected("Duplicate Signers not allowed."); + + // Accounts must be in order by account ID. No duplicates allowed. + if (lastAccountID > accountID) + return Unexpected("Unsorted Signers array."); + + // The next signature must be greater than this one. + lastAccountID = accountID; + + // Verify the signature. + 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(), + makeSlice(signature), + fullyCanonical); + } + } + catch (std::exception const&) + { + // We assume any problem lies with the signature. + validSig = false; + } + if (!validSig) + return Unexpected( + std::string("Invalid signature on account ") + + toBase58(accountID) + "."); + } + // All signatures verified. + return {}; +} + Expected STTx::checkMultiSign( RequireFullyCanonicalSig requireCanonicalSig, diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index d35cfc31508..fa102beb5a3 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -510,7 +510,8 @@ TxFormats::TxFormats() add(jss::Batch, ttBATCH, { - {sfRawTransactions, soeOPTIONAL}, + {sfRawTransactions, soeREQUIRED}, + {sfBatchSigners, soeOPTIONAL}, }, commonFields); } diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index f8d7a485d7f..5497a57d045 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -18,7 +18,9 @@ //============================================================================== #include +#include #include +#include #include #include @@ -34,6 +36,12 @@ class Batch_test : public beast::unit_test::suite std::string hash; }; + struct TestSignData + { + int index; + jtx::Account account; + }; + void validateBatchTxns( Json::Value meta, @@ -59,7 +67,6 @@ class Batch_test : public beast::unit_test::suite Json::Value jv, Json::Value const& tx, jtx::Account const& account, - XRPAmount feeDrops, std::uint8_t index, std::uint32_t outerSequence) { @@ -82,6 +89,27 @@ class Batch_test : public beast::unit_test::suite return jv; } + Json::Value + addBatchSignatures(Json::Value jv, std::vector const& signers) + { + auto const ojv = jv; + for (auto const& signer : signers) + { + Serializer ss{ + buildMultiSigningData(jtx::parse(ojv), signer.account.id())}; + std::cout << "strHex(ss.slice()): " << strHex(ss.slice()) << "\n"; + auto const sig = ripple::sign( + signer.account.pk(), signer.account.sk(), ss.slice()); + jv[sfBatchSigners.jsonName][signer.index][sfBatchSigner.jsonName] + [sfAccount.jsonName] = signer.account.human(); + jv[sfBatchSigners.jsonName][signer.index][sfBatchSigner.jsonName] + [sfSigningPubKey.jsonName] = strHex(signer.account.pk()); + jv[sfBatchSigners.jsonName][signer.index][sfBatchSigner.jsonName] + [sfTxnSignature.jsonName] = strHex(Slice{sig.data(), sig.size()}); + } + return jv; + } + void testTemplate(FeatureBitset features) { @@ -119,11 +147,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, feeDrops, 0, seq); + jv = addBatchTx(jv, tx1, alice, 0, seq); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, feeDrops, 1, seq); + jv = addBatchTx(jv, tx2, alice, 1, seq); env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); env.close(); @@ -184,11 +212,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, feeDrops, 0, seq); + jv = addBatchTx(jv, tx1, alice, 0, seq); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, feeDrops, 1, seq); + jv = addBatchTx(jv, tx2, alice, 1, seq); env(jv, fee(feeDrops * 2), @@ -246,11 +274,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, feeDrops, 0, seq); + jv = addBatchTx(jv, tx1, alice, 0, seq); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx2, alice, feeDrops, 1, seq); + jv = addBatchTx(jv, tx2, alice, 1, seq); env(jv, fee(feeDrops * 2), @@ -308,15 +336,15 @@ class Batch_test : public beast::unit_test::suite // Tx 2 Json::Value const tx1 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx1, alice, feeDrops, 0, seq); + jv = addBatchTx(jv, tx1, alice, 0, seq); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, feeDrops, 1, seq); + jv = addBatchTx(jv, tx2, alice, 1, seq); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx3, alice, feeDrops, 2, seq); + jv = addBatchTx(jv, tx3, alice, 2, seq); env(jv, fee(feeDrops * 3), txflags(tfOnlyOne), ter(tesSUCCESS)); env.close(); @@ -373,19 +401,19 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, feeDrops, 0, seq); + jv = addBatchTx(jv, tx1, alice, 0, seq); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, feeDrops, 1, seq); + jv = addBatchTx(jv, tx2, alice, 1, seq); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx3, alice, feeDrops, 2, seq); + jv = addBatchTx(jv, tx3, alice, 2, seq); // Tx 4 Json::Value const tx4 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx4, alice, feeDrops, 3, seq); + jv = addBatchTx(jv, tx4, alice, 3, seq); env(jv, fee(feeDrops * 4), txflags(tfUntilFailure), ter(tesSUCCESS)); env.close(); @@ -446,19 +474,19 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, feeDrops, 0, seq); + jv = addBatchTx(jv, tx1, alice, 0, seq); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, feeDrops, 1, seq); + jv = addBatchTx(jv, tx2, alice, 1, seq); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx3, alice, feeDrops, 2, seq); + jv = addBatchTx(jv, tx3, alice, 2, seq); // Tx 4 Json::Value const tx4 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx4, alice, feeDrops, 3, seq); + jv = addBatchTx(jv, tx4, alice, 3, seq); env(jv, fee(feeDrops * 4), txflags(tfIndependent), ter(tesSUCCESS)); env.close(); @@ -492,15 +520,301 @@ class Batch_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(bob) == preBob + XRP(3)); } + void + testAccountSet(FeatureBitset features) + { + testcase("account set"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + 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 = fset(alice, asfRequireAuth); + 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), ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "AccountSet", + "26F8C5399D4F40DEC5051F57CFBCE27F4A6EB3E013332C05748E7C5450FE074" + "4"}, + {"tesSUCCESS", + "Payment", + "591CF8801EA7B0465DBF309D2B6D103D5E5926203A10F5A433A704C29C1D3" + "26F"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + auto const meta = jrr[jss::result][jss::meta]; + validateBatchTxns(meta, testCases); + + BEAST_EXPECT(env.seq(alice) == 7); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - (feeDrops * 2)); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(1)); + } + + void + testBatch(FeatureBitset features) + { + testcase("batch"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + 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 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); + } + + jv = addBatchTx(jv, btx, 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), ter(temMALFORMED)); + env.close(); + } + + void + testClawback(FeatureBitset features) + { + testcase("clawback"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + env.trust(USD(1000), alice, bob); + env(pay(gw, alice, USD(100))); + env(pay(gw, bob, USD(100))); + env.close(); + + auto const preGw = env.balance(gw); + auto const preBob = env.balance(bob); + + auto const seq = env.seq(gw); + Json::Value jv; + jv[jss::TransactionType] = jss::Batch; + jv[jss::Account] = gw.human(); + jv[jss::Sequence] = seq; + + // Batch Transactions + jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; + + // Tx 1 + Json::Value tx1 = claw(gw, bob["USD"](10)); + jv = addBatchTx(jv, tx1, gw, 0, seq); + + // Tx 2 + Json::Value const tx2 = pay(gw, bob, XRP(1)); + jv = addBatchTx(jv, tx2, gw, 1, seq); + + env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "Clawback", + "D08330FED53B479F2949DF85717101ED513B046B06B748BDF19F5951A81DAAE" + "2"}, + {"tesSUCCESS", + "Payment", + "897B243D48B813D249F8A1353FC3E537DDCC5BD0139CF2670D0FECD435AB1A6" + "6"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + auto const meta = jrr[jss::result][jss::meta]; + validateBatchTxns(meta, testCases); + + BEAST_EXPECT(env.seq(gw) == 10); + BEAST_EXPECT(env.balance(gw) == preGw - XRP(1) - (feeDrops * 2)); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(1)); + } + + void + testAtomicSwap(FeatureBitset features) + { + testcase("atomic swap"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(1000), alice, bob); + env(pay(gw, alice, USD(100))); + env(pay(gw, bob, USD(100))); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + std::vector const signers = {{ + {0, alice}, + {1, 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 + Json::Value tx1 = pay(alice, bob, USD(10)); + jv = addBatchTx(jv, tx1, alice, 0, seq); + + // Tx 2 + Json::Value const tx2 = pay(bob, alice, USD(5)); + jv = addBatchTx(jv, tx2, bob, 1, seq); + // std::cout << "jv: " << jv << "\n"; + + jv = addBatchSignatures(jv, signers); + // std::cout << "jv: " << jv << "\n"; + + // env(jv, bsig(alice, bob), ter(tesSUCCESS)); + env(jv, ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "Payment", + "319131912A291734CCF2766390B6010E1C63D2916011EE6A154B6F210BE43A7" + "2"}, + {"tesSUCCESS", + "Payment", + "DF91E311E37F7670DBB31E98AB6C309555B5B0B20A1DBADFAB2BAC8E4DC8E27" + "0"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + auto const meta = jrr[jss::result][jss::meta]; + validateBatchTxns(meta, testCases); + + BEAST_EXPECT(env.seq(alice) == 8); + BEAST_EXPECT(env.balance(alice) == preAlice - (batchFee)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(5)); + BEAST_EXPECT(env.balance(bob) == preBob); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(5)); + } + void testWithFeats(FeatureBitset features) { - // testTemplate(features); + testTemplate(features); testAllOrNothing(features); testOnlyOne(features); testUntilFailure(features); testIndependent(features); + testAccountSet(features); + testBatch(features); + testClawback(features); + + testAtomicSwap(features); + // Test Fork From one node having 1 extra txn } diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 2af1cc99b0e..6fec7b4a8ce 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -312,6 +312,15 @@ Batch::preflight(PreflightContext const& ctx) return ret; auto& tx = ctx.tx; + bool const isSwap = tx.isFieldPresent(sfBatchSigners); + if (isSwap) + { + auto const sigResult = ctx.tx.checkBatchSign(); + if (!sigResult) + return temBAD_SIGNER; + } + + AccountID const outerAccount = tx.getAccountID(sfAccount); auto const& txns = tx.getFieldArray(sfRawTransactions); if (txns.empty()) @@ -326,7 +335,7 @@ Batch::preflight(PreflightContext const& ctx) return temMALFORMED; } - for (auto const& txn : txns) + for (STObject txn : txns) { if (!txn.isFieldPresent(sfTransactionType)) { @@ -334,12 +343,20 @@ Batch::preflight(PreflightContext const& ctx) << "Batch: TransactionType missing in array entry."; return temMALFORMED; } + if (txn.getFieldU16(sfTransactionType) == ttBATCH) + { + JLOG(ctx.j.error()) << "Batch: batch cannot have inner batch txn."; + return temMALFORMED; + } + + AccountID const innerAccount = txn.getAccountID(sfAccount); + if (!isSwap && innerAccount != outerAccount) + { + JLOG(ctx.j.error()) << "Batch: batch signer mismatch."; + return temBAD_SIGNER; + } - auto const tt = txn.getFieldU16(sfTransactionType); - auto const txtype = safe_cast(tt); - auto const account = txn.getAccountID(sfAccount); - auto const stx = - STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); + STTx const stx = STTx{std::move(txn)}; PreflightContext const pfctx( ctx.app, stx, ctx.rules, tapPREFLIGHT_BATCH, ctx.j); auto const response = invoke_preflight(pfctx); @@ -369,7 +386,7 @@ Batch::preclaim(PreclaimContext const& ctx) continue; } - auto const& txn = txns[i]; + STObject txn = txns[i]; if (!txn.isFieldPresent(sfTransactionType)) { JLOG(ctx.j.error()) @@ -377,10 +394,7 @@ Batch::preclaim(PreclaimContext const& ctx) return temMALFORMED; } - auto const tt = txn.getFieldU16(sfTransactionType); - auto const txtype = safe_cast(tt); - auto const stx = - STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); + STTx const stx = STTx{std::move(txn)}; PreclaimContext const pcctx( ctx.app, ctx.view, preflightResponses[i], stx, ctx.flags, ctx.j); auto const response = invoke_preclaim(pcctx); @@ -412,7 +426,7 @@ Batch::doApply() auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions); for (std::size_t i = 0; i < txns.size(); ++i) { - auto const& txn = txns[i]; + STObject txn = txns[i]; if (!txn.isFieldPresent(sfTransactionType)) { JLOG(ctx_.journal.error()) @@ -420,10 +434,7 @@ Batch::doApply() return temMALFORMED; } - auto const tt = txn.getFieldU16(sfTransactionType); - auto const txtype = safe_cast(tt); - auto const stx = - STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); + STTx const stx = STTx{std::move(txn)}; stxTxns.push_back(stx); } @@ -538,12 +549,9 @@ Batch::calculateBaseFee(ReadView const& view, STTx const& tx) { XRPAmount txFees{0}; auto const& txns = tx.getFieldArray(sfRawTransactions); - for (auto const& txn : txns) + for (STObject txn : txns) { - auto const tt = txn.getFieldU16(sfTransactionType); - auto const txtype = safe_cast(tt); - auto const stx = - STTx(txtype, [&txn](STObject& obj) { obj = std::move(txn); }); + STTx const stx = STTx{std::move(txn)}; txFees += Transactor::calculateBaseFee(view, tx); } extraFee += txFees; From f37377d4c287309e4880d3a530d3ada361b81a54 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 17 Jul 2024 20:40:09 +0200 Subject: [PATCH 04/71] add batch `bsig` --- src/test/jtx/impl/multisign.cpp | 44 +++++++++++++++++++++++++++ src/test/jtx/multisign.h | 53 +++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/src/test/jtx/impl/multisign.cpp b/src/test/jtx/impl/multisign.cpp index 42c3bfc78bf..227db597d21 100644 --- a/src/test/jtx/impl/multisign.cpp +++ b/src/test/jtx/impl/multisign.cpp @@ -110,6 +110,50 @@ 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 3946ea14b26..ccf359782a1 100644 --- a/src/test/jtx/multisign.h +++ b/src/test/jtx/multisign.h @@ -113,6 +113,59 @@ 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. */ From b3136a243443214a46ad0a24a352db9dea27281b Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 17 Jul 2024 20:42:19 +0200 Subject: [PATCH 05/71] [fold] clang-format --- include/xrpl/protocol/STTx.h | 2 +- src/libxrpl/protocol/InnerObjectFormats.cpp | 2 +- src/libxrpl/protocol/STTx.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/xrpl/protocol/STTx.h b/include/xrpl/protocol/STTx.h index 5fd28f89d26..c8a5927bcb7 100644 --- a/include/xrpl/protocol/STTx.h +++ b/include/xrpl/protocol/STTx.h @@ -123,7 +123,7 @@ class STTx final : public STObject, public CountedObject Expected checkSign(RequireFullyCanonicalSig requireCanonicalSig, Rules const& rules) const; - + Expected checkBatchSign() const; diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index f74689162dc..288f9ade2fd 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -160,7 +160,7 @@ InnerObjectFormats::InnerObjectFormats() {sfOuterSequence, soeREQUIRED}, {sfSequence, soeOPTIONAL}, {sfBatchIndex, soeREQUIRED}}); - + add(sfBatchSigner.jsonName.c_str(), sfBatchSigner.getCode(), {{sfAccount, soeREQUIRED}, diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index f42ebd62f27..c8cf80a0790 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -369,7 +369,7 @@ STTx::checkBatchSign() const // 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)}; // We also use the sfAccount field inside the loop. Get it once. From 5e919eb3bb51e1c8a9faf392b902fa9dbf6ddb6d Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 27 Jul 2024 12:04:21 +0200 Subject: [PATCH 06/71] [fold] remove `tapRETRY` --- src/xrpld/app/tx/detail/Batch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 6fec7b4a8ce..ab2184773b4 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -415,7 +415,7 @@ Batch::preclaim(PreclaimContext const& ctx) TER Batch::doApply() { - Sandbox sb(&ctx_.view(), tapRETRY); + Sandbox sb(&ctx_.view()); uint32_t flags = ctx_.tx.getFlags(); if (flags & tfBatchMask) From 1225a6194009c7962beeb6a7f35d633ad02b694a Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 27 Jul 2024 12:04:39 +0200 Subject: [PATCH 07/71] [fold] remove comments --- src/test/app/Batch_test.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 5497a57d045..43e5d11ec2d 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -97,7 +97,7 @@ class Batch_test : public beast::unit_test::suite { Serializer ss{ buildMultiSigningData(jtx::parse(ojv), signer.account.id())}; - std::cout << "strHex(ss.slice()): " << strHex(ss.slice()) << "\n"; + // std::cout << "strHex(ss.slice()): " << strHex(ss.slice()) << "\n"; auto const sig = ripple::sign( signer.account.pk(), signer.account.sk(), ss.slice()); jv[sfBatchSigners.jsonName][signer.index][sfBatchSigner.jsonName] @@ -765,10 +765,8 @@ class Batch_test : public beast::unit_test::suite // Tx 2 Json::Value const tx2 = pay(bob, alice, USD(5)); jv = addBatchTx(jv, tx2, bob, 1, seq); - // std::cout << "jv: " << jv << "\n"; jv = addBatchSignatures(jv, signers); - // std::cout << "jv: " << jv << "\n"; // env(jv, bsig(alice, bob), ter(tesSUCCESS)); env(jv, ter(tesSUCCESS)); @@ -803,7 +801,7 @@ class Batch_test : public beast::unit_test::suite void testWithFeats(FeatureBitset features) { - testTemplate(features); + // testTemplate(features); testAllOrNothing(features); testOnlyOne(features); testUntilFailure(features); From 304ff3e27d5e06838f20e835092b8892eea25176 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 27 Jul 2024 13:11:27 +0200 Subject: [PATCH 08/71] [fold] fix invariant workaround --- src/xrpld/app/tx/detail/InvariantCheck.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index cefdd02b778..10751d00356 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -145,13 +145,6 @@ XRPNotCreated::finalize( { // The net change should never be positive, as this would mean that the // transaction created XRP out of thin air. That's not possible. - - // DA: TODO - auto const tt = tx.getTxnType(); - if (tt == ttBATCH && res == tesSUCCESS) - { - drops_ = -fee.drops(); - } if (drops_ > 0) { JLOG(j.fatal()) << "Invariant failed: XRP net change was positive: " From aded948cac7e1e29709cf262195ce2552da86df0 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 27 Jul 2024 13:11:59 +0200 Subject: [PATCH 09/71] [fold] fix applyTransaction workaround --- src/xrpld/app/ledger/detail/BuildLedger.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/xrpld/app/ledger/detail/BuildLedger.cpp b/src/xrpld/app/ledger/detail/BuildLedger.cpp index 55070889120..d4cb953d442 100644 --- a/src/xrpld/app/ledger/detail/BuildLedger.cpp +++ b/src/xrpld/app/ledger/detail/BuildLedger.cpp @@ -117,14 +117,14 @@ applyTransactions( while (it != txns.end()) { auto const txid = it->first.getTXID(); - auto const isBatch = it->second->isFieldPresent(sfBatchTxn); + // auto const isBatch = it->second->isFieldPresent(sfBatchTxn); try { - if (isBatch && view.txExists(txid)) - { - it = txns.erase(it); - continue; - } + // if (isBatch && view.txExists(txid)) + // { + // it = txns.erase(it); + // continue; + // } if (pass == 0 && built->txExists(txid)) { From 82d4943a2ae538ecabd328483a5cab7319ecd1b7 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 27 Jul 2024 13:13:00 +0200 Subject: [PATCH 10/71] [fold] remove test template --- src/test/app/Batch_test.cpp | 70 ------------------------------------- 1 file changed, 70 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 43e5d11ec2d..a42c6721da3 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -110,75 +110,6 @@ class Batch_test : public beast::unit_test::suite return jv; } - void - testTemplate(FeatureBitset features) - { - testcase("template"); - - using namespace test::jtx; - using namespace std::literals; - - // test::jtx::Env env{*this, envconfig()}; - Env env{ - *this, - envconfig(), - features, - nullptr, - // beast::severities::kWarning - beast::severities::kTrace}; - - auto const feeDrops = env.current()->fees().base; - - auto const alice = Account("alice"); - auto const bob = Account("bob"); - auto const carol = Account("carol"); - env.fund(XRP(1000), alice, bob, carol); - env.close(); - - auto const seq = env.seq("alice"); - std::cout << "seq: " << seq << "\n"; - 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, 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), ter(tesSUCCESS)); - env.close(); - - std::vector testCases = {{ - {"tesSUCCESS", "Payment", ""}, - {"tesSUCCESS", "Payment", ""}, - }}; - - Json::Value params; - params[jss::ledger_index] = env.current()->seq() - 1; - params[jss::transactions] = true; - params[jss::expand] = true; - auto const jrr = env.rpc("json", "ledger", to_string(params)); - std::cout << "jrr: " << jrr << "\n"; - auto const meta = jrr[jss::result][jss::meta]; - validateBatchTxns(meta, testCases); - - std::cout << "seq: " << env.seq(alice) << "\n"; - std::cout << "alice: " << env.balance(alice) << "\n"; - std::cout << "bob: " << env.balance(bob) << "\n"; - - BEAST_EXPECT(env.seq(alice) == 7); - BEAST_EXPECT(env.balance(alice) == XRP(1000) - XRP(2) - (feeDrops * 2)); - BEAST_EXPECT(env.balance(bob) == XRP(1000) + XRP(2)); - } - void testAllOrNothing(FeatureBitset features) { @@ -801,7 +732,6 @@ class Batch_test : public beast::unit_test::suite void testWithFeats(FeatureBitset features) { - // testTemplate(features); testAllOrNothing(features); testOnlyOne(features); testUntilFailure(features); From eaf7893f315c715ec6c1b97457a46e2754358d9e Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 27 Jul 2024 13:13:28 +0200 Subject: [PATCH 11/71] [fold] update logging --- src/xrpld/app/tx/detail/Batch.cpp | 49 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index ab2184773b4..f13cc6ceac3 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -303,7 +303,7 @@ Batch::makeTxConsequences(PreflightContext const& ctx) return TxConsequences{ctx.tx, TxConsequences::normal}; } -std::vector preflightResponses; +std::vector preflightResults; NotTEC Batch::preflight(PreflightContext const& ctx) @@ -325,13 +325,13 @@ Batch::preflight(PreflightContext const& ctx) auto const& txns = tx.getFieldArray(sfRawTransactions); if (txns.empty()) { - JLOG(ctx.j.error()) << "Batch: txns array empty."; + JLOG(ctx.j.debug()) << "Batch: txns array empty."; return temMALFORMED; } if (txns.size() > 8) { - JLOG(ctx.j.error()) << "Batch: txns array exceeds 12 entries."; + JLOG(ctx.j.debug()) << "Batch: txns array exceeds 12 entries."; return temMALFORMED; } @@ -339,34 +339,34 @@ Batch::preflight(PreflightContext const& ctx) { if (!txn.isFieldPresent(sfTransactionType)) { - JLOG(ctx.j.error()) + JLOG(ctx.j.debug()) << "Batch: TransactionType missing in array entry."; return temMALFORMED; } if (txn.getFieldU16(sfTransactionType) == ttBATCH) { - JLOG(ctx.j.error()) << "Batch: batch cannot have inner batch txn."; + JLOG(ctx.j.debug()) << "Batch: batch cannot have inner batch txn."; return temMALFORMED; } AccountID const innerAccount = txn.getAccountID(sfAccount); if (!isSwap && innerAccount != outerAccount) { - JLOG(ctx.j.error()) << "Batch: batch signer mismatch."; + JLOG(ctx.j.debug()) << "Batch: batch signer mismatch."; return temBAD_SIGNER; } STTx const stx = STTx{std::move(txn)}; PreflightContext const pfctx( ctx.app, stx, ctx.rules, tapPREFLIGHT_BATCH, ctx.j); - auto const response = invoke_preflight(pfctx); - preflightResponses.push_back(response.first); + auto const result = invoke_preflight(pfctx); + preflightResults.push_back(result.first); } return preflight2(ctx); } -std::vector preclaimResponses; +std::vector preclaimResults; TER Batch::preclaim(PreclaimContext const& ctx) @@ -378,34 +378,34 @@ Batch::preclaim(PreclaimContext const& ctx) for (std::size_t i = 0; i < txns.size(); ++i) { // Cannot continue on failed txns - if (preflightResponses[i] != tesSUCCESS) + if (preflightResults[i] != tesSUCCESS) { - JLOG(ctx.j.error()) << "Batch: Failed Preflight Response: " - << preflightResponses[i]; - preclaimResponses.push_back(TER(preflightResponses[i])); + JLOG(ctx.j.debug()) << "Batch: Failed Preflight Result: " + << preflightResults[i]; + preclaimResults.push_back(TER(preflightResults[i])); continue; } STObject txn = txns[i]; if (!txn.isFieldPresent(sfTransactionType)) { - JLOG(ctx.j.error()) + JLOG(ctx.j.debug()) << "Batch: TransactionType missing in array entry."; return temMALFORMED; } STTx const stx = STTx{std::move(txn)}; PreclaimContext const pcctx( - ctx.app, ctx.view, preflightResponses[i], stx, ctx.flags, ctx.j); - auto const response = invoke_preclaim(pcctx); - preclaimResponses.push_back(response); + ctx.app, ctx.view, preflightResults[i], stx, ctx.flags, ctx.j); + auto const result = invoke_preclaim(pcctx); + preclaimResults.push_back(result); } - for (auto const& response : preclaimResponses) + for (auto const& result : preclaimResults) { - if (response != tesSUCCESS) + if (result != tesSUCCESS) { - return response; + return result; } } @@ -429,7 +429,7 @@ Batch::doApply() STObject txn = txns[i]; if (!txn.isFieldPresent(sfTransactionType)) { - JLOG(ctx_.journal.error()) + JLOG(ctx_.journal.debug()) << "Batch: TransactionType missing in array entry."; return temMALFORMED; } @@ -447,7 +447,7 @@ Batch::doApply() ctx_.app, ctx_.base_, stx, - preclaimResponses[i], + preclaimResults[i], ctx_.view().fees().base, tapPREFLIGHT_BATCH, ctx_.journal); @@ -485,10 +485,9 @@ Batch::doApply() ctx_.app, ctx_.base_, stx, - preclaimResponses[i], + preclaimResults[i], ctx_.view().fees().base, - ctx_.base_.open() == 1 ? tapPREFLIGHT_BATCH - : ctx_.view().flags(), + ctx_.base_.open() == 1 ? tapPREFLIGHT_BATCH : ctx_.view().flags(), ctx_.journal); auto const _result = invoke_apply(actx); From ca398070b6de0bcb420e4aef10b5f7f2caf64ce0 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 27 Jul 2024 13:14:13 +0200 Subject: [PATCH 12/71] [fold] remove log --- src/test/app/Batch_test.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index a42c6721da3..b44c9b35473 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -97,7 +97,6 @@ class Batch_test : public beast::unit_test::suite { Serializer ss{ buildMultiSigningData(jtx::parse(ojv), signer.account.id())}; - // std::cout << "strHex(ss.slice()): " << strHex(ss.slice()) << "\n"; auto const sig = ripple::sign( signer.account.pk(), signer.account.sk(), ss.slice()); jv[sfBatchSigners.jsonName][signer.index][sfBatchSigner.jsonName] From dd71073dd7f49c17135aeff7825b6fc9a45a9ee1 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 27 Jul 2024 13:15:01 +0200 Subject: [PATCH 13/71] clang-format --- src/xrpld/app/tx/detail/Batch.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index f13cc6ceac3..245f28b6a4e 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -377,11 +377,10 @@ Batch::preclaim(PreclaimContext const& ctx) auto const& txns = ctx.tx.getFieldArray(sfRawTransactions); for (std::size_t i = 0; i < txns.size(); ++i) { - // Cannot continue on failed txns if (preflightResults[i] != tesSUCCESS) { - JLOG(ctx.j.debug()) << "Batch: Failed Preflight Result: " - << preflightResults[i]; + JLOG(ctx.j.debug()) + << "Batch: Failed Preflight Result: " << preflightResults[i]; preclaimResults.push_back(TER(preflightResults[i])); continue; } @@ -487,7 +486,8 @@ Batch::doApply() stx, preclaimResults[i], ctx_.view().fees().base, - ctx_.base_.open() == 1 ? tapPREFLIGHT_BATCH : ctx_.view().flags(), + ctx_.base_.open() == 1 ? tapPREFLIGHT_BATCH + : ctx_.view().flags(), ctx_.journal); auto const _result = invoke_apply(actx); From 26518a5ca08b6c1430b27844756176171f40e4a2 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 27 Jul 2024 13:16:37 +0200 Subject: [PATCH 14/71] [fold] remove workaround --- src/xrpld/app/ledger/detail/BuildLedger.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/xrpld/app/ledger/detail/BuildLedger.cpp b/src/xrpld/app/ledger/detail/BuildLedger.cpp index d4cb953d442..2ad2b0b5bbc 100644 --- a/src/xrpld/app/ledger/detail/BuildLedger.cpp +++ b/src/xrpld/app/ledger/detail/BuildLedger.cpp @@ -117,15 +117,8 @@ applyTransactions( while (it != txns.end()) { auto const txid = it->first.getTXID(); - // auto const isBatch = it->second->isFieldPresent(sfBatchTxn); try { - // if (isBatch && view.txExists(txid)) - // { - // it = txns.erase(it); - // continue; - // } - if (pass == 0 && built->txExists(txid)) { it = txns.erase(it); From 3ceb05c2c90a7f111481e360b03413dcdc96718d Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 30 Jul 2024 14:05:32 +0200 Subject: [PATCH 15/71] update atomic --- src/test/app/Batch_test.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index b44c9b35473..e434f9dd7c8 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -665,6 +665,9 @@ class Batch_test : public beast::unit_test::suite env(pay(gw, bob, USD(100))); env.close(); + env(noop(bob), ter(tesSUCCESS)); + env.close(); + auto const preAlice = env.balance(alice); auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); @@ -675,11 +678,10 @@ class Batch_test : public beast::unit_test::suite {1, bob}, }}; - auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); - jv[jss::Sequence] = seq; + 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; @@ -690,11 +692,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, USD(10)); - jv = addBatchTx(jv, tx1, alice, 0, seq); + jv = addBatchTx(jv, tx1, alice, 0, env.seq(alice)); // Tx 2 Json::Value const tx2 = pay(bob, alice, USD(5)); - jv = addBatchTx(jv, tx2, bob, 1, seq); + jv = addBatchTx(jv, tx2, bob, 1, env.seq(bob)); jv = addBatchSignatures(jv, signers); From 12324f177e4189ddc8c829fbf88bc3b7970b0966 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 31 Jul 2024 16:08:22 +0200 Subject: [PATCH 16/71] refactor & OpenView `revert()` This is not finished. Needs to only revert the transactions submitted not the entire open ledger. --- src/libxrpl/protocol/STTx.cpp | 2 +- src/test/app/Batch_test.cpp | 481 +++++++++++++++++++++---- src/xrpld/app/tx/detail/Batch.cpp | 237 ++++++------ src/xrpld/app/tx/detail/Transactor.cpp | 25 +- src/xrpld/ledger/OpenView.h | 3 + src/xrpld/ledger/detail/OpenView.cpp | 7 + 6 files changed, 543 insertions(+), 212 deletions(-) diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index c8cf80a0790..3107dc6702b 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -195,7 +195,7 @@ STTx::getSeqProxy() const std::uint32_t const startSequence{ batchTxn.getFieldU32(sfOuterSequence)}; std::uint32_t const batchIndex{batchTxn.getFieldU8(sfBatchIndex)}; - return SeqProxy::sequence(startSequence + batchIndex + 1); + return SeqProxy::sequence(startSequence + batchIndex); } std::optional const ticketSeq{operator[](~sfTicketSequence)}; diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index e434f9dd7c8..5f2df2554d6 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -67,8 +67,9 @@ class Batch_test : public beast::unit_test::suite Json::Value jv, Json::Value const& tx, jtx::Account const& account, - std::uint8_t index, - std::uint32_t outerSequence) + std::uint8_t innerIndex, + std::uint32_t outerSequence, + std::uint8_t index) { jv[sfRawTransactions.jsonName][index] = Json::Value{}; jv[sfRawTransactions.jsonName][index][jss::RawTransaction] = tx; @@ -85,7 +86,7 @@ class Batch_test : public beast::unit_test::suite jv[sfRawTransactions.jsonName][index][jss::RawTransaction] [sfBatchTxn.jsonName][sfOuterSequence.jsonName] = outerSequence; jv[sfRawTransactions.jsonName][index][jss::RawTransaction] - [sfBatchTxn.jsonName][sfBatchIndex.jsonName] = index; + [sfBatchTxn.jsonName][sfBatchIndex.jsonName] = innerIndex; return jv; } @@ -142,11 +143,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 0, seq); + jv = addBatchTx(jv, tx1, alice, 1, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 1, seq); + jv = addBatchTx(jv, tx2, alice, 2, seq, 1); env(jv, fee(feeDrops * 2), @@ -170,7 +171,8 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - auto const meta = jrr[jss::result][jss::meta]; + // std::cout << jrr << std::endl; + auto const meta = jrr[jss::result][jss::metaData]; validateBatchTxns(meta, testCases); BEAST_EXPECT(env.seq(alice) == 7); @@ -204,11 +206,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 0, seq); + jv = addBatchTx(jv, tx1, alice, 1, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx2, alice, 1, seq); + jv = addBatchTx(jv, tx2, alice, 2, seq, 1); env(jv, fee(feeDrops * 2), @@ -226,10 +228,11 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - auto const meta = jrr[jss::result][jss::meta]; + // std::cout << jrr << std::endl; + auto const meta = jrr[jss::result][jss::metaData]; validateBatchTxns(meta, testCases); - BEAST_EXPECT(env.seq(alice) == 7); + BEAST_EXPECT(env.seq(alice) == 5); BEAST_EXPECT(env.balance(alice) == preAlice - (feeDrops * 2)); BEAST_EXPECT(env.balance(bob) == preBob); } @@ -266,15 +269,15 @@ class Batch_test : public beast::unit_test::suite // Tx 2 Json::Value const tx1 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx1, alice, 0, seq); + jv = addBatchTx(jv, tx1, alice, 1, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 1, seq); + jv = addBatchTx(jv, tx2, alice, 2, seq, 1); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx3, alice, 2, seq); + jv = addBatchTx(jv, tx3, alice, 3, seq, 2); env(jv, fee(feeDrops * 3), txflags(tfOnlyOne), ter(tesSUCCESS)); env.close(); @@ -292,10 +295,11 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - auto const meta = jrr[jss::result][jss::meta]; + // std::cout << jrr << std::endl; + auto const meta = jrr[jss::result][jss::metaData]; validateBatchTxns(meta, testCases); - BEAST_EXPECT(env.seq(alice) == 8); + BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - (feeDrops * 3)); BEAST_EXPECT(env.balance(bob) == preBob + XRP(1)); } @@ -331,19 +335,19 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 0, seq); + jv = addBatchTx(jv, tx1, alice, 1, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 1, seq); + jv = addBatchTx(jv, tx2, alice, 2, seq, 1); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx3, alice, 2, seq); + jv = addBatchTx(jv, tx3, alice, 3, seq, 2); // Tx 4 Json::Value const tx4 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx4, alice, 3, seq); + jv = addBatchTx(jv, tx4, alice, 4, seq, 3); env(jv, fee(feeDrops * 4), txflags(tfUntilFailure), ter(tesSUCCESS)); env.close(); @@ -365,10 +369,11 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - auto const meta = jrr[jss::result][jss::meta]; + // std::cout << jrr << std::endl; + auto const meta = jrr[jss::result][jss::metaData]; validateBatchTxns(meta, testCases); - BEAST_EXPECT(env.seq(alice) == 9); + BEAST_EXPECT(env.seq(alice) == 8); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - (feeDrops * 4)); BEAST_EXPECT(env.balance(bob) == preBob + XRP(2)); } @@ -404,19 +409,19 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 0, seq); + jv = addBatchTx(jv, tx1, alice, 1, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 1, seq); + jv = addBatchTx(jv, tx2, alice, 2, seq, 1); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx3, alice, 2, seq); + jv = addBatchTx(jv, tx3, alice, 3, seq, 2); // Tx 4 Json::Value const tx4 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx4, alice, 3, seq); + jv = addBatchTx(jv, tx4, alice, 4, seq, 3); env(jv, fee(feeDrops * 4), txflags(tfIndependent), ter(tesSUCCESS)); env.close(); @@ -442,7 +447,8 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - auto const meta = jrr[jss::result][jss::meta]; + // std::cout << jrr << std::endl; + auto const meta = jrr[jss::result][jss::metaData]; validateBatchTxns(meta, testCases); BEAST_EXPECT(env.seq(alice) == 9); @@ -450,6 +456,99 @@ class Batch_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(bob) == preBob + XRP(3)); } + void + testAtomicSwap(FeatureBitset features) + { + testcase("atomic swap"); + + using namespace test::jtx; + 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"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(1000), alice, bob); + env(pay(gw, alice, USD(100))); + env(pay(gw, bob, USD(100))); + env.close(); + + env(noop(bob), ter(tesSUCCESS)); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + std::vector const signers = {{ + {0, alice}, + {1, bob}, + }}; + + 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, USD(10)); + jv = addBatchTx(jv, tx1, alice, 1, env.seq(alice), 0); + + // Tx 2 + Json::Value const tx2 = pay(bob, alice, USD(5)); + jv = addBatchTx(jv, tx2, bob, 0, env.seq(bob), 1); + + jv = addBatchSignatures(jv, signers); + + // env(jv, bsig(alice, bob), ter(tesSUCCESS)); + env(jv, ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "Payment", + "319131912A291734CCF2766390B6010E1C63D2916011EE6A154B6F210BE43A7" + "2"}, + {"tesSUCCESS", + "Payment", + "DF91E311E37F7670DBB31E98AB6C309555B5B0B20A1DBADFAB2BAC8E4DC8E27" + "0"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + auto const meta = jrr[jss::result][jss::metaData]; + validateBatchTxns(meta, testCases); + + BEAST_EXPECT(env.seq(alice) == 7); + BEAST_EXPECT(env.seq(bob) == 7); + BEAST_EXPECT(env.balance(alice) == preAlice - (batchFee)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(5)); + BEAST_EXPECT(env.balance(bob) == preBob); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(5)); + } + void testAccountSet(FeatureBitset features) { @@ -481,11 +580,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = fset(alice, asfRequireAuth); - jv = addBatchTx(jv, tx1, alice, 0, seq); + jv = addBatchTx(jv, tx1, alice, 1, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 1, seq); + jv = addBatchTx(jv, tx2, alice, 2, seq, 1); env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); env.close(); @@ -506,7 +605,8 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - auto const meta = jrr[jss::result][jss::meta]; + // std::cout << jrr << std::endl; + auto const meta = jrr[jss::result][jss::metaData]; validateBatchTxns(meta, testCases); BEAST_EXPECT(env.seq(alice) == 7); @@ -555,19 +655,270 @@ class Batch_test : public beast::unit_test::suite // bTx 1 Json::Value const btx1 = pay(alice, bob, XRP(1)); - btx = addBatchTx(btx, btx1, alice, 0, seq); + btx = addBatchTx(btx, btx1, alice, 0, seq, 0); } - jv = addBatchTx(jv, btx, alice, 0, seq); + jv = addBatchTx(jv, btx, alice, 1, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 1, seq); + jv = addBatchTx(jv, tx2, alice, 2, seq, 1); env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(temMALFORMED)); env.close(); } + static uint256 + getCheckIndex(AccountID const& account, std::uint32_t uSequence) + { + return keylet::check(account, uSequence).key; + } + + void + testCheckCreate(FeatureBitset features) + { + testcase("check create"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + 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 = check::create(alice, bob, XRP(10)); + jv = addBatchTx(jv, tx1, alice, 0, seq, 0); + + // Tx 2 + Json::Value const tx2 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx2, alice, 1, seq, 1); + + env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "CheckCreate", + "26F8C5399D4F40DEC5051F57CFBCE27F4A6EB3E013332C05748E7C5450FE074" + "4"}, + {"tesSUCCESS", + "Payment", + "591CF8801EA7B0465DBF309D2B6D103D5E5926203A10F5A433A704C29C1D3" + "26F"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + auto const meta = jrr[jss::result][jss::metaData]; + validateBatchTxns(meta, testCases); + + BEAST_EXPECT(env.seq(alice) == 7); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - (feeDrops * 2)); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(1)); + } + + void + testCheckCash(FeatureBitset features) + { + testcase("check cash"); + + using namespace test::jtx; + 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"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + + env.trust(USD(1000), alice, bob); + env(pay(gw, alice, USD(100))); + env(pay(gw, bob, USD(100))); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBobUSD = env.balance(bob, USD.issue()); + + std::vector const signers = {{ + {0, alice}, + {1, 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 + uint256 const chkId{getCheckIndex(alice, seq + 1)}; + Json::Value tx1 = check::create(alice, bob, USD(10)); + jv = addBatchTx(jv, tx1, alice, 1, env.seq(alice), 0); + + // Tx 2 + Json::Value const tx2 = check::cash(bob, chkId, USD(10)); + jv = addBatchTx(jv, tx2, bob, 0, env.seq(bob), 1); + + jv = addBatchSignatures(jv, signers); + + env(jv, ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "CheckCreate", + "145CBCD0C29955E43452EF891979DF63CB9D32CDB6F91FEAEE402D39FC53845" + "5"}, + {"tesSUCCESS", + "CheckCash", + "10C5B1A9861230AD0BC9BA9FD957944B2A06F5A5C888557586D96EE58DD5861" + "8"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + auto const meta = jrr[jss::result][jss::metaData]; + validateBatchTxns(meta, testCases); + + BEAST_EXPECT(env.seq(alice) == 7); + BEAST_EXPECT(env.seq(bob) == 6); + BEAST_EXPECT(env.balance(alice) == preAlice - (batchFee)); + BEAST_EXPECT(env.balance(bob) == preBob); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(10)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(10)); + } + + void + testCheckCancel(FeatureBitset features) + { + testcase("check cancel"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + + env.trust(USD(1000), alice, bob); + env(pay(gw, alice, USD(100))); + env(pay(gw, bob, USD(100))); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBobUSD = env.balance(bob, USD.issue()); + + std::vector const signers = {{ + {0, alice}, + {1, 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 + uint256 const chkId{getCheckIndex(alice, seq + 1)}; + Json::Value tx1 = check::create(alice, bob, XRP(10)); + jv = addBatchTx(jv, tx1, alice, 1, env.seq(alice), 0); + + // Tx 2 + Json::Value const tx2 = check::cancel(bob, chkId); + jv = addBatchTx(jv, tx2, bob, 0, env.seq(bob), 1); + + jv = addBatchSignatures(jv, signers); + + env(jv, ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "CheckCreate", + "145CBCD0C29955E43452EF891979DF63CB9D32CDB6F91FEAEE402D39FC53845" + "5"}, + {"tesSUCCESS", + "CheckCancel", + "10C5B1A9861230AD0BC9BA9FD957944B2A06F5A5C888557586D96EE58DD5861" + "8"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + auto const meta = jrr[jss::result][jss::metaData]; + validateBatchTxns(meta, testCases); + + BEAST_EXPECT(env.seq(alice) == 7); + BEAST_EXPECT(env.seq(bob) == 6); + BEAST_EXPECT(env.balance(alice) == preAlice - (batchFee)); + BEAST_EXPECT(env.balance(bob) == preBob); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD); + } + void testClawback(FeatureBitset features) { @@ -609,11 +960,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = claw(gw, bob["USD"](10)); - jv = addBatchTx(jv, tx1, gw, 0, seq); + jv = addBatchTx(jv, tx1, gw, 1, seq, 0); // Tx 2 Json::Value const tx2 = pay(gw, bob, XRP(1)); - jv = addBatchTx(jv, tx2, gw, 1, seq); + jv = addBatchTx(jv, tx2, gw, 2, seq, 1); env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); env.close(); @@ -634,7 +985,7 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - auto const meta = jrr[jss::result][jss::meta]; + auto const meta = jrr[jss::result][jss::metaData]; validateBatchTxns(meta, testCases); BEAST_EXPECT(env.seq(gw) == 10); @@ -643,9 +994,9 @@ class Batch_test : public beast::unit_test::suite } void - testAtomicSwap(FeatureBitset features) + testOffer(FeatureBitset features) { - testcase("atomic swap"); + testcase("offer"); using namespace test::jtx; using namespace std::literals; @@ -660,59 +1011,44 @@ class Batch_test : public beast::unit_test::suite env.fund(XRP(1000), alice, bob, gw); env.close(); + env.trust(USD(1000), alice, bob); env(pay(gw, alice, USD(100))); env(pay(gw, bob, USD(100))); env.close(); - env(noop(bob), ter(tesSUCCESS)); - env.close(); - auto const preAlice = env.balance(alice); - auto const preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); - auto const preBobUSD = env.balance(bob, USD.issue()); - - std::vector const signers = {{ - {0, alice}, - {1, bob}, - }}; + auto const seq = env.seq(alice); 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()); + jv[jss::Sequence] = seq; // Batch Transactions jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; // Tx 1 - Json::Value tx1 = pay(alice, bob, USD(10)); - jv = addBatchTx(jv, tx1, alice, 0, env.seq(alice)); + Json::Value tx1 = offer(alice, XRP(50), USD(50)); + jv = addBatchTx(jv, tx1, alice, 0, seq, 0); // Tx 2 - Json::Value const tx2 = pay(bob, alice, USD(5)); - jv = addBatchTx(jv, tx2, bob, 1, env.seq(bob)); + Json::Value const tx2 = offer_cancel(alice, seq + 0 + 1); + jv = addBatchTx(jv, tx2, alice, 1, seq, 1); - jv = addBatchSignatures(jv, signers); - - // env(jv, bsig(alice, bob), ter(tesSUCCESS)); - env(jv, ter(tesSUCCESS)); + env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); env.close(); std::vector testCases = {{ {"tesSUCCESS", - "Payment", - "319131912A291734CCF2766390B6010E1C63D2916011EE6A154B6F210BE43A7" - "2"}, + "OfferCreate", + "145CBCD0C29955E43452EF891979DF63CB9D32CDB6F91FEAEE402D39FC53845" + "5"}, {"tesSUCCESS", - "Payment", - "DF91E311E37F7670DBB31E98AB6C309555B5B0B20A1DBADFAB2BAC8E4DC8E27" - "0"}, + "OfferCancel", + "10C5B1A9861230AD0BC9BA9FD957944B2A06F5A5C888557586D96EE58DD5861" + "8"}, }}; Json::Value params; @@ -720,14 +1056,12 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - auto const meta = jrr[jss::result][jss::meta]; + auto const meta = jrr[jss::result][jss::ledger][jss::metaData]; validateBatchTxns(meta, testCases); BEAST_EXPECT(env.seq(alice) == 8); - BEAST_EXPECT(env.balance(alice) == preAlice - (batchFee)); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(5)); + BEAST_EXPECT(env.balance(alice) == preAlice - (feeDrops * 2)); BEAST_EXPECT(env.balance(bob) == preBob); - BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(5)); } void @@ -737,12 +1071,15 @@ class Batch_test : public beast::unit_test::suite testOnlyOne(features); testUntilFailure(features); testIndependent(features); + testAtomicSwap(features); testAccountSet(features); testBatch(features); + testCheckCreate(features); + testCheckCash(features); + testCheckCancel(features); testClawback(features); - - testAtomicSwap(features); + // testOffer(features); // Test Fork From one node having 1 extra txn } diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 245f28b6a4e..15ca97de82b 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -237,26 +237,20 @@ invoke_preclaim(PreclaimContext const& ctx) if (id != beast::zero) { - // TER result = T::checkSeqProxy(ctx.view, ctx.tx, ctx.j); + TER result = T::checkSeqProxy(ctx.view, ctx.tx, ctx.j); - // if (result != tesSUCCESS) - // return result; - - // Ignore Sequence Validation on ttBATCH txns - TER result = tesSUCCESS; + if (result != tesSUCCESS) + return result; - JLOG(ctx.j.trace()) << "invoke_preclaim.Batch: " - << "\n"; result = T::checkPriorTxAndLastLedger(ctx); if (result != tesSUCCESS) return result; - // result = T::checkFee(ctx, calculateBaseFee(ctx.view, - // ctx.tx)); + result = T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx)); - // if (result != tesSUCCESS) - // return result; + if (result != tesSUCCESS) + return result; result = T::checkSign(ctx); @@ -303,8 +297,6 @@ Batch::makeTxConsequences(PreflightContext const& ctx) return TxConsequences{ctx.tx, TxConsequences::normal}; } -std::vector preflightResults; - NotTEC Batch::preflight(PreflightContext const& ctx) { @@ -355,19 +347,11 @@ Batch::preflight(PreflightContext const& ctx) JLOG(ctx.j.debug()) << "Batch: batch signer mismatch."; return temBAD_SIGNER; } - - STTx const stx = STTx{std::move(txn)}; - PreflightContext const pfctx( - ctx.app, stx, ctx.rules, tapPREFLIGHT_BATCH, ctx.j); - auto const result = invoke_preflight(pfctx); - preflightResults.push_back(result.first); } return preflight2(ctx); } -std::vector preclaimResults; - TER Batch::preclaim(PreclaimContext const& ctx) { @@ -377,14 +361,6 @@ Batch::preclaim(PreclaimContext const& ctx) auto const& txns = ctx.tx.getFieldArray(sfRawTransactions); for (std::size_t i = 0; i < txns.size(); ++i) { - if (preflightResults[i] != tesSUCCESS) - { - JLOG(ctx.j.debug()) - << "Batch: Failed Preflight Result: " << preflightResults[i]; - preclaimResults.push_back(TER(preflightResults[i])); - continue; - } - STObject txn = txns[i]; if (!txn.isFieldPresent(sfTransactionType)) { @@ -392,20 +368,6 @@ Batch::preclaim(PreclaimContext const& ctx) << "Batch: TransactionType missing in array entry."; return temMALFORMED; } - - STTx const stx = STTx{std::move(txn)}; - PreclaimContext const pcctx( - ctx.app, ctx.view, preflightResults[i], stx, ctx.flags, ctx.j); - auto const result = invoke_preclaim(pcctx); - preclaimResults.push_back(result); - } - - for (auto const& result : preclaimResults) - { - if (result != tesSUCCESS) - { - return result; - } } return tesSUCCESS; @@ -420,122 +382,127 @@ Batch::doApply() if (flags & tfBatchMask) return temINVALID_FLAG; - // SANITIZE - std::vector stxTxns; + ApplyViewImpl& avi = dynamic_cast(ctx_.view()); + TER result = tesSUCCESS; + std::map accountCount; auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions); - for (std::size_t i = 0; i < txns.size(); ++i) + for (STObject txn : txns) { - STObject txn = txns[i]; - if (!txn.isFieldPresent(sfTransactionType)) - { - JLOG(ctx_.journal.debug()) - << "Batch: TransactionType missing in array entry."; - return temMALFORMED; - } - STTx const stx = STTx{std::move(txn)}; - stxTxns.push_back(stx); - } - // DRY RUN - std::vector> dryVector; - for (std::size_t i = 0; i < stxTxns.size(); ++i) - { - auto const& stx = stxTxns[i]; - ApplyContext actx( + // preflight + PreflightContext const pfctx( ctx_.app, - ctx_.base_, stx, - preclaimResults[i], - ctx_.view().fees().base, + ctx_.view().rules(), tapPREFLIGHT_BATCH, ctx_.journal); - auto const result = invoke_apply(actx); - dryVector.emplace_back(stx.getTxnType(), result.first); - actx.discard(); - } + auto const preflightResult = + PreflightResult{pfctx, invoke_preflight(pfctx)}; + + // preclaim + std::optional pcctx; + pcctx.emplace( + ctx_.app, + ctx_.base_, + preflightResult.ter, + preflightResult.tx, + preflightResult.flags, + preflightResult.j); + auto const preclaimResult = + PreclaimResult{*pcctx, invoke_preclaim(*pcctx)}; + + // doApply + ApplyContext actx( + ctx_.app, + ctx_.base_, + preclaimResult.tx, + preclaimResult.ter, + calculateBaseFee(ctx_.base_, preclaimResult.tx), + ctx_.base_.open() == 1 ? tapPREFLIGHT_BATCH : ctx_.view().flags(), + preclaimResult.j); + auto const applyResult = invoke_apply(actx); - TER preResult = tesSUCCESS; - ApplyViewImpl& avi = dynamic_cast(ctx_.view()); - for (auto const& dryRun : dryVector) - { STObject meta{sfBatchExecution}; - meta.setFieldU8(sfTransactionResult, TERtoInt(dryRun.second)); - meta.setFieldU16(sfTransactionType, dryRun.first); + meta.setFieldU8(sfTransactionResult, TERtoInt(applyResult.first)); + meta.setFieldU16(sfTransactionType, stx.getTxnType()); + if (applyResult.first == tesSUCCESS) + meta.setFieldH256(sfTransactionHash, stx.getTransactionID()); + avi.addBatchExecutionMetaData(std::move(meta)); - // tfAllOrNothing - if (dryRun.second != tesSUCCESS && flags & tfAllOrNothing) - { - preResult = tecBATCH_FAILURE; - } - } + accountCount[stx.getAccountID(sfAccount)] += 1; - // WET RUN - TER result = tesSUCCESS; - if (preResult == tesSUCCESS) - { - std::vector batch; - avi.setHookMetaData(std::move(batch)); - for (std::size_t i = 0; i < stxTxns.size(); ++i) + if (applyResult.first != tesSUCCESS) { - auto const& stx = stxTxns[i]; - ApplyContext actx( - ctx_.app, - ctx_.base_, - stx, - preclaimResults[i], - ctx_.view().fees().base, - ctx_.base_.open() == 1 ? tapPREFLIGHT_BATCH - : ctx_.view().flags(), - ctx_.journal); - auto const _result = invoke_apply(actx); - - STObject meta{sfBatchExecution}; - meta.setFieldU8(sfTransactionResult, TERtoInt(_result.first)); - meta.setFieldU16(sfTransactionType, stx.getTxnType()); - if (_result.first == tesSUCCESS) - meta.setFieldH256(sfTransactionHash, stx.getTransactionID()); - - avi.addBatchExecutionMetaData(std::move(meta)); - - if (_result.first != tesSUCCESS) + if (flags & tfUntilFailure) { - if (flags & tfUntilFailure) - { - actx.discard(); - result = tesSUCCESS; - break; - } - if (flags & tfOnlyOne) - { - actx.discard(); - continue; - } + actx.discard(); + result = tesSUCCESS; + break; } - - if (_result.first == tesSUCCESS && flags & tfOnlyOne) + if (flags & tfOnlyOne) { + actx.discard(); + continue; + } + if (flags & tfAllOrNothing) + { + accountCount.clear(); + ctx_.base_.rawRevert(); + std::vector batch; + avi.setHookMetaData(std::move(batch)); result = tesSUCCESS; break; } } + + if (applyResult.first == tesSUCCESS && flags & tfOnlyOne) + { + result = tesSUCCESS; + break; + } } - auto const sleBase = ctx_.base_.read(keylet::account(account_)); - if (!sleBase) - return tefINTERNAL; + { + auto const sleBase = ctx_.base_.read(keylet::account(account_)); + if (!sleBase) + return tefINTERNAL; + + auto const sleSrcAcc = sb.peek(keylet::account(account_)); + if (!sleSrcAcc) + return tefINTERNAL; + + // Update Fee (Source Account) + auto const feePaid = ctx_.tx[sfFee].xrp(); + sleSrcAcc->setFieldAmount( + sfBalance, sleBase->getFieldAmount(sfBalance).xrp() - feePaid); + + // Update Sequence (Source Account) + sleSrcAcc->setFieldU32( + sfSequence, + sleSrcAcc->getFieldU32(sfSequence) + accountCount[account_]); + sb.update(sleSrcAcc); + } - auto const sleSrcAcc = sb.peek(keylet::account(account_)); - if (!sleSrcAcc) - return tefINTERNAL; + // Update Sequence (Other Inner Accounts) + for (auto const& [_account, count] : accountCount) + { + if (_account == account_) + { + // pass + } + else + { + auto const _sleSrcAcc = sb.peek(keylet::account(_account)); + if (!_sleSrcAcc) + return tefINTERNAL; - auto const feePaid = ctx_.tx[sfFee].xrp(); - sleSrcAcc->setFieldU32( - sfSequence, ctx_.tx.getFieldU32(sfSequence) + txns.size() + 1); - sleSrcAcc->setFieldAmount( - sfBalance, sleBase->getFieldAmount(sfBalance).xrp() - feePaid); - sb.update(sleSrcAcc); + _sleSrcAcc->setFieldU32( + sfSequence, _sleSrcAcc->getFieldU32(sfSequence) + count); + sb.update(_sleSrcAcc); + } + } sb.apply(ctx_.rawView()); return result; } diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 1deb050ad4e..584396909c8 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -200,6 +200,16 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) return temBAD_FEE; auto const feePaid = ctx.tx[sfFee].xrp(); + if (ctx.tx.isFieldPresent(sfBatchTxn)) + { + if (feePaid == beast::zero) + { + return tesSUCCESS; + } + JLOG(ctx.j.warn()) << "Batch: sfFee must be zero."; + return temBAD_FEE; + } + if (!isLegalAmount(feePaid) || feePaid < beast::zero) return temBAD_FEE; @@ -314,12 +324,20 @@ Transactor::checkSeqProxy( if (tx.isFieldPresent(sfBatchTxn)) { + if (tx.getFieldU32(sfSequence) != 0) + { + JLOG(j.trace()) << "applyTransaction: has both a Sequence number " + "and a BatchTxn"; + return temBAD_SEQUENCE; + } + STObject const batchTxn = const_cast(tx) .getField(sfBatchTxn) .downcast(); + std::uint32_t const startSequence{ + batchTxn.getFieldU32(sfOuterSequence)}; std::uint32_t const batchIndex{batchTxn.getFieldU8(sfBatchIndex)}; - a_seq = SeqProxy::sequence(a_seq.value() - (batchIndex + 1)); - a_seq = t_seqProx; + a_seq = SeqProxy::sequence(startSequence + batchIndex); } if (t_seqProx.isSeq()) @@ -397,8 +415,7 @@ Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx) (ctx.view.seq() > ctx.tx.getFieldU32(sfLastLedgerSequence))) return tefMAX_LEDGER; - if (ctx.view.txExists(ctx.tx.getTransactionID()) && - ctx.tx.getTxnType() != ttBATCH && !ctx.tx.isFieldPresent(sfBatchTxn)) + if (ctx.view.txExists(ctx.tx.getTransactionID())) return tefALREADY; return tesSUCCESS; diff --git a/src/xrpld/ledger/OpenView.h b/src/xrpld/ledger/OpenView.h index bd8627a18b2..38b3ec635b9 100644 --- a/src/xrpld/ledger/OpenView.h +++ b/src/xrpld/ledger/OpenView.h @@ -242,6 +242,9 @@ class OpenView final : public ReadView, public TxsRawView void rawReplace(std::shared_ptr const& sle) override; + + void + rawRevert(); void rawDestroyXRP(XRPAmount const& fee) override; diff --git a/src/xrpld/ledger/detail/OpenView.cpp b/src/xrpld/ledger/detail/OpenView.cpp index 619006161f8..06adcec1efc 100644 --- a/src/xrpld/ledger/detail/OpenView.cpp +++ b/src/xrpld/ledger/detail/OpenView.cpp @@ -247,6 +247,13 @@ OpenView::rawReplace(std::shared_ptr const& sle) items_.replace(sle); } +void +OpenView::rawRevert() +{ + txs_.clear(); + items_.revert(); +} + void OpenView::rawDestroyXRP(XRPAmount const& fee) { From e9f045b6798c6d95ff0df9053d0ed86cf8bc3f64 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 31 Jul 2024 16:10:45 +0200 Subject: [PATCH 17/71] [fold] clang-format --- src/xrpld/ledger/OpenView.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xrpld/ledger/OpenView.h b/src/xrpld/ledger/OpenView.h index 38b3ec635b9..6ac83b43549 100644 --- a/src/xrpld/ledger/OpenView.h +++ b/src/xrpld/ledger/OpenView.h @@ -242,7 +242,7 @@ class OpenView final : public ReadView, public TxsRawView void rawReplace(std::shared_ptr const& sle) override; - + void rawRevert(); From 676b9b685627febdd05816487828cadc4a04590e Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 31 Jul 2024 16:16:18 +0200 Subject: [PATCH 18/71] [fold] add revert --- src/xrpld/app/ledger/detail/BuildLedger.cpp | 1 + src/xrpld/ledger/detail/RawStateTable.cpp | 7 +++++++ src/xrpld/ledger/detail/RawStateTable.h | 3 +++ 3 files changed, 11 insertions(+) diff --git a/src/xrpld/app/ledger/detail/BuildLedger.cpp b/src/xrpld/app/ledger/detail/BuildLedger.cpp index 2ad2b0b5bbc..fcd3c3062e5 100644 --- a/src/xrpld/app/ledger/detail/BuildLedger.cpp +++ b/src/xrpld/app/ledger/detail/BuildLedger.cpp @@ -119,6 +119,7 @@ applyTransactions( auto const txid = it->first.getTXID(); try { + if (pass == 0 && built->txExists(txid)) { it = txns.erase(it); diff --git a/src/xrpld/ledger/detail/RawStateTable.cpp b/src/xrpld/ledger/detail/RawStateTable.cpp index 4d3732f8a45..9d8e0029d24 100644 --- a/src/xrpld/ledger/detail/RawStateTable.cpp +++ b/src/xrpld/ledger/detail/RawStateTable.cpp @@ -303,6 +303,13 @@ RawStateTable::replace(std::shared_ptr const& sle) } } +void +RawStateTable::revert() +{ + // Clear the items_ map + items_.clear(); +} + std::shared_ptr RawStateTable::read(ReadView const& base, Keylet const& k) const { diff --git a/src/xrpld/ledger/detail/RawStateTable.h b/src/xrpld/ledger/detail/RawStateTable.h index 2db37d98333..73983924f48 100644 --- a/src/xrpld/ledger/detail/RawStateTable.h +++ b/src/xrpld/ledger/detail/RawStateTable.h @@ -83,6 +83,9 @@ class RawStateTable void replace(std::shared_ptr const& sle); + void + revert(); + std::shared_ptr read(ReadView const& base, Keylet const& k) const; From 6c6180d52bc0030e0e26a4a409f8988362e91994 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 31 Jul 2024 16:28:30 +0200 Subject: [PATCH 19/71] [fold] clang-format --- src/xrpld/app/ledger/detail/BuildLedger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xrpld/app/ledger/detail/BuildLedger.cpp b/src/xrpld/app/ledger/detail/BuildLedger.cpp index fcd3c3062e5..8c4a7a3f41d 100644 --- a/src/xrpld/app/ledger/detail/BuildLedger.cpp +++ b/src/xrpld/app/ledger/detail/BuildLedger.cpp @@ -117,9 +117,9 @@ applyTransactions( while (it != txns.end()) { auto const txid = it->first.getTXID(); + try { - if (pass == 0 && built->txExists(txid)) { it = txns.erase(it); From bd03ea3aee072e46e9922d8c124000c24f3238b4 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 1 Aug 2024 10:37:35 +0200 Subject: [PATCH 20/71] [revert] remove rawRevert func --- src/xrpld/ledger/OpenView.h | 3 --- src/xrpld/ledger/detail/OpenView.cpp | 7 ------- src/xrpld/ledger/detail/RawStateTable.cpp | 7 ------- src/xrpld/ledger/detail/RawStateTable.h | 3 --- 4 files changed, 20 deletions(-) diff --git a/src/xrpld/ledger/OpenView.h b/src/xrpld/ledger/OpenView.h index 6ac83b43549..bd8627a18b2 100644 --- a/src/xrpld/ledger/OpenView.h +++ b/src/xrpld/ledger/OpenView.h @@ -243,9 +243,6 @@ class OpenView final : public ReadView, public TxsRawView void rawReplace(std::shared_ptr const& sle) override; - void - rawRevert(); - void rawDestroyXRP(XRPAmount const& fee) override; diff --git a/src/xrpld/ledger/detail/OpenView.cpp b/src/xrpld/ledger/detail/OpenView.cpp index 06adcec1efc..619006161f8 100644 --- a/src/xrpld/ledger/detail/OpenView.cpp +++ b/src/xrpld/ledger/detail/OpenView.cpp @@ -247,13 +247,6 @@ OpenView::rawReplace(std::shared_ptr const& sle) items_.replace(sle); } -void -OpenView::rawRevert() -{ - txs_.clear(); - items_.revert(); -} - void OpenView::rawDestroyXRP(XRPAmount const& fee) { diff --git a/src/xrpld/ledger/detail/RawStateTable.cpp b/src/xrpld/ledger/detail/RawStateTable.cpp index 9d8e0029d24..4d3732f8a45 100644 --- a/src/xrpld/ledger/detail/RawStateTable.cpp +++ b/src/xrpld/ledger/detail/RawStateTable.cpp @@ -303,13 +303,6 @@ RawStateTable::replace(std::shared_ptr const& sle) } } -void -RawStateTable::revert() -{ - // Clear the items_ map - items_.clear(); -} - std::shared_ptr RawStateTable::read(ReadView const& base, Keylet const& k) const { diff --git a/src/xrpld/ledger/detail/RawStateTable.h b/src/xrpld/ledger/detail/RawStateTable.h index 73983924f48..2db37d98333 100644 --- a/src/xrpld/ledger/detail/RawStateTable.h +++ b/src/xrpld/ledger/detail/RawStateTable.h @@ -83,9 +83,6 @@ class RawStateTable void replace(std::shared_ptr const& sle); - void - revert(); - std::shared_ptr read(ReadView const& base, Keylet const& k) const; From c0e00b0ad8b715ab200b5ce4edb7742b0dd3cd8d Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 3 Aug 2024 12:38:51 +0200 Subject: [PATCH 21/71] refactor with stacking views --- src/libxrpl/protocol/STTx.cpp | 4 +- src/test/app/Batch_test.cpp | 290 ++++++++++-------- src/xrpld/app/tx/detail/ApplyContext.cpp | 41 +++ src/xrpld/app/tx/detail/ApplyContext.h | 9 +- src/xrpld/app/tx/detail/Batch.cpp | 370 ++--------------------- src/xrpld/app/tx/detail/Transactor.cpp | 23 +- src/xrpld/app/tx/detail/apply.cpp | 5 +- src/xrpld/ledger/ApplyView.h | 3 - 8 files changed, 266 insertions(+), 479 deletions(-) diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 3107dc6702b..41673160c09 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -195,7 +195,9 @@ STTx::getSeqProxy() const std::uint32_t const startSequence{ batchTxn.getFieldU32(sfOuterSequence)}; std::uint32_t const batchIndex{batchTxn.getFieldU8(sfBatchIndex)}; - return SeqProxy::sequence(startSequence + batchIndex); + std::uint8_t const sourceAdj = + getAccountID(sfAccount) == batchTxn.getAccountID(sfAccount) ? 1 : 0; + return SeqProxy::sequence(startSequence + batchIndex + sourceAdj); } std::optional const ticketSeq{operator[](~sfTicketSequence)}; diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 5f2df2554d6..c355547d7da 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -42,11 +42,23 @@ class Batch_test : public beast::unit_test::suite jtx::Account account; }; + Json::Value + getTxByIndex(Json::Value jrr, std::uint8_t index) + { + for (auto const& txn : jrr[jss::result][jss::ledger][jss::transactions]) + { + if (txn[jss::metaData][sfTransactionIndex.jsonName] == index) + return txn; + } + return {}; + } + void validateBatchTxns( Json::Value meta, std::vector const& batchResults) { + BEAST_EXPECT(meta[sfBatchExecutions.jsonName].size() > 0); size_t index = 0; for (auto const& _batchTxn : meta[sfBatchExecutions.jsonName]) { @@ -66,6 +78,7 @@ class Batch_test : public beast::unit_test::suite addBatchTx( Json::Value jv, Json::Value const& tx, + PublicKey const& pk, jtx::Account const& account, std::uint8_t innerIndex, std::uint32_t outerSequence, @@ -74,7 +87,7 @@ class Batch_test : public beast::unit_test::suite jv[sfRawTransactions.jsonName][index] = Json::Value{}; jv[sfRawTransactions.jsonName][index][jss::RawTransaction] = tx; jv[sfRawTransactions.jsonName][index][jss::RawTransaction] - [jss::SigningPubKey] = strHex(account.pk()); + [jss::SigningPubKey] = strHex(pk); jv[sfRawTransactions.jsonName][index][jss::RawTransaction] [sfFee.jsonName] = 0; jv[sfRawTransactions.jsonName][index][jss::RawTransaction] @@ -143,11 +156,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 1, seq, 0); + jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq, 1); + jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); env(jv, fee(feeDrops * 2), @@ -158,12 +171,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "FE01269C9BABCE17758CEF4DA45BDB529DDA0105FD2360BE00316345637E1" - "88D"}, + "BC50DFE508E921460443261F46383A68610BB929F78030EA700E373654187851"}, {"tesSUCCESS", "Payment", - "591CF8801EA7B0465DBF309D2B6D103D5E5926203A10F5A433A704C29C1D3" - "26F"}, + "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBAD"}, }}; Json::Value params; @@ -171,9 +182,9 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; - auto const meta = jrr[jss::result][jss::metaData]; - validateBatchTxns(meta, testCases); + std::cout << jrr << std::endl; + auto const txn = getTxByIndex(jrr, 2); + validateBatchTxns(txn[jss::metaData], testCases); BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT( @@ -206,32 +217,18 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 1, seq, 0); + jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx2, alice, 2, seq, 1); + jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), - ter(tesSUCCESS)); + ter(tecBATCH_FAILURE)); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", "Payment", ""}, - {"tecUNFUNDED_PAYMENT", "Payment", ""}, - }}; - - Json::Value params; - params[jss::ledger_index] = env.current()->seq() - 1; - params[jss::transactions] = true; - params[jss::expand] = true; - auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; - auto const meta = jrr[jss::result][jss::metaData]; - validateBatchTxns(meta, testCases); - BEAST_EXPECT(env.seq(alice) == 5); BEAST_EXPECT(env.balance(alice) == preAlice - (feeDrops * 2)); BEAST_EXPECT(env.balance(bob) == preBob); @@ -269,25 +266,24 @@ class Batch_test : public beast::unit_test::suite // Tx 2 Json::Value const tx1 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx1, alice, 1, seq, 0); + jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq, 1); + jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx3, alice, 3, seq, 2); + jv = addBatchTx(jv, tx3, alice.pk(), alice, 2, seq, 2); env(jv, fee(feeDrops * 3), txflags(tfOnlyOne), ter(tesSUCCESS)); env.close(); std::vector testCases = {{ - {"tecUNFUNDED_PAYMENT", "Payment", ""}, + {"tecUNFUNDED_PAYMENT", "Payment", "32B90ABCD36E4601196708F5C93568BA49BE6F1221D58703EAFFB568FAC9807E"}, {"tesSUCCESS", "Payment", - "591CF8801EA7B0465DBF309D2B6D103D5E5926203A10F5A433A704C29C1D326" - "F"}, + "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBAD"}, }}; Json::Value params; @@ -296,8 +292,8 @@ class Batch_test : public beast::unit_test::suite params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; - auto const meta = jrr[jss::result][jss::metaData]; - validateBatchTxns(meta, testCases); + auto const txn = getTxByIndex(jrr, 2); + validateBatchTxns(txn[jss::metaData], testCases); BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - (feeDrops * 3)); @@ -335,19 +331,19 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 1, seq, 0); + jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq, 1); + jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx3, alice, 3, seq, 2); + jv = addBatchTx(jv, tx3, alice.pk(), alice, 2, seq, 2); // Tx 4 Json::Value const tx4 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx4, alice, 4, seq, 3); + jv = addBatchTx(jv, tx4, alice.pk(), alice, 3, seq, 3); env(jv, fee(feeDrops * 4), txflags(tfUntilFailure), ter(tesSUCCESS)); env.close(); @@ -355,13 +351,16 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "FE01269C9BABCE17758CEF4DA45BDB529DDA0105FD2360BE00316345637E188" - "D"}, + "BC50DFE508E921460443261F46383A68610BB929F78030EA700E37365418785" + "1"}, {"tesSUCCESS", "Payment", - "591CF8801EA7B0465DBF309D2B6D103D5E5926203A10F5A433A704C29C1D326" - "F"}, - {"tecUNFUNDED_PAYMENT", "Payment", ""}, + "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBA" + "D"}, + {"tecUNFUNDED_PAYMENT", + "Payment", + "5B3B8F9557B38CABE92B8A420C36508711B921B912C391E7029836CD1BAE0BB" + "6"}, }}; Json::Value params; @@ -370,8 +369,8 @@ class Batch_test : public beast::unit_test::suite params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; - auto const meta = jrr[jss::result][jss::metaData]; - validateBatchTxns(meta, testCases); + auto const txn = getTxByIndex(jrr, 3); + validateBatchTxns(txn[jss::metaData], testCases); BEAST_EXPECT(env.seq(alice) == 8); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - (feeDrops * 4)); @@ -409,19 +408,19 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice, 1, seq, 0); + jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq, 1); + jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx3, alice, 3, seq, 2); + jv = addBatchTx(jv, tx3, alice.pk(), alice, 2, seq, 2); // Tx 4 Json::Value const tx4 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx4, alice, 4, seq, 3); + jv = addBatchTx(jv, tx4, alice.pk(), alice, 3, seq, 3); env(jv, fee(feeDrops * 4), txflags(tfIndependent), ter(tesSUCCESS)); env.close(); @@ -429,17 +428,16 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "FE01269C9BABCE17758CEF4DA45BDB529DDA0105FD2360BE00316345637E188" - "D"}, + "BC50DFE508E921460443261F46383A68610BB929F78030EA700E373654187851"}, {"tesSUCCESS", "Payment", - "591CF8801EA7B0465DBF309D2B6D103D5E5926203A10F5A433A704C29C1D326" - "F"}, - {"tecUNFUNDED_PAYMENT", "Payment", ""}, + "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBAD"}, + {"tecUNFUNDED_PAYMENT", + "Payment", + "5B3B8F9557B38CABE92B8A420C36508711B921B912C391E7029836CD1BAE0BB6"}, {"tesSUCCESS", "Payment", - "963BCD15F8CC7D6FB3D3154324CDF6CFBEF6A230496676D58DB92109E4A9F1C" - "8"}, + "FCB23E17BDCCC55DC354EF8F6D3D1E7E39E705ADF65106D005731736B190A1EC"}, }}; Json::Value params; @@ -448,8 +446,8 @@ class Batch_test : public beast::unit_test::suite params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; - auto const meta = jrr[jss::result][jss::metaData]; - validateBatchTxns(meta, testCases); + auto const txn = getTxByIndex(jrr, 4); + validateBatchTxns(txn[jss::metaData], testCases); BEAST_EXPECT(env.seq(alice) == 9); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - (feeDrops * 4)); @@ -509,11 +507,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, USD(10)); - jv = addBatchTx(jv, tx1, alice, 1, env.seq(alice), 0); + jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, env.seq(alice), 0); // Tx 2 Json::Value const tx2 = pay(bob, alice, USD(5)); - jv = addBatchTx(jv, tx2, bob, 0, env.seq(bob), 1); + jv = addBatchTx(jv, tx2, bob.pk(), alice, 0, env.seq(bob), 1); jv = addBatchSignatures(jv, signers); @@ -524,12 +522,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "319131912A291734CCF2766390B6010E1C63D2916011EE6A154B6F210BE43A7" - "2"}, + "319131912A291734CCF2766390B6010E1C63D2916011EE6A154B6F210BE43A72"}, {"tesSUCCESS", "Payment", - "DF91E311E37F7670DBB31E98AB6C309555B5B0B20A1DBADFAB2BAC8E4DC8E27" - "0"}, + "CBA8ADE2945A4DC41E9E979CE274630E38B606ECB5922366FF77539FE3D01CCD"}, }}; Json::Value params; @@ -538,8 +534,8 @@ class Batch_test : public beast::unit_test::suite params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; - auto const meta = jrr[jss::result][jss::metaData]; - validateBatchTxns(meta, testCases); + auto const txn = getTxByIndex(jrr, 2); + validateBatchTxns(txn[jss::metaData], testCases); BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.seq(bob) == 7); @@ -580,11 +576,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = fset(alice, asfRequireAuth); - jv = addBatchTx(jv, tx1, alice, 1, seq, 0); + jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq, 1); + jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); env.close(); @@ -592,12 +588,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "AccountSet", - "26F8C5399D4F40DEC5051F57CFBCE27F4A6EB3E013332C05748E7C5450FE074" - "4"}, + "21D36FEF5EE86F7B4B8609661E138AB166780745F48A94981FB9BBFFF94E780C"}, {"tesSUCCESS", "Payment", - "591CF8801EA7B0465DBF309D2B6D103D5E5926203A10F5A433A704C29C1D3" - "26F"}, + "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBAD"}, }}; Json::Value params; @@ -606,8 +600,8 @@ class Batch_test : public beast::unit_test::suite params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; - auto const meta = jrr[jss::result][jss::metaData]; - validateBatchTxns(meta, testCases); + auto const txn = getTxByIndex(jrr, 2); + validateBatchTxns(txn[jss::metaData], testCases); BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - (feeDrops * 2)); @@ -655,14 +649,14 @@ class Batch_test : public beast::unit_test::suite // bTx 1 Json::Value const btx1 = pay(alice, bob, XRP(1)); - btx = addBatchTx(btx, btx1, alice, 0, seq, 0); + btx = addBatchTx(btx, btx1, alice.pk(), alice, 0, seq, 0); } - jv = addBatchTx(jv, btx, alice, 1, seq, 0); + jv = addBatchTx(jv, btx, alice.pk(), alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 2, seq, 1); + jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(temMALFORMED)); env.close(); @@ -705,11 +699,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = check::create(alice, bob, XRP(10)); - jv = addBatchTx(jv, tx1, alice, 0, seq, 0); + jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice, 1, seq, 1); + jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); env.close(); @@ -717,12 +711,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "CheckCreate", - "26F8C5399D4F40DEC5051F57CFBCE27F4A6EB3E013332C05748E7C5450FE074" - "4"}, + "80229431EBDCF659E1EE1B6E5DC94B195B2E52E81D88663224EC8C06F301FF1F"}, {"tesSUCCESS", "Payment", - "591CF8801EA7B0465DBF309D2B6D103D5E5926203A10F5A433A704C29C1D3" - "26F"}, + "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBAD"}, }}; Json::Value params; @@ -730,8 +722,9 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - auto const meta = jrr[jss::result][jss::metaData]; - validateBatchTxns(meta, testCases); + // std::cout << jrr << std::endl; + auto const txn = getTxByIndex(jrr, 2); + validateBatchTxns(txn[jss::metaData], testCases); BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - (feeDrops * 2)); @@ -747,9 +740,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"); @@ -791,11 +781,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 uint256 const chkId{getCheckIndex(alice, seq + 1)}; Json::Value tx1 = check::create(alice, bob, USD(10)); - jv = addBatchTx(jv, tx1, alice, 1, env.seq(alice), 0); + jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, env.seq(alice), 0); // Tx 2 Json::Value const tx2 = check::cash(bob, chkId, USD(10)); - jv = addBatchTx(jv, tx2, bob, 0, env.seq(bob), 1); + jv = addBatchTx(jv, tx2, bob.pk(), alice, 0, env.seq(bob), 1); jv = addBatchSignatures(jv, signers); @@ -805,12 +795,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "CheckCreate", - "145CBCD0C29955E43452EF891979DF63CB9D32CDB6F91FEAEE402D39FC53845" - "5"}, + "CE920705201AC0E4B3C50E52170BF1ED31E90457CAC4E79A0E1C1AE82BA5CC46"}, {"tesSUCCESS", "CheckCash", - "10C5B1A9861230AD0BC9BA9FD957944B2A06F5A5C888557586D96EE58DD5861" - "8"}, + "165AE829F04BD375DDC3A7F57386B2B2A9386211839C005E1481A4E4282F625F"}, }}; Json::Value params; @@ -819,8 +807,8 @@ class Batch_test : public beast::unit_test::suite params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; - auto const meta = jrr[jss::result][jss::metaData]; - validateBatchTxns(meta, testCases); + auto const txn = getTxByIndex(jrr, 2); + validateBatchTxns(txn[jss::metaData], testCases); BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.seq(bob) == 6); @@ -880,11 +868,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 uint256 const chkId{getCheckIndex(alice, seq + 1)}; Json::Value tx1 = check::create(alice, bob, XRP(10)); - jv = addBatchTx(jv, tx1, alice, 1, env.seq(alice), 0); + jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, env.seq(alice), 0); // Tx 2 Json::Value const tx2 = check::cancel(bob, chkId); - jv = addBatchTx(jv, tx2, bob, 0, env.seq(bob), 1); + jv = addBatchTx(jv, tx2, bob.pk(), alice, 0, env.seq(bob), 1); jv = addBatchSignatures(jv, signers); @@ -894,12 +882,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "CheckCreate", - "145CBCD0C29955E43452EF891979DF63CB9D32CDB6F91FEAEE402D39FC53845" - "5"}, + "3C33B38C8804F0FF01BEADE2CFD665C028A53A4220E0D08A73EB7CBBB82D59AC"}, {"tesSUCCESS", "CheckCancel", - "10C5B1A9861230AD0BC9BA9FD957944B2A06F5A5C888557586D96EE58DD5861" - "8"}, + "BFED069E7ECDFD313BDAC1A985C9F05EFD2FBBF63D6C42E8DDEF09087C4D233A"}, }}; Json::Value params; @@ -908,8 +894,8 @@ class Batch_test : public beast::unit_test::suite params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; - auto const meta = jrr[jss::result][jss::metaData]; - validateBatchTxns(meta, testCases); + auto const txn = getTxByIndex(jrr, 2); + validateBatchTxns(txn[jss::metaData], testCases); BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.seq(bob) == 6); @@ -960,11 +946,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = claw(gw, bob["USD"](10)); - jv = addBatchTx(jv, tx1, gw, 1, seq, 0); + jv = addBatchTx(jv, tx1, gw.pk(), gw, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(gw, bob, XRP(1)); - jv = addBatchTx(jv, tx2, gw, 2, seq, 1); + jv = addBatchTx(jv, tx2, gw.pk(), gw, 1, seq, 1); env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); env.close(); @@ -972,12 +958,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Clawback", - "D08330FED53B479F2949DF85717101ED513B046B06B748BDF19F5951A81DAAE" - "2"}, + "D08330FED53B479F2949DF85717101ED513B046B06B748BDF19F5951A81DAAE2"}, {"tesSUCCESS", "Payment", - "897B243D48B813D249F8A1353FC3E537DDCC5BD0139CF2670D0FECD435AB1A6" - "6"}, + "FEB6D7EE4BE48851B1CE9B31DB39A5A4FDFBB59DFFE1A49146FD6D9177C1ECC6"}, }}; Json::Value params; @@ -985,8 +969,9 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - auto const meta = jrr[jss::result][jss::metaData]; - validateBatchTxns(meta, testCases); + // std::cout << jrr << std::endl; + auto const txn = getTxByIndex(jrr, 2); + validateBatchTxns(txn[jss::metaData], testCases); BEAST_EXPECT(env.seq(gw) == 10); BEAST_EXPECT(env.balance(gw) == preGw - XRP(1) - (feeDrops * 2)); @@ -994,14 +979,17 @@ class Batch_test : public beast::unit_test::suite } void - testOffer(FeatureBitset features) + testOfferCancel(FeatureBitset features) { - testcase("offer"); + testcase("offer cancel"); using namespace test::jtx; 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"); @@ -1031,11 +1019,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = offer(alice, XRP(50), USD(50)); - jv = addBatchTx(jv, tx1, alice, 0, seq, 0); + jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); // Tx 2 - Json::Value const tx2 = offer_cancel(alice, seq + 0 + 1); - jv = addBatchTx(jv, tx2, alice, 1, seq, 1); + Json::Value const tx2 = offer_cancel(alice, seq + 1); + jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); env.close(); @@ -1043,12 +1031,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "OfferCreate", - "145CBCD0C29955E43452EF891979DF63CB9D32CDB6F91FEAEE402D39FC53845" - "5"}, + "145CBCD0C29955E43452EF891979DF63CB9D32CDB6F91FEAEE402D39FC538455"}, {"tesSUCCESS", "OfferCancel", - "10C5B1A9861230AD0BC9BA9FD957944B2A06F5A5C888557586D96EE58DD5861" - "8"}, + "10C5B1A9861230AD0BC9BA9FD957944B2A06F5A5C888557586D96EE58DD58618"}, }}; Json::Value params; @@ -1056,14 +1042,61 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - auto const meta = jrr[jss::result][jss::ledger][jss::metaData]; - validateBatchTxns(meta, testCases); + // std::cout << jrr << std::endl; + auto const txn = getTxByIndex(jrr, 3); + validateBatchTxns(txn[jss::metaData], testCases); - BEAST_EXPECT(env.seq(alice) == 8); + BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.balance(alice) == preAlice - (feeDrops * 2)); BEAST_EXPECT(env.balance(bob) == preBob); } + void + testSubmit(FeatureBitset features) + { + testcase("submit"); + + using namespace test::jtx; + 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"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + + env.trust(USD(1000), alice, bob); + env(pay(gw, alice, USD(100))); + env(pay(gw, bob, USD(100))); + env.close(); + + auto jv = pay(alice, bob, USD(1)); + jv[sfBatchTxn.jsonName] = Json::Value{}; + jv[sfBatchTxn.jsonName][jss::Account] = alice.human(); + jv[sfBatchTxn.jsonName][sfOuterSequence.jsonName] = 0; + jv[sfBatchTxn.jsonName][sfBatchIndex.jsonName] = 0; + + Serializer s; + auto jt = env.jt(jv); + s.erase(); + jt.stx->add(s); + auto const jr = env.rpc("submit", strHex(s.slice())); + BEAST_EXPECT( + jr.isMember(jss::engine_result) && + jr[jss::engine_result] == "tesSUCCESS"); + + env.close(); + std::cout << jr << std::endl; + } + void testWithFeats(FeatureBitset features) { @@ -1079,9 +1112,14 @@ class Batch_test : public beast::unit_test::suite testCheckCash(features); testCheckCancel(features); testClawback(features); - // testOffer(features); + // testOfferCancel(features); + + // testSubmit(features); // Test Fork From one node having 1 extra txn + + // Multisign Atomic + // If the 2nd fails and needs the 3rd } public: diff --git a/src/xrpld/app/tx/detail/ApplyContext.cpp b/src/xrpld/app/tx/detail/ApplyContext.cpp index 969af7960eb..86277d1f073 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.cpp +++ b/src/xrpld/app/tx/detail/ApplyContext.cpp @@ -59,6 +59,47 @@ ApplyContext::apply(TER ter) view_->apply(base_, tx, ter, journal); } +/** + * Applies the changes in the given OpenView to the ApplyContext's base. + * If the base is not open, the changes in the OpenView are directly applied to + * the base. + * + * @param open The OpenView containing the changes to be applied. + */ +void +ApplyContext::applyOpenView(OpenView& open) +{ + if (!base_.open()) + open.apply(base_); +} + +/** + * Applies the fee for the transaction. + * + * This function retrieves the account ID from the transaction and reads the + * corresponding account state from the base ledger. It then updates the balance + * field of the account state with the balance from the base ledger and updates + * the account state in the current view. + * + * @note This function assumes that both the account state in the base ledger + * and the current view exist. If either of them is missing, the function does + * not perform any updates. + */ +void +ApplyContext::applyFee() +{ + AccountID const account = tx.getAccountID(sfAccount); + auto const sleBase = base_.read(keylet::account(account)); + auto const sle = view_->peek(keylet::account(account)); + assert(sle != nullptr || sleBase != nullptr || account == beast::zero); + if (sle && sleBase) + { + auto const feePaid = tx[sfFee].xrp(); + sle->setFieldAmount(sfBalance, (*sleBase)[sfBalance].xrp()); + view_->update(sle); + } +} + std::size_t ApplyContext::size() { diff --git a/src/xrpld/app/tx/detail/ApplyContext.h b/src/xrpld/app/tx/detail/ApplyContext.h index bca0a40bcae..b5cd7fd5c3a 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.h +++ b/src/xrpld/app/tx/detail/ApplyContext.h @@ -49,7 +49,6 @@ class ApplyContext TER const preclaimResult; XRPAmount const baseFee; beast::Journal const journal; - OpenView& base_; ApplyView& view() @@ -83,6 +82,12 @@ class ApplyContext /** Apply the transaction result to the base. */ void apply(TER); + + /** Apply the transaction result to the base. */ + void applyOpenView(OpenView& open); + + /** Apply the fee to the account. */ + void applyFee(); /** Get the number of unapplied changes. */ std::size_t @@ -122,7 +127,7 @@ class ApplyContext XRPAmount const fee, std::index_sequence); - // OpenView& base_; + OpenView& base_; ApplyFlags flags_; std::optional view_; }; diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 15ca97de82b..5b0d2c665bf 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -17,280 +17,18 @@ */ //============================================================================== -#include -#include -#include -#include -#include -#include -#include -#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include #include #include #include #include +#include namespace ripple { -namespace { - -struct UnknownTxnType : std::exception -{ - TxType txnType; - UnknownTxnType(TxType t) : txnType{t} - { - } -}; - -// Call a lambda with the concrete transaction type as a template parameter -// throw an "UnknownTxnType" exception on error -template -auto -with_txn_type(TxType txnType, F&& f) -{ - switch (txnType) - { - case ttACCOUNT_DELETE: - return f.template operator()(); - case ttACCOUNT_SET: - return f.template operator()(); - case ttCHECK_CANCEL: - return f.template operator()(); - case ttCHECK_CASH: - return f.template operator()(); - case ttCHECK_CREATE: - return f.template operator()(); - case ttDEPOSIT_PREAUTH: - return f.template operator()(); - case ttOFFER_CANCEL: - return f.template operator()(); - case ttOFFER_CREATE: - return f.template operator()(); - case ttESCROW_CREATE: - return f.template operator()(); - case ttESCROW_FINISH: - return f.template operator()(); - case ttESCROW_CANCEL: - return f.template operator()(); - case ttPAYCHAN_CLAIM: - return f.template operator()(); - case ttPAYCHAN_CREATE: - return f.template operator()(); - case ttPAYCHAN_FUND: - return f.template operator()(); - case ttPAYMENT: - return f.template operator()(); - case ttREGULAR_KEY_SET: - return f.template operator()(); - case ttSIGNER_LIST_SET: - return f.template operator()(); - case ttTICKET_CREATE: - return f.template operator()(); - case ttTRUST_SET: - return f.template operator()(); - case ttAMENDMENT: - case ttFEE: - case ttUNL_MODIFY: - return f.template operator()(); - case ttNFTOKEN_MINT: - return f.template operator()(); - case ttNFTOKEN_BURN: - return f.template operator()(); - case ttNFTOKEN_CREATE_OFFER: - return f.template operator()(); - case ttNFTOKEN_CANCEL_OFFER: - return f.template operator()(); - case ttNFTOKEN_ACCEPT_OFFER: - return f.template operator()(); - case ttCLAWBACK: - return f.template operator()(); - case ttAMM_CREATE: - return f.template operator()(); - case ttAMM_DEPOSIT: - return f.template operator()(); - case ttAMM_WITHDRAW: - return f.template operator()(); - case ttAMM_VOTE: - return f.template operator()(); - case ttAMM_BID: - return f.template operator()(); - case ttAMM_DELETE: - return f.template operator()(); - case ttXCHAIN_CREATE_BRIDGE: - return f.template operator()(); - case ttXCHAIN_MODIFY_BRIDGE: - return f.template operator()(); - case ttXCHAIN_CREATE_CLAIM_ID: - return f.template operator()(); - case ttXCHAIN_COMMIT: - return f.template operator()(); - case ttXCHAIN_CLAIM: - return f.template operator()(); - case ttXCHAIN_ADD_CLAIM_ATTESTATION: - return f.template operator()(); - case ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION: - return f.template operator()(); - case ttXCHAIN_ACCOUNT_CREATE_COMMIT: - return f.template operator()(); - case ttDID_SET: - return f.template operator()(); - case ttDID_DELETE: - return f.template operator()(); - case ttBATCH: - return f.template operator()(); - default: - throw UnknownTxnType(txnType); - } -} -} // namespace - -// clang-format off -// Current formatter for rippled is based on clang-10, which does not handle `requires` clauses -template -requires(T::ConsequencesFactory == Transactor::Normal) -TxConsequences - consequences_helper(PreflightContext const& ctx) -{ - return TxConsequences(ctx.tx); -}; - -// For Transactor::Blocker -template -requires(T::ConsequencesFactory == Transactor::Blocker) -TxConsequences - consequences_helper(PreflightContext const& ctx) -{ - return TxConsequences(ctx.tx, TxConsequences::blocker); -}; - -// For Transactor::Custom -template -requires(T::ConsequencesFactory == Transactor::Custom) -TxConsequences - consequences_helper(PreflightContext const& ctx) -{ - return T::makeTxConsequences(ctx); -}; -// clang-format on - -static std::pair -invoke_preflight(PreflightContext const& ctx) -{ - try - { - return with_txn_type(ctx.tx.getTxnType(), [&]() { - auto const tec = T::preflight(ctx); - return std::make_pair( - tec, - isTesSuccess(tec) ? consequences_helper(ctx) - : TxConsequences{tec}); - }); - } - catch (UnknownTxnType const& e) - { - // Should never happen - JLOG(ctx.j.fatal()) - << "Unknown transaction type in preflight: " << e.txnType; - assert(false); - return {temUNKNOWN, TxConsequences{temUNKNOWN}}; - } -} - -static TER -invoke_preclaim(PreclaimContext const& ctx) -{ - try - { - // use name hiding to accomplish compile-time polymorphism of static - // class functions for Transactor and derived classes. - return with_txn_type(ctx.tx.getTxnType(), [&]() { - // If the transactor requires a valid account and the transaction - // doesn't list one, preflight will have already a flagged a - // failure. - auto const id = ctx.tx.getAccountID(sfAccount); - - if (id != beast::zero) - { - TER result = T::checkSeqProxy(ctx.view, ctx.tx, ctx.j); - - if (result != tesSUCCESS) - return result; - - result = T::checkPriorTxAndLastLedger(ctx); - - if (result != tesSUCCESS) - return result; - - result = T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx)); - - if (result != tesSUCCESS) - return result; - - result = T::checkSign(ctx); - - if (result != tesSUCCESS) - return result; - } - - return T::preclaim(ctx); - }); - } - catch (UnknownTxnType const& e) - { - // Should never happen - JLOG(ctx.j.fatal()) - << "Unknown transaction type in preclaim: " << e.txnType; - assert(false); - return temUNKNOWN; - } -} - -static std::pair -invoke_apply(ApplyContext& ctx) -{ - try - { - return with_txn_type(ctx.tx.getTxnType(), [&]() { - T p(ctx); - return p(); - }); - } - catch (UnknownTxnType const& e) - { - // Should never happen - JLOG(ctx.journal.fatal()) - << "Unknown transaction type in apply: " << e.txnType; - assert(false); - return {temUNKNOWN, false}; - } -} - TxConsequences Batch::makeTxConsequences(PreflightContext const& ctx) { @@ -348,7 +86,6 @@ Batch::preflight(PreflightContext const& ctx) return temBAD_SIGNER; } } - return preflight2(ctx); } @@ -377,133 +114,90 @@ TER Batch::doApply() { Sandbox sb(&ctx_.view()); + bool changed = false; uint32_t flags = ctx_.tx.getFlags(); if (flags & tfBatchMask) return temINVALID_FLAG; - ApplyViewImpl& avi = dynamic_cast(ctx_.view()); TER result = tesSUCCESS; + ApplyViewImpl& avi = dynamic_cast(ctx_.view()); + OpenView subView(&sb); std::map accountCount; + auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions); for (STObject txn : txns) { STTx const stx = STTx{std::move(txn)}; + auto const [ter, applied] = ripple::apply( + ctx_.app, subView, stx, tapFAIL_HARD, ctx_.journal); - // preflight - PreflightContext const pfctx( - ctx_.app, - stx, - ctx_.view().rules(), - tapPREFLIGHT_BATCH, - ctx_.journal); - auto const preflightResult = - PreflightResult{pfctx, invoke_preflight(pfctx)}; - - // preclaim - std::optional pcctx; - pcctx.emplace( - ctx_.app, - ctx_.base_, - preflightResult.ter, - preflightResult.tx, - preflightResult.flags, - preflightResult.j); - auto const preclaimResult = - PreclaimResult{*pcctx, invoke_preclaim(*pcctx)}; - - // doApply - ApplyContext actx( - ctx_.app, - ctx_.base_, - preclaimResult.tx, - preclaimResult.ter, - calculateBaseFee(ctx_.base_, preclaimResult.tx), - ctx_.base_.open() == 1 ? tapPREFLIGHT_BATCH : ctx_.view().flags(), - preclaimResult.j); - auto const applyResult = invoke_apply(actx); + changed = true; + // Add Inner Txn Metadata STObject meta{sfBatchExecution}; - meta.setFieldU8(sfTransactionResult, TERtoInt(applyResult.first)); + meta.setFieldU8(sfTransactionResult, TERtoInt(ter)); meta.setFieldU16(sfTransactionType, stx.getTxnType()); - if (applyResult.first == tesSUCCESS) + if (ter == tesSUCCESS || isTecClaim(ter)) meta.setFieldH256(sfTransactionHash, stx.getTransactionID()); - avi.addBatchExecutionMetaData(std::move(meta)); + // Update Account:Count Map accountCount[stx.getAccountID(sfAccount)] += 1; - if (applyResult.first != tesSUCCESS) + if (ter != tesSUCCESS) { if (flags & tfUntilFailure) { - actx.discard(); result = tesSUCCESS; break; } if (flags & tfOnlyOne) { - actx.discard(); continue; } if (flags & tfAllOrNothing) { accountCount.clear(); - ctx_.base_.rawRevert(); std::vector batch; avi.setHookMetaData(std::move(batch)); - result = tesSUCCESS; + result = tecBATCH_FAILURE; + changed = false; break; } } - if (applyResult.first == tesSUCCESS && flags & tfOnlyOne) + if (ter == tesSUCCESS && flags & tfOnlyOne) { result = tesSUCCESS; break; } } + // Apply SubView + if (changed) { - auto const sleBase = ctx_.base_.read(keylet::account(account_)); - if (!sleBase) - return tefINTERNAL; - - auto const sleSrcAcc = sb.peek(keylet::account(account_)); - if (!sleSrcAcc) - return tefINTERNAL; - - // Update Fee (Source Account) - auto const feePaid = ctx_.tx[sfFee].xrp(); - sleSrcAcc->setFieldAmount( - sfBalance, sleBase->getFieldAmount(sfBalance).xrp() - feePaid); - - // Update Sequence (Source Account) - sleSrcAcc->setFieldU32( - sfSequence, - sleSrcAcc->getFieldU32(sfSequence) + accountCount[account_]); - sb.update(sleSrcAcc); + ctx_.applyOpenView(subView); } - // Update Sequence (Other Inner Accounts) for (auto const& [_account, count] : accountCount) { + auto const sleSrcAcc = sb.peek(keylet::account(_account)); + if (!sleSrcAcc) + return tefINTERNAL; + if (_account == account_) { - // pass - } - else - { - auto const _sleSrcAcc = sb.peek(keylet::account(_account)); - if (!_sleSrcAcc) - return tefINTERNAL; - - _sleSrcAcc->setFieldU32( - sfSequence, _sleSrcAcc->getFieldU32(sfSequence) + count); - sb.update(_sleSrcAcc); + // Update Sequence (Source Account) + sleSrcAcc->setFieldU32( + sfSequence, + sleSrcAcc->getFieldU32(sfSequence) + count); + sb.update(sleSrcAcc); } } + sb.apply(ctx_.rawView()); + return result; } diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 584396909c8..ea6e0f61a38 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -337,7 +337,10 @@ Transactor::checkSeqProxy( std::uint32_t const startSequence{ batchTxn.getFieldU32(sfOuterSequence)}; std::uint32_t const batchIndex{batchTxn.getFieldU8(sfBatchIndex)}; - a_seq = SeqProxy::sequence(startSequence + batchIndex); + std::uint8_t const sourceAdj = + tx.getAccountID(sfAccount) == batchTxn.getAccountID(sfAccount) ? 1 + : 0; + a_seq = SeqProxy::sequence(startSequence + batchIndex + sourceAdj); } if (t_seqProx.isSeq()) @@ -428,10 +431,6 @@ Transactor::consumeSeqProxy(SLE::pointer const& sleAccount) SeqProxy const seqProx = ctx_.tx.getSeqProxy(); if (seqProx.isSeq()) { - // do not update sequence of sfAccountTxnID for batch tx - if (ctx_.tx.isFieldPresent(sfBatchTxn)) - return tesSUCCESS; - // Note that if this transaction is a TicketCreate, then // the transaction will modify the account root sfSequence // yet again. @@ -938,8 +937,8 @@ Transactor::operator()() if (ctx_.size() > oversizeMetaDataCap) result = tecOVERSIZE; - if ((isTecClaim(result) && (view().flags() & tapFAIL_HARD)) || - view().flags() & tapPREFLIGHT_BATCH) + if ((isTecClaim(result) && (view().flags() & tapFAIL_HARD) && + !ctx_.tx.isFieldPresent(sfBatchTxn))) { // If the tapFAIL_HARD flag is set, a tec result // must not do anything @@ -1028,6 +1027,15 @@ Transactor::operator()() applied = isTecClaim(result); } + // Apply fee for batch transaction if it was successfully applied + if (applied && ctx_.tx.getTxnType() == ttBATCH && result == tesSUCCESS) + { + // If the transaction is a batch transaction, the fee is already + // deducted from the account balance before executing the inner txns. + // So, we need to "re" apply the fee again. + ctx_.applyFee(); + } + if (applied) { // Check invariants: if `tecINVARIANT_FAILED` is not returned, we can @@ -1074,6 +1082,7 @@ Transactor::operator()() if (!view().open() && fee != beast::zero) ctx_.destroyXRP(fee); + // Once we call apply, we will no longer be able to look at view() ctx_.apply(result); } diff --git a/src/xrpld/app/tx/detail/apply.cpp b/src/xrpld/app/tx/detail/apply.cpp index 3c1398264da..89f8cdeee73 100644 --- a/src/xrpld/app/tx/detail/apply.cpp +++ b/src/xrpld/app/tx/detail/apply.cpp @@ -43,8 +43,9 @@ checkValidity( { auto const id = tx.getTransactionID(); auto const flags = router.getFlags(id); - - if (rules.enabled(featureBatch) && applyFlags & tapPREFLIGHT_BATCH) + + // Validate Inner BatchTxn + if (rules.enabled(featureBatch) && tx.isFieldPresent(sfBatchTxn)) { // batched transactions do not contain signatures if (tx.isFieldPresent(sfTxnSignature)) diff --git a/src/xrpld/ledger/ApplyView.h b/src/xrpld/ledger/ApplyView.h index 7cc0504476d..f0166cd0b38 100644 --- a/src/xrpld/ledger/ApplyView.h +++ b/src/xrpld/ledger/ApplyView.h @@ -39,9 +39,6 @@ enum ApplyFlags : std::uint32_t { // Transaction came from a privileged source tapUNLIMITED = 0x400, - - // Transaction is being tested against preflight before emission - tapPREFLIGHT_BATCH = 0x800, }; constexpr ApplyFlags From a3e15a189663a8d1f2d57b46e8af3016e19318e4 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 3 Aug 2024 12:40:53 +0200 Subject: [PATCH 22/71] clang-format --- src/test/app/Batch_test.cpp | 77 +++++++++++++++++--------- src/xrpld/app/tx/detail/ApplyContext.h | 10 ++-- src/xrpld/app/tx/detail/Batch.cpp | 13 ++--- src/xrpld/app/tx/detail/Transactor.cpp | 1 - src/xrpld/app/tx/detail/apply.cpp | 2 +- 5 files changed, 63 insertions(+), 40 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index c355547d7da..5f42d8e94ad 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -171,10 +171,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "BC50DFE508E921460443261F46383A68610BB929F78030EA700E373654187851"}, + "BC50DFE508E921460443261F46383A68610BB929F78030EA700E373654187" + "851"}, {"tesSUCCESS", "Payment", - "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBAD"}, + "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BD" + "BAD"}, }}; Json::Value params; @@ -280,10 +282,14 @@ class Batch_test : public beast::unit_test::suite env.close(); std::vector testCases = {{ - {"tecUNFUNDED_PAYMENT", "Payment", "32B90ABCD36E4601196708F5C93568BA49BE6F1221D58703EAFFB568FAC9807E"}, + {"tecUNFUNDED_PAYMENT", + "Payment", + "32B90ABCD36E4601196708F5C93568BA49BE6F1221D58703EAFFB568FAC9807" + "E"}, {"tesSUCCESS", "Payment", - "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBAD"}, + "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBA" + "D"}, }}; Json::Value params; @@ -428,16 +434,20 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "BC50DFE508E921460443261F46383A68610BB929F78030EA700E373654187851"}, + "BC50DFE508E921460443261F46383A68610BB929F78030EA700E37365418785" + "1"}, {"tesSUCCESS", "Payment", - "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBAD"}, + "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBA" + "D"}, {"tecUNFUNDED_PAYMENT", "Payment", - "5B3B8F9557B38CABE92B8A420C36508711B921B912C391E7029836CD1BAE0BB6"}, + "5B3B8F9557B38CABE92B8A420C36508711B921B912C391E7029836CD1BAE0BB" + "6"}, {"tesSUCCESS", "Payment", - "FCB23E17BDCCC55DC354EF8F6D3D1E7E39E705ADF65106D005731736B190A1EC"}, + "FCB23E17BDCCC55DC354EF8F6D3D1E7E39E705ADF65106D005731736B190A1E" + "C"}, }}; Json::Value params; @@ -522,10 +532,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "319131912A291734CCF2766390B6010E1C63D2916011EE6A154B6F210BE43A72"}, + "319131912A291734CCF2766390B6010E1C63D2916011EE6A154B6F210BE43A7" + "2"}, {"tesSUCCESS", "Payment", - "CBA8ADE2945A4DC41E9E979CE274630E38B606ECB5922366FF77539FE3D01CCD"}, + "CBA8ADE2945A4DC41E9E979CE274630E38B606ECB5922366FF77539FE3D01CC" + "D"}, }}; Json::Value params; @@ -588,10 +600,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "AccountSet", - "21D36FEF5EE86F7B4B8609661E138AB166780745F48A94981FB9BBFFF94E780C"}, + "21D36FEF5EE86F7B4B8609661E138AB166780745F48A94981FB9BBFFF94E780" + "C"}, {"tesSUCCESS", "Payment", - "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBAD"}, + "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBA" + "D"}, }}; Json::Value params; @@ -711,10 +725,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "CheckCreate", - "80229431EBDCF659E1EE1B6E5DC94B195B2E52E81D88663224EC8C06F301FF1F"}, + "80229431EBDCF659E1EE1B6E5DC94B195B2E52E81D88663224EC8C06F301FF1" + "F"}, {"tesSUCCESS", "Payment", - "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBAD"}, + "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBA" + "D"}, }}; Json::Value params; @@ -795,10 +811,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "CheckCreate", - "CE920705201AC0E4B3C50E52170BF1ED31E90457CAC4E79A0E1C1AE82BA5CC46"}, + "CE920705201AC0E4B3C50E52170BF1ED31E90457CAC4E79A0E1C1AE82BA5CC4" + "6"}, {"tesSUCCESS", "CheckCash", - "165AE829F04BD375DDC3A7F57386B2B2A9386211839C005E1481A4E4282F625F"}, + "165AE829F04BD375DDC3A7F57386B2B2A9386211839C005E1481A4E4282F625" + "F"}, }}; Json::Value params; @@ -882,10 +900,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "CheckCreate", - "3C33B38C8804F0FF01BEADE2CFD665C028A53A4220E0D08A73EB7CBBB82D59AC"}, + "3C33B38C8804F0FF01BEADE2CFD665C028A53A4220E0D08A73EB7CBBB82D59A" + "C"}, {"tesSUCCESS", "CheckCancel", - "BFED069E7ECDFD313BDAC1A985C9F05EFD2FBBF63D6C42E8DDEF09087C4D233A"}, + "BFED069E7ECDFD313BDAC1A985C9F05EFD2FBBF63D6C42E8DDEF09087C4D233" + "A"}, }}; Json::Value params; @@ -958,10 +978,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Clawback", - "D08330FED53B479F2949DF85717101ED513B046B06B748BDF19F5951A81DAAE2"}, + "D08330FED53B479F2949DF85717101ED513B046B06B748BDF19F5951A81DAAE" + "2"}, {"tesSUCCESS", "Payment", - "FEB6D7EE4BE48851B1CE9B31DB39A5A4FDFBB59DFFE1A49146FD6D9177C1ECC6"}, + "FEB6D7EE4BE48851B1CE9B31DB39A5A4FDFBB59DFFE1A49146FD6D9177C1ECC" + "6"}, }}; Json::Value params; @@ -1031,10 +1053,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "OfferCreate", - "145CBCD0C29955E43452EF891979DF63CB9D32CDB6F91FEAEE402D39FC538455"}, + "145CBCD0C29955E43452EF891979DF63CB9D32CDB6F91FEAEE402D39FC53845" + "5"}, {"tesSUCCESS", "OfferCancel", - "10C5B1A9861230AD0BC9BA9FD957944B2A06F5A5C888557586D96EE58DD58618"}, + "10C5B1A9861230AD0BC9BA9FD957944B2A06F5A5C888557586D96EE58DD5861" + "8"}, }}; Json::Value params; @@ -1060,9 +1084,8 @@ 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 - }; + Env env{ + *this, envconfig(), features, nullptr, beast::severities::kTrace}; auto const feeDrops = env.current()->fees().base; auto const alice = Account("alice"); @@ -1092,7 +1115,7 @@ class Batch_test : public beast::unit_test::suite BEAST_EXPECT( jr.isMember(jss::engine_result) && jr[jss::engine_result] == "tesSUCCESS"); - + env.close(); std::cout << jr << std::endl; } @@ -1113,7 +1136,7 @@ class Batch_test : public beast::unit_test::suite testCheckCancel(features); testClawback(features); // testOfferCancel(features); - + // testSubmit(features); // Test Fork From one node having 1 extra txn diff --git a/src/xrpld/app/tx/detail/ApplyContext.h b/src/xrpld/app/tx/detail/ApplyContext.h index b5cd7fd5c3a..21e62cd0cc4 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.h +++ b/src/xrpld/app/tx/detail/ApplyContext.h @@ -82,12 +82,14 @@ class ApplyContext /** Apply the transaction result to the base. */ void apply(TER); - + /** Apply the transaction result to the base. */ - void applyOpenView(OpenView& open); - + void + applyOpenView(OpenView& open); + /** Apply the fee to the account. */ - void applyFee(); + void + applyFee(); /** Get the number of unapplied changes. */ std::size_t diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 5b0d2c665bf..d4b5103fc65 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -129,8 +129,8 @@ Batch::doApply() for (STObject txn : txns) { STTx const stx = STTx{std::move(txn)}; - auto const [ter, applied] = ripple::apply( - ctx_.app, subView, stx, tapFAIL_HARD, ctx_.journal); + auto const [ter, applied] = + ripple::apply(ctx_.app, subView, stx, tapFAIL_HARD, ctx_.journal); changed = true; @@ -185,19 +185,18 @@ Batch::doApply() auto const sleSrcAcc = sb.peek(keylet::account(_account)); if (!sleSrcAcc) return tefINTERNAL; - + if (_account == account_) { // Update Sequence (Source Account) sleSrcAcc->setFieldU32( - sfSequence, - sleSrcAcc->getFieldU32(sfSequence) + count); + sfSequence, sleSrcAcc->getFieldU32(sfSequence) + count); sb.update(sleSrcAcc); } } - + sb.apply(ctx_.rawView()); - + return result; } diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index ea6e0f61a38..3f95e35476c 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -1082,7 +1082,6 @@ Transactor::operator()() if (!view().open() && fee != beast::zero) ctx_.destroyXRP(fee); - // Once we call apply, we will no longer be able to look at view() ctx_.apply(result); } diff --git a/src/xrpld/app/tx/detail/apply.cpp b/src/xrpld/app/tx/detail/apply.cpp index 89f8cdeee73..e7ef819e283 100644 --- a/src/xrpld/app/tx/detail/apply.cpp +++ b/src/xrpld/app/tx/detail/apply.cpp @@ -43,7 +43,7 @@ checkValidity( { auto const id = tx.getTransactionID(); auto const flags = router.getFlags(id); - + // Validate Inner BatchTxn if (rules.enabled(featureBatch) && tx.isFieldPresent(sfBatchTxn)) { From 70e993185c7bf5e77dc03729d6dc4e22d8607571 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 3 Aug 2024 13:22:43 +0200 Subject: [PATCH 23/71] [temp] invariant workaround --- src/xrpld/app/tx/detail/InvariantCheck.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index 10751d00356..5f921be1029 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -143,6 +143,12 @@ XRPNotCreated::finalize( ReadView const&, beast::Journal const& j) { + // DA TODO: FIX THIS + if (tx.getTxnType() == ttBATCH && res == tesSUCCESS) + { + drops_ = -fee.drops(); + } + // The net change should never be positive, as this would mean that the // transaction created XRP out of thin air. That's not possible. if (drops_ > 0) From c693b8fc7d8edd9dcc2106623063b9f01e2b3535 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 3 Aug 2024 14:37:08 +0200 Subject: [PATCH 24/71] update fields --- include/xrpl/protocol/SField.h | 2 +- include/xrpl/protocol/jss.h | 1 + src/libxrpl/protocol/InnerObjectFormats.cpp | 3 +- src/libxrpl/protocol/SField.cpp | 2 +- src/libxrpl/protocol/STTx.cpp | 7 +- src/test/app/Batch_test.cpp | 100 ++++++++++---------- src/xrpld/app/tx/detail/Transactor.cpp | 8 +- 7 files changed, 62 insertions(+), 61 deletions(-) diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index 4c40e6d2f77..c78a8af6bf5 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -442,7 +442,6 @@ extern SF_UINT32 const sfEmitGeneration; extern SF_UINT32 const sfVoteWeight; extern SF_UINT32 const sfFirstNFTokenSequence; extern SF_UINT32 const sfOracleDocumentID; -extern SF_UINT32 const sfOuterSequence; // 64-bit integers (common) extern SF_UINT64 const sfIndexNext; @@ -597,6 +596,7 @@ extern SF_ACCOUNT const sfAttestationSignerAccount; extern SF_ACCOUNT const sfAttestationRewardAccount; extern SF_ACCOUNT const sfLockingChainDoor; extern SF_ACCOUNT const sfIssuingChainDoor; +extern SF_ACCOUNT const sfOuterAccount; // path set extern SField const sfPaths; diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index 8ce005ea2d7..c3becd24b2f 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -116,6 +116,7 @@ JSS(Oracle); // ledger type. JSS(OracleDelete); // transaction type. JSS(OracleDocumentID); // field JSS(OracleSet); // transaction type. +JSS(OuterAccount); // field JSS(Owner); // field JSS(Paths); // in/out: TransactionSign JSS(PayChannel); // ledger type. diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 288f9ade2fd..56c1f848a55 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -156,8 +156,7 @@ InnerObjectFormats::InnerObjectFormats() add(sfBatchTxn.jsonName.c_str(), sfBatchTxn.getCode(), - {{sfAccount, soeREQUIRED}, - {sfOuterSequence, soeREQUIRED}, + {{sfOuterAccount, soeREQUIRED}, {sfSequence, soeOPTIONAL}, {sfBatchIndex, soeREQUIRED}}); diff --git a/src/libxrpl/protocol/SField.cpp b/src/libxrpl/protocol/SField.cpp index 959c16614ca..1a2bcb689e5 100644 --- a/src/libxrpl/protocol/SField.cpp +++ b/src/libxrpl/protocol/SField.cpp @@ -168,7 +168,6 @@ CONSTRUCT_TYPED_SFIELD(sfEmitGeneration, "EmitGeneration", UINT32, CONSTRUCT_TYPED_SFIELD(sfVoteWeight, "VoteWeight", UINT32, 48); CONSTRUCT_TYPED_SFIELD(sfFirstNFTokenSequence, "FirstNFTokenSequence", UINT32, 50); CONSTRUCT_TYPED_SFIELD(sfOracleDocumentID, "OracleDocumentID", UINT32, 51); -CONSTRUCT_TYPED_SFIELD(sfOuterSequence, "OuterSequence", UINT32, 52); // 64-bit integers (common) CONSTRUCT_TYPED_SFIELD(sfIndexNext, "IndexNext", UINT64, 1); @@ -329,6 +328,7 @@ CONSTRUCT_TYPED_SFIELD(sfAttestationSignerAccount, "AttestationSignerAccount", A CONSTRUCT_TYPED_SFIELD(sfAttestationRewardAccount, "AttestationRewardAccount", ACCOUNT, 21); CONSTRUCT_TYPED_SFIELD(sfLockingChainDoor, "LockingChainDoor", ACCOUNT, 22); CONSTRUCT_TYPED_SFIELD(sfIssuingChainDoor, "IssuingChainDoor", ACCOUNT, 23); +CONSTRUCT_TYPED_SFIELD(sfOuterAccount, "OuterAccount", ACCOUNT, 24); // vector of 256-bit CONSTRUCT_TYPED_SFIELD(sfIndexes, "Indexes", VECTOR256, 1, SField::sMD_Never); diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 41673160c09..39fbf3c413d 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -192,11 +192,12 @@ STTx::getSeqProxy() const STObject const batchTxn = const_cast(*this) .getField(sfBatchTxn) .downcast(); - std::uint32_t const startSequence{ - batchTxn.getFieldU32(sfOuterSequence)}; + std::uint32_t const startSequence{batchTxn.getFieldU32(sfSequence)}; std::uint32_t const batchIndex{batchTxn.getFieldU8(sfBatchIndex)}; std::uint8_t const sourceAdj = - getAccountID(sfAccount) == batchTxn.getAccountID(sfAccount) ? 1 : 0; + getAccountID(sfAccount) == batchTxn.getAccountID(sfOuterAccount) + ? 1 + : 0; return SeqProxy::sequence(startSequence + batchIndex + sourceAdj); } diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 5f42d8e94ad..ce5b9de497f 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -95,9 +95,9 @@ class Batch_test : public beast::unit_test::suite jv[sfRawTransactions.jsonName][index][jss::RawTransaction] [sfBatchTxn.jsonName] = Json::Value{}; jv[sfRawTransactions.jsonName][index][jss::RawTransaction] - [sfBatchTxn.jsonName][jss::Account] = account.human(); + [sfBatchTxn.jsonName][jss::OuterAccount] = account.human(); jv[sfRawTransactions.jsonName][index][jss::RawTransaction] - [sfBatchTxn.jsonName][sfOuterSequence.jsonName] = outerSequence; + [sfBatchTxn.jsonName][sfSequence.jsonName] = outerSequence; jv[sfRawTransactions.jsonName][index][jss::RawTransaction] [sfBatchTxn.jsonName][sfBatchIndex.jsonName] = innerIndex; return jv; @@ -171,12 +171,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "BC50DFE508E921460443261F46383A68610BB929F78030EA700E373654187" - "851"}, + "3664A012DA8936D0ECF7B84E7447DB88088A2DA9FD61A7BDC4C4DB0CD9944" + "3AC"}, {"tesSUCCESS", "Payment", - "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BD" - "BAD"}, + "44B76513FE9A57E84B837139C1D83A81EB70C88842EC85A561A71F05DF514" + "273"}, }}; Json::Value params; @@ -184,7 +184,7 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - std::cout << jrr << std::endl; + // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], testCases); @@ -284,12 +284,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tecUNFUNDED_PAYMENT", "Payment", - "32B90ABCD36E4601196708F5C93568BA49BE6F1221D58703EAFFB568FAC9807" - "E"}, + "3ACCF055EB1E32947E86B9105E1E71CF6E1DE65D42273DBF006D3AB165DF8B4" + "2"}, {"tesSUCCESS", "Payment", - "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBA" - "D"}, + "44B76513FE9A57E84B837139C1D83A81EB70C88842EC85A561A71F05DF51427" + "3"}, }}; Json::Value params; @@ -357,16 +357,16 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "BC50DFE508E921460443261F46383A68610BB929F78030EA700E37365418785" - "1"}, + "3664A012DA8936D0ECF7B84E7447DB88088A2DA9FD61A7BDC4C4DB0CD99443A" + "C"}, {"tesSUCCESS", "Payment", - "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBA" - "D"}, + "44B76513FE9A57E84B837139C1D83A81EB70C88842EC85A561A71F05DF51427" + "3"}, {"tecUNFUNDED_PAYMENT", "Payment", - "5B3B8F9557B38CABE92B8A420C36508711B921B912C391E7029836CD1BAE0BB" - "6"}, + "ECBA4387E1C76823E0E3EFA216ECABB991B6B3C175E3D29226086B37241A768" + "2"}, }}; Json::Value params; @@ -434,20 +434,20 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "BC50DFE508E921460443261F46383A68610BB929F78030EA700E37365418785" - "1"}, + "3664A012DA8936D0ECF7B84E7447DB88088A2DA9FD61A7BDC4C4DB0CD99443A" + "C"}, {"tesSUCCESS", "Payment", - "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBA" - "D"}, + "44B76513FE9A57E84B837139C1D83A81EB70C88842EC85A561A71F05DF51427" + "3"}, {"tecUNFUNDED_PAYMENT", "Payment", - "5B3B8F9557B38CABE92B8A420C36508711B921B912C391E7029836CD1BAE0BB" - "6"}, + "ECBA4387E1C76823E0E3EFA216ECABB991B6B3C175E3D29226086B37241A768" + "2"}, {"tesSUCCESS", "Payment", - "FCB23E17BDCCC55DC354EF8F6D3D1E7E39E705ADF65106D005731736B190A1E" - "C"}, + "2A7AEB6F8EF57D2E6104104B1FA125ABA5B12A94116E0829E74B54D0394E749" + "1"}, }}; Json::Value params; @@ -532,12 +532,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "319131912A291734CCF2766390B6010E1C63D2916011EE6A154B6F210BE43A7" - "2"}, + "99C5E1DEBC039D9BADCDB2B786F79F0A72A6E6EE40386BA7485C94B20E6CF8E" + "E"}, {"tesSUCCESS", "Payment", - "CBA8ADE2945A4DC41E9E979CE274630E38B606ECB5922366FF77539FE3D01CC" - "D"}, + "C18F28FD9BCEF3EC24FADC20BD25608751AF185B319BDD9EBCEAF2D24200F55" + "C"}, }}; Json::Value params; @@ -600,12 +600,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "AccountSet", - "21D36FEF5EE86F7B4B8609661E138AB166780745F48A94981FB9BBFFF94E780" - "C"}, + "B9BF25231F9923E1F0AD95BFC8F66EED4E76E3B7C36D23326661CB57D7CF5E1" + "3"}, {"tesSUCCESS", "Payment", - "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBA" - "D"}, + "44B76513FE9A57E84B837139C1D83A81EB70C88842EC85A561A71F05DF51427" + "3"}, }}; Json::Value params; @@ -725,12 +725,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "CheckCreate", - "80229431EBDCF659E1EE1B6E5DC94B195B2E52E81D88663224EC8C06F301FF1" - "F"}, + "92E8D7C221CAF96B70EDE21E5DD3A3126F73474EAB7ABB639A6FAF5E45C7D13" + "6"}, {"tesSUCCESS", "Payment", - "B76CA59CEBEF3B9D64F1ED81BFEA5A6BE4E1A9194F81776916A4A4E4C79BDBA" - "D"}, + "44B76513FE9A57E84B837139C1D83A81EB70C88842EC85A561A71F05DF51427" + "3"}, }}; Json::Value params; @@ -811,12 +811,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "CheckCreate", - "CE920705201AC0E4B3C50E52170BF1ED31E90457CAC4E79A0E1C1AE82BA5CC4" - "6"}, + "4C63C1F06AE7429CB29D24E32E395C5CEFAB392730B002AF0597E04FCA3651A" + "0"}, {"tesSUCCESS", "CheckCash", - "165AE829F04BD375DDC3A7F57386B2B2A9386211839C005E1481A4E4282F625" - "F"}, + "4923A3865BFA7DDD2F43CB4C15B461505CA0605079CC99DCBBCEFE210E57B17" + "C"}, }}; Json::Value params; @@ -900,12 +900,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "CheckCreate", - "3C33B38C8804F0FF01BEADE2CFD665C028A53A4220E0D08A73EB7CBBB82D59A" - "C"}, + "7EA52BD67C03CE73EB3621491EA66A3DC1E1CA0B3AEBC2A8E56908329A6C28B" + "1"}, {"tesSUCCESS", "CheckCancel", - "BFED069E7ECDFD313BDAC1A985C9F05EFD2FBBF63D6C42E8DDEF09087C4D233" - "A"}, + "083804635D4A38BE94D35F4FD900AC4B864294926345594409FD70630AFA963" + "4"}, }}; Json::Value params; @@ -978,12 +978,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Clawback", - "D08330FED53B479F2949DF85717101ED513B046B06B748BDF19F5951A81DAAE" - "2"}, + "838F5265749C559B067F5852B98A2B262AA22A88C556E6A51DD7EF9B842FAB1" + "5"}, {"tesSUCCESS", "Payment", - "FEB6D7EE4BE48851B1CE9B31DB39A5A4FDFBB59DFFE1A49146FD6D9177C1ECC" - "6"}, + "CDD0AF925A52E2D9C9661FDBDD2CD1856FDA058B5E4C262974F9D90698A2800" + "0"}, }}; Json::Value params; @@ -1104,7 +1104,7 @@ class Batch_test : public beast::unit_test::suite auto jv = pay(alice, bob, USD(1)); jv[sfBatchTxn.jsonName] = Json::Value{}; jv[sfBatchTxn.jsonName][jss::Account] = alice.human(); - jv[sfBatchTxn.jsonName][sfOuterSequence.jsonName] = 0; + jv[sfBatchTxn.jsonName][sfSequence.jsonName] = 0; jv[sfBatchTxn.jsonName][sfBatchIndex.jsonName] = 0; Serializer s; diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 3f95e35476c..e0e55d597f8 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -334,12 +334,12 @@ Transactor::checkSeqProxy( STObject const batchTxn = const_cast(tx) .getField(sfBatchTxn) .downcast(); - std::uint32_t const startSequence{ - batchTxn.getFieldU32(sfOuterSequence)}; + std::uint32_t const startSequence{batchTxn.getFieldU32(sfSequence)}; std::uint32_t const batchIndex{batchTxn.getFieldU8(sfBatchIndex)}; std::uint8_t const sourceAdj = - tx.getAccountID(sfAccount) == batchTxn.getAccountID(sfAccount) ? 1 - : 0; + tx.getAccountID(sfAccount) == batchTxn.getAccountID(sfOuterAccount) + ? 1 + : 0; a_seq = SeqProxy::sequence(startSequence + batchIndex + sourceAdj); } From 418836c4b7779fdea0276dfb5f2ade809cd10cc1 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 3 Aug 2024 16:00:06 +0200 Subject: [PATCH 25/71] reject batch txn on submit --- include/xrpl/protocol/TER.h | 1 + src/libxrpl/protocol/TER.cpp | 1 + src/test/app/Batch_test.cpp | 58 ++++++++++++++-------- src/xrpld/app/ledger/detail/OpenLedger.cpp | 2 +- src/xrpld/app/misc/NetworkOPs.cpp | 17 ++++++- src/xrpld/overlay/detail/PeerImp.cpp | 2 +- 6 files changed, 56 insertions(+), 25 deletions(-) diff --git a/include/xrpl/protocol/TER.h b/include/xrpl/protocol/TER.h index b96b09267f4..b72ed747e63 100644 --- a/include/xrpl/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -139,6 +139,7 @@ enum TEMcodes : TERUnderlyingType { temARRAY_EMPTY, temARRAY_TOO_LARGE, + temINVALID_BATCH, }; //------------------------------------------------------------------------------ diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index a6f82ec7e8b..467cb9a20e0 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -205,6 +205,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(terRETRY, "Retry transaction."), MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."), diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index ce5b9de497f..9b7773b7b48 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -1083,11 +1083,8 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - // test::jtx::Env env{*this, envconfig()}; - Env env{ - *this, envconfig(), features, nullptr, beast::severities::kTrace}; + test::jtx::Env env{*this, envconfig()}; - auto const feeDrops = env.current()->fees().base; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const gw = Account("gw"); @@ -1101,23 +1098,42 @@ class Batch_test : public beast::unit_test::suite env(pay(gw, bob, USD(100))); env.close(); - auto jv = pay(alice, bob, USD(1)); - jv[sfBatchTxn.jsonName] = Json::Value{}; - jv[sfBatchTxn.jsonName][jss::Account] = alice.human(); - jv[sfBatchTxn.jsonName][sfSequence.jsonName] = 0; - jv[sfBatchTxn.jsonName][sfBatchIndex.jsonName] = 0; - - Serializer s; - auto jt = env.jt(jv); - s.erase(); - jt.stx->add(s); - auto const jr = env.rpc("submit", strHex(s.slice())); - BEAST_EXPECT( - jr.isMember(jss::engine_result) && - jr[jss::engine_result] == "tesSUCCESS"); + { + auto jv = pay(alice, bob, USD(1)); + jv[sfBatchTxn.jsonName] = Json::Value{}; + jv[sfBatchTxn.jsonName][jss::OuterAccount] = alice.human(); + jv[sfBatchTxn.jsonName][sfSequence.jsonName] = 0; + jv[sfBatchTxn.jsonName][sfBatchIndex.jsonName] = 0; + + Serializer s; + auto jt = env.jt(jv); + jv.removeMember(sfTxnSignature.jsonName); + s.erase(); + jt.stx->add(s); + auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result]; + // std::cout << jrr << std::endl; + BEAST_EXPECT( + jrr[jss::status] == "error" && + jrr[jss::error] == "invalidTransaction"); - env.close(); - std::cout << jr << std::endl; + env.close(); + } + { + std::string txBlob = + "1200002280000000240000000561D4838D7EA4C68000000000000000000000" + "0000005553440000000000A407AF5856CCF3C42619DAA925813FC955C72983" + "68400000000000000A73210388935426E0D08083314842EDFBB2D517BD4769" + "9F9A4527318A8E10468C97C0528114AE123A8556F3CF91154711376AFB0F89" + "4F832B3D8314F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90FE023240000" + "0000801814AE123A8556F3CF91154711376AFB0F894F832B3D00101400E1"; + auto const jrr = env.rpc("submit", txBlob)[jss::result]; + // std::cout << jrr << std::endl; + BEAST_EXPECT( + jrr[jss::status] == "success" && + jrr[jss::engine_result] == "temINVALID_BATCH"); + + env.close(); + } } void @@ -1137,7 +1153,7 @@ class Batch_test : public beast::unit_test::suite testClawback(features); // testOfferCancel(features); - // testSubmit(features); + testSubmit(features); // Test Fork From one node having 1 extra txn diff --git a/src/xrpld/app/ledger/detail/OpenLedger.cpp b/src/xrpld/app/ledger/detail/OpenLedger.cpp index 030b055cfc3..0af84ab5f61 100644 --- a/src/xrpld/app/ledger/detail/OpenLedger.cpp +++ b/src/xrpld/app/ledger/detail/OpenLedger.cpp @@ -122,7 +122,7 @@ OpenLedger::accept( auto const& tx = txpair.first; auto const txId = tx->getTransactionID(); - // skip emitted txns + // skip batch txns if (tx->isFieldPresent(sfBatchTxn)) continue; diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 90cc4c42b44..cbf95a9b1fc 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -1141,6 +1141,15 @@ NetworkOPsImp::submitTransaction(std::shared_ptr const& iTrans) return; } + // Enforce Network bar for batch txn + auto const view = m_ledgerMaster.getCurrentLedger(); + if (view->rules().enabled(featureBatch) && iTrans->isFieldPresent(sfBatchTxn)) + { + JLOG(m_journal.error()) + << "Submitted transaction invalid: BatchTxn present."; + return; + } + // this is an asynchronous interface auto const trans = sterilize(*iTrans); @@ -1217,6 +1226,9 @@ NetworkOPsImp::processTransaction( if (view->rules().enabled(featureBatch) && tx.isFieldPresent(ripple::sfBatchTxn)) { + transaction->setStatus(INVALID); + transaction->setResult(temINVALID_BATCH); + app_.getHashRouter().setFlags(transaction->getID(), SF_BAD); return; } @@ -1478,12 +1490,13 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) auto const toSkip = app_.getHashRouter().shouldRelay(e.transaction->getID()); - if (toSkip) + auto const txn = *(e.transaction->getSTransaction()); + if (toSkip && !txn.isFieldPresent(sfBatchTxn)) { protocol::TMTransaction tx; Serializer s; - e.transaction->getSTransaction()->add(s); + txn.add(s); tx.set_rawtransaction(s.data(), s.size()); tx.set_status(protocol::tsCURRENT); tx.set_receivetimestamp( diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 3d93d91d02c..0feab4431bb 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -2728,7 +2728,7 @@ PeerImp::checkTransaction( // VFALCO TODO Rewrite to not use exceptions try { - // charge strongly for relaying Hook emitted txns + // charge strongly for relaying batch txns if (stx->isFieldPresent(sfBatchTxn)) { JLOG(p_journal_.warn()) << "Ignoring Network relayed Tx containing " From fe0997245697e5b4c84be64f40e9897f2ef8a5f7 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 3 Aug 2024 16:03:58 +0200 Subject: [PATCH 26/71] clang-format --- src/xrpld/app/misc/NetworkOPs.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index cbf95a9b1fc..9a592deec7f 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -1143,7 +1143,8 @@ NetworkOPsImp::submitTransaction(std::shared_ptr const& iTrans) // Enforce Network bar for batch txn auto const view = m_ledgerMaster.getCurrentLedger(); - if (view->rules().enabled(featureBatch) && iTrans->isFieldPresent(sfBatchTxn)) + if (view->rules().enabled(featureBatch) && + iTrans->isFieldPresent(sfBatchTxn)) { JLOG(m_journal.error()) << "Submitted transaction invalid: BatchTxn present."; From a72b070e54cda9986a210c192dca1b0a42d7b5ac Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sat, 3 Aug 2024 17:14:24 +0200 Subject: [PATCH 27/71] add tests --- src/test/app/Batch_test.cpp | 256 +++++++++++++++++++++++++++++- src/xrpld/app/tx/detail/Batch.cpp | 57 ++++--- 2 files changed, 284 insertions(+), 29 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 9b7773b7b48..92db70c2d33 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -123,6 +123,253 @@ class Batch_test : public beast::unit_test::suite return jv; } + void + testEnable(FeatureBitset features) + { + testcase("enabled"); + + using namespace test::jtx; + using namespace std::literals; + + for (bool const withBatch : {true, false}) + { + auto const amend = withBatch ? features : features - featureBatch; + test::jtx::Env env{*this, envconfig(), amend}; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + 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 const tx1 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + + auto const txResult = + withBatch ? ter(tesSUCCESS) : ter(temDISABLED); + env(jv, txResult); + env.close(); + } + } + + void + testPreflight(FeatureBitset features) + { + testcase("preflight"); + + using namespace test::jtx; + using namespace std::literals; + + //---------------------------------------------------------------------- + // preflight + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + // 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)); + env.close(); + } + + // temMALFORMED: 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)); + env.close(); + } + + // temMALFORMED: 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 = 0; i < 13; ++i) + { + Json::Value const tx = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx, alice.pk(), alice, i, seq, i); + } + + env(jv, ter(temMALFORMED)); + env.close(); + } + + // temBAD_SIGNATURE: Batch: invalid batch txn signature. + { + std::vector const signers = {{ + {0, alice}, + {1, 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.pk(), alice, 0, env.seq(alice), 0); + + // Tx 2 + Json::Value const tx2 = pay(bob, alice, XRP(5)); + jv = addBatchTx(jv, tx2, bob.pk(), alice, 0, env.seq(bob), 1); + + for (auto const& signer : signers) + { + Serializer ss{ + buildMultiSigningData(jtx::parse(jv), signer.account.id())}; + auto const sig = ripple::sign( + signer.account.pk(), signer.account.sk(), ss.slice()); + jv[sfBatchSigners.jsonName][signer.index] + [sfBatchSigner.jsonName][sfAccount.jsonName] = + signer.account.human(); + jv[sfBatchSigners.jsonName][signer.index] + [sfBatchSigner.jsonName][sfSigningPubKey.jsonName] = + strHex(alice.pk()); + jv[sfBatchSigners.jsonName][signer.index] + [sfBatchSigner.jsonName][sfTxnSignature.jsonName] = + strHex(Slice{sig.data(), sig.size()}); + } + env(jv, ter(temBAD_SIGNATURE)); + env.close(); + } + + // // temMALFORMED: Batch: TransactionType missing in array entry. + // { + // 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}; + + // // Tx 1 + // Json::Value tx1 = pay(alice, bob, XRP(10)); + // jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + // jv[sfRawTransactions.jsonName][0u][jss::RawTransaction].removeMember( + // jss::TransactionType); + + // env(jv, ter(temMALFORMED)); + // env.close(); + // } + + // temMALFORMED: 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.pk(), alice, 0, seq, 0); + } + + jv = addBatchTx(jv, btx, alice.pk(), alice, 0, seq, 0); + + // Tx 2 + Json::Value const tx2 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + + env(jv, ter(temMALFORMED)); + env.close(); + } + + // temBAD_SIGNER: Batch: inner txn not signed by the right user. + { + std::vector const signers = {{ + {0, alice}, + {1, 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.pk(), alice, 0, env.seq(alice), 0); + + // Tx 2 + Json::Value const tx2 = pay(bob, alice, XRP(5)); + jv = addBatchTx(jv, tx2, bob.pk(), alice, 0, env.seq(bob), 1); + + jv = addBatchSignatures(jv, signers); + env(jv, ter(temBAD_SIGNER)); + env.close(); + } + } + void testAllOrNothing(FeatureBitset features) { @@ -1139,12 +1386,13 @@ class Batch_test : public beast::unit_test::suite void testWithFeats(FeatureBitset features) { + testEnable(features); + testPreflight(features); testAllOrNothing(features); testOnlyOne(features); testUntilFailure(features); testIndependent(features); testAtomicSwap(features); - testAccountSet(features); testBatch(features); testCheckCreate(features); @@ -1152,13 +1400,13 @@ class Batch_test : public beast::unit_test::suite testCheckCancel(features); testClawback(features); // testOfferCancel(features); - testSubmit(features); - // Test Fork From one node having 1 extra txn - + // DA TODO: // Multisign Atomic // If the 2nd fails and needs the 3rd + // Validatate all errors in checkBatchSign() + // tem failure that does not consume the sequence } public: diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index d4b5103fc65..4c129f5138e 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -38,16 +38,18 @@ Batch::makeTxConsequences(PreflightContext const& ctx) NotTEC Batch::preflight(PreflightContext const& ctx) { + if (!ctx.rules.enabled(featureBatch)) + return temDISABLED; + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; auto& tx = ctx.tx; - bool const isSwap = tx.isFieldPresent(sfBatchSigners); - if (isSwap) + + if (tx.getFlags() & tfBatchMask) { - auto const sigResult = ctx.tx.checkBatchSign(); - if (!sigResult) - return temBAD_SIGNER; + JLOG(ctx.j.debug()) << "Batch: invalid flags."; + return temINVALID_FLAG; } AccountID const outerAccount = tx.getAccountID(sfAccount); @@ -65,6 +67,16 @@ Batch::preflight(PreflightContext const& ctx) return temMALFORMED; } + if (tx.isFieldPresent(sfBatchSigners)) + { + auto const sigResult = ctx.tx.checkBatchSign(); + if (!sigResult) + { + JLOG(ctx.j.debug()) << "Batch: invalid batch txn signature."; + return temBAD_SIGNATURE; + } + } + for (STObject txn : txns) { if (!txn.isFieldPresent(sfTransactionType)) @@ -79,11 +91,21 @@ Batch::preflight(PreflightContext const& ctx) return temMALFORMED; } - AccountID const innerAccount = txn.getAccountID(sfAccount); - if (!isSwap && innerAccount != outerAccount) + if (auto const innerAccount = txn.getAccountID(sfAccount); + innerAccount != outerAccount) { - JLOG(ctx.j.debug()) << "Batch: batch signer mismatch."; - return temBAD_SIGNER; + if (tx.getFieldArray(sfBatchSigners).end() == + std::find_if( + tx.getFieldArray(sfBatchSigners).begin(), + tx.getFieldArray(sfBatchSigners).end(), + [innerAccount](STObject const& signer) { + return signer.getAccountID(sfAccount) == innerAccount; + })) + { + JLOG(ctx.j.debug()) + << "Batch: inner txn not signed by the right user."; + return temBAD_SIGNER; + } } } return preflight2(ctx); @@ -95,18 +117,6 @@ Batch::preclaim(PreclaimContext const& ctx) if (!ctx.view.rules().enabled(featureBatch)) return temDISABLED; - auto const& txns = ctx.tx.getFieldArray(sfRawTransactions); - for (std::size_t i = 0; i < txns.size(); ++i) - { - STObject txn = txns[i]; - if (!txn.isFieldPresent(sfTransactionType)) - { - JLOG(ctx.j.debug()) - << "Batch: TransactionType missing in array entry."; - return temMALFORMED; - } - } - return tesSUCCESS; } @@ -115,10 +125,7 @@ Batch::doApply() { Sandbox sb(&ctx_.view()); bool changed = false; - - uint32_t flags = ctx_.tx.getFlags(); - if (flags & tfBatchMask) - return temINVALID_FLAG; + auto const flags = ctx_.tx.getFlags(); TER result = tesSUCCESS; ApplyViewImpl& avi = dynamic_cast(ctx_.view()); From 0a45c160bd87493eb0edcbb6af2187a02317ac71 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 5 Aug 2024 08:51:29 +0200 Subject: [PATCH 28/71] [fold] remove unused code --- src/xrpld/app/tx/detail/ApplyContext.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/xrpld/app/tx/detail/ApplyContext.cpp b/src/xrpld/app/tx/detail/ApplyContext.cpp index 86277d1f073..a45f105491e 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.cpp +++ b/src/xrpld/app/tx/detail/ApplyContext.cpp @@ -94,7 +94,6 @@ ApplyContext::applyFee() assert(sle != nullptr || sleBase != nullptr || account == beast::zero); if (sle && sleBase) { - auto const feePaid = tx[sfFee].xrp(); sle->setFieldAmount(sfBalance, (*sleBase)[sfBalance].xrp()); view_->update(sle); } From 2a0b9d4b5ff3a47c056c0dffae1e9fa7b43ab42c Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 5 Aug 2024 08:52:53 +0200 Subject: [PATCH 29/71] include batch execution in tec failures --- src/xrpld/app/tx/detail/Transactor.cpp | 5 +++++ src/xrpld/ledger/ApplyViewImpl.h | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index e0e55d597f8..f5ef8c61d78 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -850,7 +850,12 @@ removeDeletedTrustLines( std::pair Transactor::reset(XRPAmount fee) { + ApplyViewImpl& avi = dynamic_cast(ctx_.view()); + std::vector executions; + avi.copyBatchMetaData(executions); ctx_.discard(); + ApplyViewImpl& avi2 = dynamic_cast(ctx_.view()); + avi2.setBatchMetaData(std::move(executions)); auto const txnAcct = view().peek(keylet::account(ctx_.tx.getAccountID(sfAccount))); diff --git a/src/xrpld/ledger/ApplyViewImpl.h b/src/xrpld/ledger/ApplyViewImpl.h index ba98e29518b..dcce178867a 100644 --- a/src/xrpld/ledger/ApplyViewImpl.h +++ b/src/xrpld/ledger/ApplyViewImpl.h @@ -84,11 +84,20 @@ class ApplyViewImpl final : public detail::ApplyViewBase } void - setHookMetaData(std::vector&& batch) + setBatchMetaData(std::vector&& batch) { batchExecution_ = std::move(batch); } + void + copyBatchMetaData(std::vector& execution) + { + std::copy( + batchExecution_.begin(), + batchExecution_.end(), + std::back_inserter(execution)); + } + /** Get the number of modified entries */ std::size_t From 3a9963a3abbfd5d04ed8e516a2eb5d441ee3bc9b Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Mon, 5 Aug 2024 09:55:20 +0200 Subject: [PATCH 30/71] atomic revert on non tec failures --- src/test/app/Batch_test.cpp | 145 ++++++++++++++++++++++++++++-- src/xrpld/app/tx/detail/Batch.cpp | 11 ++- 2 files changed, 147 insertions(+), 9 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 92db70c2d33..dce92bac7a9 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -370,6 +370,140 @@ class Batch_test : public beast::unit_test::suite } } + void + testOutOfSequence(FeatureBitset features) + { + testcase("out of sequence"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + // tfAllOrNothing + { + 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.pk(), alice, 0, seq, 0); + + // Tx 2 + Json::Value const tx2 = + jtx::trust(alice, alice["USD"](1000), tfSetfAuth); + jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + + // Tx 3 + Json::Value const tx3 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx3, alice.pk(), alice, 2, seq, 2); + + env(jv, + fee(feeDrops * 3), + txflags(tfAllOrNothing), + ter(tefNO_AUTH_REQUIRED)); + env.close(); + } + + // tfUntilFailure + { + 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.pk(), alice, 0, seq, 0); + + // Tx 2 + Json::Value const tx2 = + jtx::trust(alice, alice["USD"](1000), tfSetfAuth); + jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + + // Tx 3 + Json::Value const tx3 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx3, alice.pk(), alice, 2, seq, 2); + + env(jv, + fee(feeDrops * 3), + txflags(tfUntilFailure), + ter(tefNO_AUTH_REQUIRED)); + env.close(); + } + + // tfOnlyOne + { + 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.pk(), alice, 0, seq, 0); + + // Tx 2 + Json::Value const tx2 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + + env(jv, + fee(feeDrops * 2), + txflags(tfOnlyOne), + ter(tefNO_AUTH_REQUIRED)); + env.close(); + } + + // tfIndependent + { + 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.pk(), alice, 0, seq, 0); + + // Tx 2 + Json::Value const tx2 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + + env(jv, + fee(feeDrops * 3), + txflags(tfIndependent), + ter(tefNO_AUTH_REQUIRED)); + env.close(); + } + } + void testAllOrNothing(FeatureBitset features) { @@ -720,9 +854,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"); @@ -1256,9 +1387,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"); @@ -1388,6 +1516,7 @@ class Batch_test : public beast::unit_test::suite { testEnable(features); testPreflight(features); + testOutOfSequence(features); testAllOrNothing(features); testOnlyOne(features); testUntilFailure(features); @@ -1406,7 +1535,9 @@ class Batch_test : public beast::unit_test::suite // Multisign Atomic // If the 2nd fails and needs the 3rd // Validatate all errors in checkBatchSign() - // tem failure that does not consume the sequence + // tem failure that does not consume the sequence: Handled with atomic + // revert Assertion failed: (feeLevelPaid >= baseLevel), function apply, + // file TxQ.cpp, line 1155. } public: diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 4c129f5138e..9f198e4abac 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -154,6 +154,15 @@ Batch::doApply() if (ter != tesSUCCESS) { + // Atomic Revert on non tec failure + if (!isTecClaim(ter)) + { + accountCount.clear(); + result = ter; + changed = false; + break; + } + if (flags & tfUntilFailure) { result = tesSUCCESS; @@ -166,8 +175,6 @@ Batch::doApply() if (flags & tfAllOrNothing) { accountCount.clear(); - std::vector batch; - avi.setHookMetaData(std::move(batch)); result = tecBATCH_FAILURE; changed = false; break; From 2c3c5c4dcfd7a1ba0740bad6e503c2a33bbc782f Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 9 Aug 2024 10:48:12 +0200 Subject: [PATCH 31/71] fix sequence & fee --- src/test/app/Batch_test.cpp | 153 +++++++++++++++++++++++-- src/xrpld/app/tx/detail/Batch.cpp | 9 ++ src/xrpld/app/tx/detail/Transactor.cpp | 42 +------ 3 files changed, 156 insertions(+), 48 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index dce92bac7a9..245a1a2add4 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -370,6 +370,149 @@ class Batch_test : public beast::unit_test::suite } } + void + testBadSequence(FeatureBitset features) + { + testcase("bad sequence"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(1000), alice, bob); + env(pay(gw, alice, USD(100))); + env(pay(gw, bob, USD(100))); + env.close(); + + env(noop(bob), ter(tesSUCCESS)); + env.close(); + + auto const preAliceSeq = env.seq(alice); + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBobSeq = env.seq(bob); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + std::vector const signers = {{ + {0, alice}, + {1, 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.pk(), alice, 0, preAliceSeq, 0); + + // Tx 2 + Json::Value const tx2 = pay(bob, alice, USD(5)); + jv = addBatchTx(jv, tx2, bob.pk(), alice, 10, preBobSeq, 1); + + jv = addBatchSignatures(jv, signers); + + env(jv, ter(terPRE_SEQ)); + env.close(); + + // Alice & Bob should not be affected. + BEAST_EXPECT(env.seq(alice) == preAliceSeq); + BEAST_EXPECT(env.balance(alice) == preAlice); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD); + BEAST_EXPECT(env.seq(bob) == preBobSeq); + BEAST_EXPECT(env.balance(bob) == preBob); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD); + } + + void + testBadFee(FeatureBitset features) + { + testcase("bad fee"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(1000), alice, bob); + env(pay(gw, alice, USD(100))); + env(pay(gw, bob, USD(100))); + env.close(); + + env(noop(bob), ter(tesSUCCESS)); + env.close(); + + auto const preAliceSeq = env.seq(alice); + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBobSeq = env.seq(bob); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + std::vector const signers = {{ + {0, alice}, + {1, 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.pk(), alice, 0, env.seq(alice), 0); + + // Tx 2 + Json::Value const tx2 = pay(bob, alice, USD(5)); + jv = addBatchTx(jv, tx2, bob.pk(), alice, 0, env.seq(bob), 1); + + jv = addBatchSignatures(jv, signers); + + env(jv, ter(telINSUF_FEE_P)); + env.close(); + + // Alice & Bob should not be affected. + BEAST_EXPECT(env.seq(alice) == preAliceSeq); + BEAST_EXPECT(env.balance(alice) == preAlice); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD); + BEAST_EXPECT(env.seq(bob) == preBobSeq); + BEAST_EXPECT(env.balance(bob) == preBob); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD); + } + void testOutOfSequence(FeatureBitset features) { @@ -1516,6 +1659,8 @@ class Batch_test : public beast::unit_test::suite { testEnable(features); testPreflight(features); + testBadSequence(features); + testBadFee(features); testOutOfSequence(features); testAllOrNothing(features); testOnlyOne(features); @@ -1530,14 +1675,6 @@ class Batch_test : public beast::unit_test::suite testClawback(features); // testOfferCancel(features); testSubmit(features); - - // DA TODO: - // Multisign Atomic - // If the 2nd fails and needs the 3rd - // Validatate all errors in checkBatchSign() - // tem failure that does not consume the sequence: Handled with atomic - // revert Assertion failed: (feeLevelPaid >= baseLevel), function apply, - // file TxQ.cpp, line 1155. } public: diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 9f198e4abac..4856cfd8f97 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -217,6 +217,7 @@ Batch::doApply() XRPAmount Batch::calculateBaseFee(ReadView const& view, STTx const& tx) { + // Calculate the Inner Txn Fees XRPAmount extraFee{0}; if (tx.isFieldPresent(sfRawTransactions)) { @@ -229,6 +230,14 @@ Batch::calculateBaseFee(ReadView const& view, STTx const& tx) } extraFee += txFees; } + + // Calculate the BatchSigners Fees + if (tx.isFieldPresent(sfBatchSigners)) + { + auto const signers = tx.getFieldArray(sfBatchSigners); + extraFee += (signers.size() + 2) * view.fees().base; + } + return extraFee; } diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index f5ef8c61d78..ecebfc91b28 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -213,33 +213,7 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) if (!isLegalAmount(feePaid) || feePaid < beast::zero) return temBAD_FEE; - // Only check fee is sufficient when the ledger is open. - if (ctx.view.open() && ctx.tx.getTxnType() == ttBATCH) - { - XRPAmount feeDue = XRPAmount{0}; - auto const& txns = ctx.tx.getFieldArray(sfRawTransactions); - for (std::size_t i = 0; i < txns.size(); ++i) - { - auto const& txn = txns[i]; - if (!txn.isFieldPresent(sfFee)) - { - JLOG(ctx.j.warn()) << "Batch: sfFee missing in array entry."; - return telINSUF_FEE_P; - } - auto const _fee = txn.getFieldAmount(sfFee); - feeDue += _fee.xrp(); - } - - if (feePaid < feeDue) - { - JLOG(ctx.j.trace()) - << "Insufficient fee paid: " << to_string(feePaid) << "/" - << to_string(feeDue); - return telINSUF_FEE_P; - } - } - - if (ctx.view.open() && ctx.tx.getTxnType() != ttBATCH) + if (ctx.view.open()) { auto const feeDue = minimumFee(ctx.app, baseFee, ctx.view.fees(), ctx.flags); @@ -326,21 +300,9 @@ Transactor::checkSeqProxy( { if (tx.getFieldU32(sfSequence) != 0) { - JLOG(j.trace()) << "applyTransaction: has both a Sequence number " - "and a BatchTxn"; + JLOG(j.trace()) << "applyTransaction: BatchTxn has a Sequence number"; return temBAD_SEQUENCE; } - - STObject const batchTxn = const_cast(tx) - .getField(sfBatchTxn) - .downcast(); - std::uint32_t const startSequence{batchTxn.getFieldU32(sfSequence)}; - std::uint32_t const batchIndex{batchTxn.getFieldU8(sfBatchIndex)}; - std::uint8_t const sourceAdj = - tx.getAccountID(sfAccount) == batchTxn.getAccountID(sfOuterAccount) - ? 1 - : 0; - a_seq = SeqProxy::sequence(startSequence + batchIndex + sourceAdj); } if (t_seqProx.isSeq()) From 3ab585be30e8a584ec007d38a2d2e60ef7fc428c Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 9 Aug 2024 10:52:03 +0200 Subject: [PATCH 32/71] clang-format --- src/libxrpl/protocol/TxFormats.cpp | 2 +- src/xrpld/app/tx/detail/Transactor.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index 8cda85fc45a..f1e029aad7b 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -514,7 +514,7 @@ TxFormats::TxFormats() {sfOwner, soeOPTIONAL}, }, commonFields); - + add(jss::Batch, ttBATCH, { diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 00a60b38502..a2b80c69bba 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -300,7 +300,8 @@ Transactor::checkSeqProxy( { if (tx.getFieldU32(sfSequence) != 0) { - JLOG(j.trace()) << "applyTransaction: BatchTxn has a Sequence number"; + JLOG(j.trace()) + << "applyTransaction: BatchTxn has a Sequence number"; return temBAD_SEQUENCE; } } From 26ee55505e87d86cdd4a0786c501646aed44d6f7 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 5 Sep 2024 23:41:49 +0200 Subject: [PATCH 33/71] previousFields + nested views + signers --- include/xrpl/protocol/STTx.h | 15 +- src/libxrpl/protocol/InnerObjectFormats.cpp | 5 +- src/libxrpl/protocol/STTx.cpp | 110 +++++- src/test/app/Batch_test.cpp | 394 +++++++++++++++++--- src/xrpld/app/tx/detail/Batch.cpp | 49 ++- src/xrpld/app/tx/detail/Transactor.cpp | 101 +++-- src/xrpld/app/tx/detail/Transactor.h | 18 +- src/xrpld/app/tx/detail/applySteps.cpp | 3 + src/xrpld/ledger/ApplyViewImpl.h | 7 + src/xrpld/ledger/detail/ApplyStateTable.cpp | 8 + src/xrpld/ledger/detail/ApplyStateTable.h | 1 + src/xrpld/ledger/detail/ApplyViewImpl.cpp | 2 +- 12 files changed, 610 insertions(+), 103 deletions(-) diff --git a/include/xrpl/protocol/STTx.h b/include/xrpl/protocol/STTx.h index c8a5927bcb7..ec1013aebef 100644 --- a/include/xrpl/protocol/STTx.h +++ b/include/xrpl/protocol/STTx.h @@ -120,12 +120,14 @@ class STTx final : public STObject, public CountedObject @return `true` if valid signature. If invalid, the error message string. */ enum class RequireFullyCanonicalSig : bool { no, yes }; + Expected checkSign(RequireFullyCanonicalSig requireCanonicalSig, Rules const& rules) const; Expected - checkBatchSign() const; + checkBatchSign(RequireFullyCanonicalSig requireCanonicalSig, Rules const& rules) + const; // SQL Functions with metadata. static std::string const& @@ -151,6 +153,17 @@ class STTx final : public STObject, public CountedObject RequireFullyCanonicalSig requireCanonicalSig, Rules const& rules) const; + Expected + checkBatchSingleSign( + STObject const& batchSigner, + RequireFullyCanonicalSig requireCanonicalSig) const; + + Expected + checkBatchMultiSign( + STObject const& batchSigner, + RequireFullyCanonicalSig requireCanonicalSig, + Rules const& rules) const; + STBase* copy(std::size_t n, void* buf) const override; STBase* diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 56c1f848a55..a4881f298d9 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -163,8 +163,9 @@ InnerObjectFormats::InnerObjectFormats() add(sfBatchSigner.jsonName.c_str(), sfBatchSigner.getCode(), {{sfAccount, soeREQUIRED}, - {sfSigningPubKey, soeREQUIRED}, - {sfTxnSignature, soeREQUIRED}}); + {sfSigningPubKey, soeOPTIONAL}, + {sfTxnSignature, soeOPTIONAL}, + {sfSigners, soeOPTIONAL}}); } InnerObjectFormats const& diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 39fbf3c413d..93d61e091c2 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -361,25 +361,114 @@ STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const } Expected -STTx::checkBatchSign() const +STTx::checkBatchSign( + RequireFullyCanonicalSig requireCanonicalSig, + Rules const& rules) const { - STArray const& signers{getFieldArray(sfBatchSigners)}; + try + { + STArray const& signers{getFieldArray(sfBatchSigners)}; + for (auto const& signer : signers) + { + Blob const& signingPubKey = signer.getFieldVL(sfSigningPubKey); + auto const result = signingPubKey.empty() + ? checkBatchMultiSign(signer, requireCanonicalSig, rules) + : checkBatchSingleSign(signer, requireCanonicalSig); + + if (!result) + return result; + } + return {}; + } + catch (std::exception const&) + { + } + return Unexpected("Internal signature check failure."); +} + +Expected +STTx::checkBatchSingleSign( + STObject const& batchSigner, + RequireFullyCanonicalSig requireCanonicalSig) const +{ + // We don't allow both a non-empty sfSigningPubKey and an sfSigners. + // That would allow the transaction to be signed two ways. So if both + // fields are present the signature is invalid. + 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); + + bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) || + (requireCanonicalSig == RequireFullyCanonicalSig::yes); + + auto const spk = batchSigner.getFieldVL(sfSigningPubKey); + + if (publicKeyType(makeSlice(spk))) + { + Blob const signature = batchSigner.getFieldVL(sfTxnSignature); + Blob const data = getSigningData(*this); + + validSig = verify( + PublicKey(makeSlice(spk)), + s.slice(), + makeSlice(signature), + fullyCanonical); + } + } + catch (std::exception const&) + { + // Assume it was a signature failure. + validSig = false; + } + if (validSig == false) + return Unexpected("Invalid signature."); + // Signature was verified. + return {}; +} + +Expected +STTx::checkBatchMultiSign( + STObject const& batchSigner, + RequireFullyCanonicalSig requireCanonicalSig, + Rules const& rules) const +{ + // Make sure the MultiSigners are present. Otherwise they are not + // attempting multi-signing and we just have a bad SigningPubKey. + if (!batchSigner.isFieldPresent(sfSigners)) + return Unexpected("Empty SigningPubKey."); + + // We don't allow both an sfSigners and an sfTxnSignature. Both fields + // being present would indicate that the transaction is signed both ways. + if (batchSigner.isFieldPresent(sfTxnSignature)) + return Unexpected("Cannot both single- and multi-sign."); + + STArray const& signers{batchSigner.getFieldArray(sfSigners)}; // There are well known bounds that the number of signers must be within. - if (signers.size() == 0 || signers.size() > 8) - return Unexpected("Invalid Batch Signers array size."); + if (signers.size() < minMultiSigners || + 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)}; // We also use the sfAccount field inside the loop. Get it once. - // auto const txnAccountID = getAccountID(sfAccount); + auto const txnAccountID = batchSigner.getAccountID(sfAccount); // Determine whether signatures must be full canonical. - bool const fullyCanonical = true; + bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) || + (requireCanonicalSig == RequireFullyCanonicalSig::yes); // Signers must be in sorted order by AccountID. AccountID lastAccountID(beast::zero); @@ -388,9 +477,9 @@ STTx::checkBatchSign() const { auto const accountID = signer.getAccountID(sfAccount); - // // The account owner may not multisign for themselves. - // if (accountID == txnAccountID) - // return Unexpected("Invalid multisigner."); + // The account owner may not multisign for themselves. + if (accountID == txnAccountID) + return Unexpected("Invalid multisigner."); // No duplicate signers allowed. if (lastAccountID == accountID) @@ -415,6 +504,7 @@ STTx::checkBatchSign() const if (publicKeyType(makeSlice(spk))) { Blob const signature = signer.getFieldVL(sfTxnSignature); + validSig = verify( PublicKey(makeSlice(spk)), s.slice(), diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 245a1a2add4..6c2cc28bd06 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -74,6 +74,31 @@ class Batch_test : public beast::unit_test::suite } } + void + validateBatchMeta( + Json::Value meta, + STAmount const& balance, + std::uint32_t const& sequence) + { + for (Json::Value const& node : meta[sfAffectedNodes.jsonName]) + { + if (node.isMember(sfModifiedNode.jsonName)) + { + Json::Value const& modified = node[sfModifiedNode.jsonName]; + std::string const entryType = + modified[sfLedgerEntryType.jsonName].asString(); + if (entryType == jss::AccountRoot) + { + auto const& previousFields = + modified[sfPreviousFields.jsonName]; + std::uint32_t const prevSeq = + previousFields[sfSequence.jsonName].asUInt(); + BEAST_EXPECT(prevSeq == sequence); + } + } + } + } + Json::Value addBatchTx( Json::Value jv, @@ -87,7 +112,7 @@ class Batch_test : public beast::unit_test::suite jv[sfRawTransactions.jsonName][index] = Json::Value{}; jv[sfRawTransactions.jsonName][index][jss::RawTransaction] = tx; jv[sfRawTransactions.jsonName][index][jss::RawTransaction] - [jss::SigningPubKey] = strHex(pk); + [jss::SigningPubKey] = ""; jv[sfRawTransactions.jsonName][index][jss::RawTransaction] [sfFee.jsonName] = 0; jv[sfRawTransactions.jsonName][index][jss::RawTransaction] @@ -123,6 +148,27 @@ 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) { @@ -430,12 +476,13 @@ class Batch_test : public beast::unit_test::suite jv = addBatchSignatures(jv, signers); - env(jv, ter(terPRE_SEQ)); + // Internally telPRE_SEQ + env(jv, ter(tecBATCH_FAILURE)); env.close(); - // Alice & Bob should not be affected. - BEAST_EXPECT(env.seq(alice) == preAliceSeq); - BEAST_EXPECT(env.balance(alice) == preAlice); + // Alice pays fee & Bob should not be affected. + BEAST_EXPECT(env.seq(alice) == preAliceSeq + 1); + BEAST_EXPECT(env.balance(alice) == preAlice - batchFee); BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD); BEAST_EXPECT(env.seq(bob) == preBobSeq); BEAST_EXPECT(env.balance(bob) == preBob); @@ -554,10 +601,11 @@ class Batch_test : public beast::unit_test::suite Json::Value const tx3 = pay(alice, bob, XRP(1)); jv = addBatchTx(jv, tx3, alice.pk(), alice, 2, seq, 2); + // Internally tefNO_AUTH_REQUIRED env(jv, fee(feeDrops * 3), txflags(tfAllOrNothing), - ter(tefNO_AUTH_REQUIRED)); + ter(tecBATCH_FAILURE)); env.close(); } @@ -585,10 +633,11 @@ class Batch_test : public beast::unit_test::suite Json::Value const tx3 = pay(alice, bob, XRP(1)); jv = addBatchTx(jv, tx3, alice.pk(), alice, 2, seq, 2); + // Internally tefNO_AUTH_REQUIRED env(jv, fee(feeDrops * 3), txflags(tfUntilFailure), - ter(tefNO_AUTH_REQUIRED)); + ter(tecBATCH_FAILURE)); env.close(); } @@ -612,10 +661,11 @@ class Batch_test : public beast::unit_test::suite Json::Value const tx2 = pay(alice, bob, XRP(1)); jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + // Internally tefNO_AUTH_REQUIRED env(jv, fee(feeDrops * 2), txflags(tfOnlyOne), - ter(tefNO_AUTH_REQUIRED)); + ter(tecBATCH_FAILURE)); env.close(); } @@ -639,10 +689,11 @@ class Batch_test : public beast::unit_test::suite Json::Value const tx2 = pay(alice, bob, XRP(1)); jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + // Internally tefNO_AUTH_REQUIRED env(jv, fee(feeDrops * 3), txflags(tfIndependent), - ter(tefNO_AUTH_REQUIRED)); + ter(tecBATCH_FAILURE)); env.close(); } } @@ -695,12 +746,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "3664A012DA8936D0ECF7B84E7447DB88088A2DA9FD61A7BDC4C4DB0CD9944" - "3AC"}, + "3FC47334C663DB77520598095095A7C3AB85C9863E56F5687AD1E78132530" + "0FF"}, {"tesSUCCESS", "Payment", - "44B76513FE9A57E84B837139C1D83A81EB70C88842EC85A561A71F05DF514" - "273"}, + "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5" + "BE5"}, }}; Json::Value params; @@ -711,6 +762,7 @@ class Batch_test : public beast::unit_test::suite // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT( @@ -755,6 +807,15 @@ class Batch_test : public beast::unit_test::suite ter(tecBATCH_FAILURE)); env.close(); + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + auto const txn = getTxByIndex(jrr, 2); + validateBatchMeta(txn[jss::metaData], preAlice, seq); + BEAST_EXPECT(env.seq(alice) == 5); BEAST_EXPECT(env.balance(alice) == preAlice - (feeDrops * 2)); BEAST_EXPECT(env.balance(bob) == preBob); @@ -808,12 +869,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tecUNFUNDED_PAYMENT", "Payment", - "3ACCF055EB1E32947E86B9105E1E71CF6E1DE65D42273DBF006D3AB165DF8B4" - "2"}, + "1C9CBF5AF5D0AA97CDF7AE7175D7BA27FA4DD274CF0B4C650475C635F5DBEFC0"}, {"tesSUCCESS", "Payment", - "44B76513FE9A57E84B837139C1D83A81EB70C88842EC85A561A71F05DF51427" - "3"}, + "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE5"}, }}; Json::Value params; @@ -824,6 +883,7 @@ class Batch_test : public beast::unit_test::suite // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - (feeDrops * 3)); @@ -881,16 +941,13 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "3664A012DA8936D0ECF7B84E7447DB88088A2DA9FD61A7BDC4C4DB0CD99443A" - "C"}, + "3FC47334C663DB77520598095095A7C3AB85C9863E56F5687AD1E781325300FF"}, {"tesSUCCESS", "Payment", - "44B76513FE9A57E84B837139C1D83A81EB70C88842EC85A561A71F05DF51427" - "3"}, + "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE5"}, {"tecUNFUNDED_PAYMENT", "Payment", - "ECBA4387E1C76823E0E3EFA216ECABB991B6B3C175E3D29226086B37241A768" - "2"}, + "68803BEF141614DBBB34FA34BE0E485D79A43328891A9A8BDC461B6F22836A5C"}, }}; Json::Value params; @@ -901,6 +958,7 @@ class Batch_test : public beast::unit_test::suite // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 3); validateBatchTxns(txn[jss::metaData], testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 8); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - (feeDrops * 4)); @@ -958,20 +1016,16 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "3664A012DA8936D0ECF7B84E7447DB88088A2DA9FD61A7BDC4C4DB0CD99443A" - "C"}, + "3FC47334C663DB77520598095095A7C3AB85C9863E56F5687AD1E781325300FF"}, {"tesSUCCESS", "Payment", - "44B76513FE9A57E84B837139C1D83A81EB70C88842EC85A561A71F05DF51427" - "3"}, + "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE5"}, {"tecUNFUNDED_PAYMENT", "Payment", - "ECBA4387E1C76823E0E3EFA216ECABB991B6B3C175E3D29226086B37241A768" - "2"}, + "68803BEF141614DBBB34FA34BE0E485D79A43328891A9A8BDC461B6F22836A5C"}, {"tesSUCCESS", "Payment", - "2A7AEB6F8EF57D2E6104104B1FA125ABA5B12A94116E0829E74B54D0394E749" - "1"}, + "37A717146557951C8B1271843A3255C6A3B3465D2DD2E48FF7EB2670168E7841"}, }}; Json::Value params; @@ -982,6 +1036,7 @@ class Batch_test : public beast::unit_test::suite // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 4); validateBatchTxns(txn[jss::metaData], testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 9); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - (feeDrops * 4)); @@ -989,9 +1044,9 @@ class Batch_test : public beast::unit_test::suite } void - testAtomicSwap(FeatureBitset features) + testAtomicSwapIOU(FeatureBitset features) { - testcase("atomic swap"); + testcase("atomic swap iou"); using namespace test::jtx; using namespace std::literals; @@ -1014,20 +1069,20 @@ 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 preAliceUSD = env.balance(alice, USD.issue()); auto const preBob = env.balance(bob); auto const preBobUSD = env.balance(bob, USD.issue()); std::vector const signers = {{ - {0, alice}, - {1, bob}, + {0, bob}, }}; Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); - jv[jss::Sequence] = env.seq(alice); + jv[jss::Sequence] = seq; auto const batchFee = ((signers.size() + 2) * feeDrops) + feeDrops * 2; jv[jss::Fee] = to_string(batchFee); jv[jss::Flags] = tfAllOrNothing; @@ -1053,12 +1108,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "99C5E1DEBC039D9BADCDB2B786F79F0A72A6E6EE40386BA7485C94B20E6CF8E" - "E"}, + "2071E28FCACC9EBC81E8B94F0A0663F9D808209F2803E02224027C8B9CD57C53"}, {"tesSUCCESS", "Payment", - "C18F28FD9BCEF3EC24FADC20BD25608751AF185B319BDD9EBCEAF2D24200F55" - "C"}, + "F757008AF55CCDE3511016AC2402672A7A08F2F33E1BFA2ED7EE2DD486B6462E"}, }}; Json::Value params; @@ -1069,6 +1122,7 @@ class Batch_test : public beast::unit_test::suite // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.seq(bob) == 7); @@ -1078,6 +1132,241 @@ class Batch_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(5)); } + void + testAtomicSwapXRP(FeatureBitset features) + { + testcase("atomic swap xrp"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + env.fund(XRP(1000), alice, bob); + env.close(); + + env(noop(bob), ter(tesSUCCESS)); + env.close(); + + 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] = 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.pk(), alice, 0, env.seq(alice), 0); + + // Tx 2 + Json::Value const tx2 = pay(bob, alice, XRP(5)); + jv = addBatchTx(jv, tx2, bob.pk(), alice, 0, env.seq(bob), 1); + + jv = addBatchSignatures(jv, signers); + + // env(jv, bsig(alice, bob), ter(tesSUCCESS)); + env(jv, ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "Payment", + "CE9F2CC015613C5A11D1E2B0F9340EDDA977C2AD1321A8C1358EC66E3710BA2" + "4"}, + {"tesSUCCESS", + "Payment", + "9464BBDA1E5893486507DDA75D702739B9FE3DA94D9D002A2DBD3840688AF76" + "6"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + auto const txn = getTxByIndex(jrr, 2); + validateBatchTxns(txn[jss::metaData], testCases); + validateBatchMeta(txn[jss::metaData], STAmount(XRP(1000)), 4); + + BEAST_EXPECT(env.seq(alice) == 6); + BEAST_EXPECT(env.seq(bob) == 6); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(5) - (batchFee)); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(5)); + } + + void + testMultisign(FeatureBitset features) + { + testcase("multisign"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + env(signers(alice, 2, {{bob, 1}, {carol, 1}})); + env.close(); + + 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 const tx1 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + + // Tx 2 + Json::Value const tx2 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + + env(jv, + fee(feeDrops * 2 + (feeDrops * 4)), + txflags(tfAllOrNothing), + msig(bob, carol), + ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "Payment", + "9A6D5FD4DB3EBC179D51F9DA2950474DA093E853E7D0C7446413F5101F8C84E5"}, + {"tesSUCCESS", + "Payment", + "21131DBC8CD39D1A514939F988B56235F33A38BD58762CE0CAF8EFA9489DB327"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + auto const txn = getTxByIndex(jrr, 2); + validateBatchTxns(txn[jss::metaData], testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq); + + BEAST_EXPECT(env.seq(alice) == 8); + BEAST_EXPECT( + env.balance(alice) == preAlice - XRP(2) - (feeDrops * 2 + (feeDrops * 4))); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(2)); + } + + void + testMultisignSwap(FeatureBitset features) + { + testcase("atomic multisign swap"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const dave = Account("dave"); + + env.fund(XRP(1000), alice, bob, carol, dave); + env.close(); + + env(signers(bob, 2, {{carol, 1}, {dave, 1}})); + env.close(); + + 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.pk(), alice, 0, env.seq(alice), 0); + + // Tx 2 + Json::Value const tx2 = pay(bob, alice, XRP(5)); + jv = addBatchTx(jv, tx2, bob.pk(), alice, 0, env.seq(bob), 1); + + jv = addBatchMultiSignatures(jv, 0, bob, signers); + + // env(jv, bsig(alice, bob), ter(tesSUCCESS)); + env(jv, ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "Payment", + "CE9F2CC015613C5A11D1E2B0F9340EDDA977C2AD1321A8C1358EC66E3710BA2" + "4"}, + {"tesSUCCESS", + "Payment", + "9464BBDA1E5893486507DDA75D702739B9FE3DA94D9D002A2DBD3840688AF76" + "6"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + auto const txn = getTxByIndex(jrr, 2); + validateBatchTxns(txn[jss::metaData], testCases); + validateBatchMeta(txn[jss::metaData], STAmount(XRP(1000)), 4); + + BEAST_EXPECT(env.seq(alice) == 6); + BEAST_EXPECT(env.seq(bob) == 6); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(5) - (batchFee)); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(5)); + } + void testAccountSet(FeatureBitset features) { @@ -1654,6 +1943,18 @@ class Batch_test : public beast::unit_test::suite } } + void + testTTs(FeatureBitset features) + { + testAccountSet(features); + testBatch(features); + testCheckCreate(features); + testCheckCash(features); + testCheckCancel(features); + testClawback(features); + // testOfferCancel(features); + } + void testWithFeats(FeatureBitset features) { @@ -1666,15 +1967,10 @@ class Batch_test : public beast::unit_test::suite testOnlyOne(features); testUntilFailure(features); testIndependent(features); - testAtomicSwap(features); - testAccountSet(features); - testBatch(features); - testCheckCreate(features); - testCheckCash(features); - testCheckCancel(features); - testClawback(features); - // testOfferCancel(features); - testSubmit(features); + testAtomicSwapIOU(features); + testAtomicSwapXRP(features); + testMultisign(features); + testMultisignSwap(features); } public: diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 4856cfd8f97..8765f396c06 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -69,7 +69,11 @@ Batch::preflight(PreflightContext const& ctx) if (tx.isFieldPresent(sfBatchSigners)) { - auto const sigResult = ctx.tx.checkBatchSign(); + 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.debug()) << "Batch: invalid batch txn signature."; @@ -135,10 +139,15 @@ Batch::doApply() auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions); for (STObject txn : txns) { + OpenView innerView(&subView); + STTx const stx = STTx{std::move(txn)}; auto const [ter, applied] = - ripple::apply(ctx_.app, subView, stx, tapFAIL_HARD, ctx_.journal); + ripple::apply(ctx_.app, innerView, stx, tapFAIL_HARD, ctx_.journal); + if (applied) + innerView.apply(subView); + changed = true; // Add Inner Txn Metadata @@ -158,7 +167,7 @@ Batch::doApply() if (!isTecClaim(ter)) { accountCount.clear(); - result = ter; + result = tecBATCH_FAILURE; changed = false; break; } @@ -194,19 +203,41 @@ Batch::doApply() ctx_.applyOpenView(subView); } - for (auto const& [_account, count] : accountCount) + // Clean Up + // 1. Update the account_ sfSequence to include any tes/tec inner txns + + // Reason: The sequence (1) is consumed before the inner batch txns + // however we dont know how many of the inner txns will succeed depending + // on the batch type. (This could be moved to `getSeqProxy()`) + + // 2. Set the batch prevFields so they are included in metadata + + // Reason: When the outer batch is applied (at the end), the Sequence and + // Balance have already been updated, therefore there are no PreviousFields + // when adding the metadata. This adds them. { - auto const sleSrcAcc = sb.peek(keylet::account(_account)); + auto const sleSrcAcc = sb.peek(keylet::account(account_)); if (!sleSrcAcc) return tefINTERNAL; - if (_account == account_) + STAmount const txFee = ctx_.tx.getFieldAmount(sfFee); + std::uint32_t const txSeq = ctx_.tx.getFieldU32(sfSequence); + STAmount const accBal = sleSrcAcc->getFieldAmount(sfBalance); + std::uint32_t const accSeq = sleSrcAcc->getFieldU32(sfSequence); + + // only update if the account_ batch has tes/tec inner txns + uint32_t const count = accountCount[account_]; + if (count != 0) { - // Update Sequence (Source Account) - sleSrcAcc->setFieldU32( - sfSequence, sleSrcAcc->getFieldU32(sfSequence) + count); + sleSrcAcc->setFieldU32(sfSequence, accSeq + count); sb.update(sleSrcAcc); } + + // update the batch prev fields + STObject prevFields{sfPreviousFields}; + prevFields.setFieldU32(sfSequence, txSeq); + prevFields.setFieldAmount(sfBalance, accBal + txFee); + avi.addBatchPrevMetaData(std::move(prevFields)); } sb.apply(ctx_.rawView()); diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index a2b80c69bba..fd7e7fec810 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -503,17 +503,20 @@ Transactor::apply() NotTEC Transactor::checkSign(PreclaimContext const& ctx) { + // do not check signature of inner batch txn + if (ctx.tx.isFieldPresent(sfBatchTxn)) + return tesSUCCESS; + + auto const idAccount = ctx.tx.getAccountID(sfAccount); + // If the pk is empty, then we must be multi-signing. if (ctx.tx.getSigningPubKey().empty()) - return checkMultiSign(ctx); - - return checkSingleSign(ctx); -} + { + STArray const& txSigners(ctx.tx.getFieldArray(sfSigners)); + return checkMultiSign(ctx.view, idAccount, txSigners, ctx.j); + } -NotTEC -Transactor::checkSingleSign(PreclaimContext const& ctx) -{ - // Check that the value in the signing key slot is a public key. + // Check Single Sign auto const pkSigner = ctx.tx.getSigningPubKey(); if (!publicKeyType(makeSlice(pkSigner))) { @@ -521,17 +524,55 @@ Transactor::checkSingleSign(PreclaimContext const& ctx) << "checkSingleSign: signing public key type is unknown"; return tefBAD_AUTH; // FIXME: should be better error! } - - // Look up the account. auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner))); - auto const idAccount = ctx.tx.getAccountID(sfAccount); auto const sleAccount = ctx.view.read(keylet::account(idAccount)); if (!sleAccount) return terNO_ACCOUNT; + return checkSingleSign( + idSigner, idAccount, sleAccount, ctx.view.rules(), ctx.j); +} + +NotTEC +Transactor::checkBatchSign(PreclaimContext const& ctx) +{ + NotTEC ret = tesSUCCESS; + STArray const& signers{ctx.tx.getFieldArray(sfBatchSigners)}; + for (auto const& signer : signers) + { + auto const idAccount = signer.getAccountID(sfAccount); + + Blob const& pkSigner = signer.getFieldVL(sfSigningPubKey); + if (pkSigner.empty()) + { + STArray const& txSigners(signer.getFieldArray(sfSigners)); + ret = checkMultiSign(ctx.view, idAccount, txSigners, ctx.j); + } else { + if (!publicKeyType(makeSlice(pkSigner))) + ret = tefBAD_AUTH; + + auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner))); + auto const sleAccount = ctx.view.read(keylet::account(idAccount)); + if (!sleAccount) + ret = terNO_ACCOUNT; + + ret = checkSingleSign(idSigner, idAccount, sleAccount, ctx.view.rules(), ctx.j); + } + } + return ret; +} + +NotTEC +Transactor::checkSingleSign( + AccountID const& idSigner, + AccountID const& idAccount, + std::shared_ptr sleAccount, + Rules const& rules, + beast::Journal j) +{ bool const isMasterDisabled = sleAccount->isFlag(lsfDisableMaster); - if (ctx.view.rules().enabled(fixMasterKeyAsRegularKey)) + if (rules.enabled(fixMasterKeyAsRegularKey)) { // Signed with regular key. if ((*sleAccount)[~sfRegularKey] == idSigner) @@ -568,7 +609,7 @@ Transactor::checkSingleSign(PreclaimContext const& ctx) else if (sleAccount->isFieldPresent(sfRegularKey)) { // Signing key does not match master or regular key. - JLOG(ctx.j.trace()) + JLOG(j.trace()) << "checkSingleSign: Not authorized to use account."; return tefBAD_AUTH; } @@ -576,7 +617,7 @@ Transactor::checkSingleSign(PreclaimContext const& ctx) { // No regular key on account and signing key does not match master key. // FIXME: Why differentiate this case from tefBAD_AUTH? - JLOG(ctx.j.trace()) + JLOG(j.trace()) << "checkSingleSign: Not authorized to use account."; return tefBAD_AUTH_MASTER; } @@ -585,16 +626,19 @@ Transactor::checkSingleSign(PreclaimContext const& ctx) } NotTEC -Transactor::checkMultiSign(PreclaimContext const& ctx) +Transactor::checkMultiSign( + ReadView const& view, + AccountID const& id, + STArray const& txSigners, + beast::Journal j) { - auto const id = ctx.tx.getAccountID(sfAccount); // Get mTxnAccountID's SignerList and Quorum. std::shared_ptr sleAccountSigners = - ctx.view.read(keylet::signers(id)); + view.read(keylet::signers(id)); // If the signer list doesn't exist the account is not multi-signing. if (!sleAccountSigners) { - JLOG(ctx.j.trace()) + JLOG(j.trace()) << "applyTransaction: Invalid: Not a multi-signing account."; return tefNOT_MULTI_SIGNING; } @@ -605,12 +649,11 @@ Transactor::checkMultiSign(PreclaimContext const& ctx) assert(sleAccountSigners->getFieldU32(sfSignerListID) == 0); auto accountSigners = - SignerEntries::deserialize(*sleAccountSigners, ctx.j, "ledger"); + SignerEntries::deserialize(*sleAccountSigners, j, "ledger"); if (!accountSigners) return accountSigners.error(); // Get the array of transaction signers. - STArray const& txSigners(ctx.tx.getFieldArray(sfSigners)); // Walk the accountSigners performing a variety of checks and see if // the quorum is met. @@ -629,7 +672,7 @@ Transactor::checkMultiSign(PreclaimContext const& ctx) { if (++iter == accountSigners->end()) { - JLOG(ctx.j.trace()) + JLOG(j.trace()) << "applyTransaction: Invalid SigningAccount.Account."; return tefBAD_SIGNATURE; } @@ -637,7 +680,7 @@ Transactor::checkMultiSign(PreclaimContext const& ctx) if (iter->account != txSignerAcctID) { // The SigningAccount is not in the SignerEntries. - JLOG(ctx.j.trace()) + JLOG(j.trace()) << "applyTransaction: Invalid SigningAccount.Account."; return tefBAD_SIGNATURE; } @@ -649,7 +692,7 @@ Transactor::checkMultiSign(PreclaimContext const& ctx) if (!publicKeyType(makeSlice(spk))) { - JLOG(ctx.j.trace()) + JLOG(j.trace()) << "checkMultiSign: signing public key type is unknown"; return tefBAD_SIGNATURE; } @@ -682,7 +725,7 @@ Transactor::checkMultiSign(PreclaimContext const& ctx) // In any of these cases we need to know whether the account is in // the ledger. Determine that now. - auto sleTxSignerRoot = ctx.view.read(keylet::account(txSignerAcctID)); + auto sleTxSignerRoot = view.read(keylet::account(txSignerAcctID)); if (signingAcctIDFromPubKey == txSignerAcctID) { @@ -695,7 +738,7 @@ Transactor::checkMultiSign(PreclaimContext const& ctx) if (signerAccountFlags & lsfDisableMaster) { - JLOG(ctx.j.trace()) + JLOG(j.trace()) << "applyTransaction: Signer:Account lsfDisableMaster."; return tefMASTER_DISABLED; } @@ -707,21 +750,21 @@ Transactor::checkMultiSign(PreclaimContext const& ctx) // Public key must hash to the account's regular key. if (!sleTxSignerRoot) { - JLOG(ctx.j.trace()) << "applyTransaction: Non-phantom signer " + JLOG(j.trace()) << "applyTransaction: Non-phantom signer " "lacks account root."; return tefBAD_SIGNATURE; } if (!sleTxSignerRoot->isFieldPresent(sfRegularKey)) { - JLOG(ctx.j.trace()) + JLOG(j.trace()) << "applyTransaction: Account lacks RegularKey."; return tefBAD_SIGNATURE; } if (signingAcctIDFromPubKey != sleTxSignerRoot->getAccountID(sfRegularKey)) { - JLOG(ctx.j.trace()) + JLOG(j.trace()) << "applyTransaction: Account doesn't match RegularKey."; return tefBAD_SIGNATURE; } @@ -733,7 +776,7 @@ Transactor::checkMultiSign(PreclaimContext const& ctx) // Cannot perform transaction if quorum is not met. if (weightSum < sleAccountSigners->getFieldU32(sfSignerQuorum)) { - JLOG(ctx.j.trace()) + JLOG(j.trace()) << "applyTransaction: Signers failed to meet quorum."; return tefBAD_QUORUM; } diff --git a/src/xrpld/app/tx/detail/Transactor.h b/src/xrpld/app/tx/detail/Transactor.h index c587e5e1994..6904ff6a453 100644 --- a/src/xrpld/app/tx/detail/Transactor.h +++ b/src/xrpld/app/tx/detail/Transactor.h @@ -136,6 +136,9 @@ class Transactor static NotTEC checkSign(PreclaimContext const& ctx); + + static NotTEC + checkBatchSign(PreclaimContext const& ctx); // Returns the fee in fee units, not scaled for load. static XRPAmount @@ -195,9 +198,20 @@ class Transactor TER payFee(); static NotTEC - checkSingleSign(PreclaimContext const& ctx); + checkSingleSign( + AccountID const& idSigner, + AccountID const& idAccount, + std::shared_ptr sleAccount, + Rules const& rules, + beast::Journal j + ); static NotTEC - checkMultiSign(PreclaimContext const& ctx); + checkMultiSign( + ReadView const& view, + AccountID const& idAccount, + STArray const& txSigners, + beast::Journal j + ); void trapTransaction(uint256) const; }; diff --git a/src/xrpld/app/tx/detail/applySteps.cpp b/src/xrpld/app/tx/detail/applySteps.cpp index 791d56b8564..4c97fa2f77a 100644 --- a/src/xrpld/app/tx/detail/applySteps.cpp +++ b/src/xrpld/app/tx/detail/applySteps.cpp @@ -271,6 +271,9 @@ invoke_preclaim(PreclaimContext const& ctx) result = T::checkSign(ctx); + if (ctx.tx.getTxnType() == ttBATCH) + result = T::checkBatchSign(ctx); + if (result != tesSUCCESS) return result; } diff --git a/src/xrpld/ledger/ApplyViewImpl.h b/src/xrpld/ledger/ApplyViewImpl.h index dcce178867a..aeebd026c04 100644 --- a/src/xrpld/ledger/ApplyViewImpl.h +++ b/src/xrpld/ledger/ApplyViewImpl.h @@ -77,6 +77,12 @@ class ApplyViewImpl final : public detail::ApplyViewBase /* Set hook metadata for a hook execution * Takes ownership / use std::move */ + void + addBatchPrevMetaData(STObject const& prevFields) + { + batchPrev_ = prevFields; + } + void addBatchExecutionMetaData(STObject&& batchExecution) { @@ -117,6 +123,7 @@ class ApplyViewImpl final : public detail::ApplyViewBase private: std::optional deliver_; std::vector batchExecution_; + std::optional batchPrev_; }; } // namespace ripple diff --git a/src/xrpld/ledger/detail/ApplyStateTable.cpp b/src/xrpld/ledger/detail/ApplyStateTable.cpp index fabcc74ff79..46c142c3881 100644 --- a/src/xrpld/ledger/detail/ApplyStateTable.cpp +++ b/src/xrpld/ledger/detail/ApplyStateTable.cpp @@ -116,6 +116,7 @@ ApplyStateTable::apply( TER ter, std::optional const& deliver, std::vector const& batchExecution, + std::optional const& batchPrev, beast::Journal j) { // Build metadata and insert @@ -213,6 +214,13 @@ ApplyStateTable::apply( prevs.emplace_back(obj); } + if (tx.getTxnType() == ttBATCH && nodeType == ltACCOUNT_ROOT) + { + // TODO: This could fail if the fields already exist + for (auto const& obj : *batchPrev) + prevs.emplace_back(obj); + } + if (!prevs.empty()) meta.getAffectedNode(item.first) .emplace_back(std::move(prevs)); diff --git a/src/xrpld/ledger/detail/ApplyStateTable.h b/src/xrpld/ledger/detail/ApplyStateTable.h index b42f590831c..d4cd78522a8 100644 --- a/src/xrpld/ledger/detail/ApplyStateTable.h +++ b/src/xrpld/ledger/detail/ApplyStateTable.h @@ -71,6 +71,7 @@ class ApplyStateTable TER ter, std::optional const& deliver, std::vector const& batchExecution, + std::optional const& batchPrev, beast::Journal j); bool diff --git a/src/xrpld/ledger/detail/ApplyViewImpl.cpp b/src/xrpld/ledger/detail/ApplyViewImpl.cpp index 528e6715d14..dd3b4c85d62 100644 --- a/src/xrpld/ledger/detail/ApplyViewImpl.cpp +++ b/src/xrpld/ledger/detail/ApplyViewImpl.cpp @@ -31,7 +31,7 @@ ApplyViewImpl::ApplyViewImpl(ReadView const* base, ApplyFlags flags) void ApplyViewImpl::apply(OpenView& to, STTx const& tx, TER ter, beast::Journal j) { - items_.apply(to, tx, ter, deliver_, batchExecution_, j); + items_.apply(to, tx, ter, deliver_, batchExecution_, batchPrev_, j); } std::size_t From 0d5242ed6f151db74be954ce52b6bcac47600dd9 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 6 Sep 2024 00:29:22 +0200 Subject: [PATCH 34/71] clang-format --- include/xrpl/protocol/STTx.h | 7 +-- src/test/app/Batch_test.cpp | 71 +++++++++++++++++--------- src/xrpld/app/tx/detail/Batch.cpp | 7 +-- src/xrpld/app/tx/detail/Transactor.cpp | 18 +++---- src/xrpld/app/tx/detail/Transactor.h | 8 ++- 5 files changed, 67 insertions(+), 44 deletions(-) diff --git a/include/xrpl/protocol/STTx.h b/include/xrpl/protocol/STTx.h index ec1013aebef..627a3518d42 100644 --- a/include/xrpl/protocol/STTx.h +++ b/include/xrpl/protocol/STTx.h @@ -120,14 +120,15 @@ class STTx final : public STObject, public CountedObject @return `true` if valid signature. If invalid, the error message string. */ enum class RequireFullyCanonicalSig : bool { no, yes }; - + Expected checkSign(RequireFullyCanonicalSig requireCanonicalSig, Rules const& rules) const; Expected - checkBatchSign(RequireFullyCanonicalSig requireCanonicalSig, Rules const& rules) - const; + checkBatchSign( + RequireFullyCanonicalSig requireCanonicalSig, + Rules const& rules) const; // SQL Functions with metadata. static std::string const& diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 6c2cc28bd06..085bb988148 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -149,23 +149,32 @@ class Batch_test : public beast::unit_test::suite } Json::Value - addBatchMultiSignatures(Json::Value jv, int index, jtx::Account account, std::vector const& signers) + 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())}; + 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()}); + 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; + jv[sfBatchSigners.jsonName][index][sfBatchSigner.jsonName] + [sfAccount.jsonName] = account.human(); + jv[sfBatchSigners.jsonName][index][sfBatchSigner.jsonName] + [sfSigners.jsonName] = jvSigners; return jv; } @@ -869,10 +878,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tecUNFUNDED_PAYMENT", "Payment", - "1C9CBF5AF5D0AA97CDF7AE7175D7BA27FA4DD274CF0B4C650475C635F5DBEFC0"}, + "1C9CBF5AF5D0AA97CDF7AE7175D7BA27FA4DD274CF0B4C650475C635F5DBEFC" + "0"}, {"tesSUCCESS", "Payment", - "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE5"}, + "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE" + "5"}, }}; Json::Value params; @@ -941,13 +952,16 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "3FC47334C663DB77520598095095A7C3AB85C9863E56F5687AD1E781325300FF"}, + "3FC47334C663DB77520598095095A7C3AB85C9863E56F5687AD1E781325300F" + "F"}, {"tesSUCCESS", "Payment", - "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE5"}, + "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE" + "5"}, {"tecUNFUNDED_PAYMENT", "Payment", - "68803BEF141614DBBB34FA34BE0E485D79A43328891A9A8BDC461B6F22836A5C"}, + "68803BEF141614DBBB34FA34BE0E485D79A43328891A9A8BDC461B6F22836A5" + "C"}, }}; Json::Value params; @@ -1016,16 +1030,20 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "3FC47334C663DB77520598095095A7C3AB85C9863E56F5687AD1E781325300FF"}, + "3FC47334C663DB77520598095095A7C3AB85C9863E56F5687AD1E781325300F" + "F"}, {"tesSUCCESS", "Payment", - "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE5"}, + "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE" + "5"}, {"tecUNFUNDED_PAYMENT", "Payment", - "68803BEF141614DBBB34FA34BE0E485D79A43328891A9A8BDC461B6F22836A5C"}, + "68803BEF141614DBBB34FA34BE0E485D79A43328891A9A8BDC461B6F22836A5" + "C"}, {"tesSUCCESS", "Payment", - "37A717146557951C8B1271843A3255C6A3B3465D2DD2E48FF7EB2670168E7841"}, + "37A717146557951C8B1271843A3255C6A3B3465D2DD2E48FF7EB2670168E784" + "1"}, }}; Json::Value params; @@ -1108,10 +1126,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "2071E28FCACC9EBC81E8B94F0A0663F9D808209F2803E02224027C8B9CD57C53"}, + "2071E28FCACC9EBC81E8B94F0A0663F9D808209F2803E02224027C8B9CD57C5" + "3"}, {"tesSUCCESS", "Payment", - "F757008AF55CCDE3511016AC2402672A7A08F2F33E1BFA2ED7EE2DD486B6462E"}, + "F757008AF55CCDE3511016AC2402672A7A08F2F33E1BFA2ED7EE2DD486B6462" + "E"}, }}; Json::Value params; @@ -1261,11 +1281,13 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", - "Payment", - "9A6D5FD4DB3EBC179D51F9DA2950474DA093E853E7D0C7446413F5101F8C84E5"}, + "Payment", + "9A6D5FD4DB3EBC179D51F9DA2950474DA093E853E7D0C7446413F5101F8C84E" + "5"}, {"tesSUCCESS", - "Payment", - "21131DBC8CD39D1A514939F988B56235F33A38BD58762CE0CAF8EFA9489DB327"}, + "Payment", + "21131DBC8CD39D1A514939F988B56235F33A38BD58762CE0CAF8EFA9489DB32" + "7"}, }}; Json::Value params; @@ -1280,7 +1302,8 @@ class Batch_test : public beast::unit_test::suite BEAST_EXPECT(env.seq(alice) == 8); BEAST_EXPECT( - env.balance(alice) == preAlice - XRP(2) - (feeDrops * 2 + (feeDrops * 4))); + env.balance(alice) == + preAlice - XRP(2) - (feeDrops * 2 + (feeDrops * 4))); BEAST_EXPECT(env.balance(bob) == preBob + XRP(2)); } diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 8765f396c06..2bf4f24dc7b 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -73,7 +73,8 @@ Batch::preflight(PreflightContext const& ctx) ctx.rules.enabled(featureRequireFullyCanonicalSig) ? STTx::RequireFullyCanonicalSig::yes : STTx::RequireFullyCanonicalSig::no; - auto const sigResult = ctx.tx.checkBatchSign(requireCanonicalSig, ctx.rules); + auto const sigResult = + ctx.tx.checkBatchSign(requireCanonicalSig, ctx.rules); if (!sigResult) { JLOG(ctx.j.debug()) << "Batch: invalid batch txn signature."; @@ -147,7 +148,7 @@ Batch::doApply() if (applied) innerView.apply(subView); - + changed = true; // Add Inner Txn Metadata @@ -224,7 +225,7 @@ Batch::doApply() std::uint32_t const txSeq = ctx_.tx.getFieldU32(sfSequence); STAmount const accBal = sleSrcAcc->getFieldAmount(sfBalance); std::uint32_t const accSeq = sleSrcAcc->getFieldU32(sfSequence); - + // only update if the account_ batch has tes/tec inner txns uint32_t const count = accountCount[account_]; if (count != 0) diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index fd7e7fec810..cb30d3ee877 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -547,7 +547,9 @@ Transactor::checkBatchSign(PreclaimContext const& ctx) { STArray const& txSigners(signer.getFieldArray(sfSigners)); ret = checkMultiSign(ctx.view, idAccount, txSigners, ctx.j); - } else { + } + else + { if (!publicKeyType(makeSlice(pkSigner))) ret = tefBAD_AUTH; @@ -556,7 +558,8 @@ Transactor::checkBatchSign(PreclaimContext const& ctx) if (!sleAccount) ret = terNO_ACCOUNT; - ret = checkSingleSign(idSigner, idAccount, sleAccount, ctx.view.rules(), ctx.j); + ret = checkSingleSign( + idSigner, idAccount, sleAccount, ctx.view.rules(), ctx.j); } } return ret; @@ -609,16 +612,14 @@ Transactor::checkSingleSign( else if (sleAccount->isFieldPresent(sfRegularKey)) { // Signing key does not match master or regular key. - JLOG(j.trace()) - << "checkSingleSign: Not authorized to use account."; + JLOG(j.trace()) << "checkSingleSign: Not authorized to use account."; return tefBAD_AUTH; } else { // No regular key on account and signing key does not match master key. // FIXME: Why differentiate this case from tefBAD_AUTH? - JLOG(j.trace()) - << "checkSingleSign: Not authorized to use account."; + JLOG(j.trace()) << "checkSingleSign: Not authorized to use account."; return tefBAD_AUTH_MASTER; } @@ -751,7 +752,7 @@ Transactor::checkMultiSign( if (!sleTxSignerRoot) { JLOG(j.trace()) << "applyTransaction: Non-phantom signer " - "lacks account root."; + "lacks account root."; return tefBAD_SIGNATURE; } @@ -776,8 +777,7 @@ Transactor::checkMultiSign( // Cannot perform transaction if quorum is not met. if (weightSum < sleAccountSigners->getFieldU32(sfSignerQuorum)) { - JLOG(j.trace()) - << "applyTransaction: Signers failed to meet quorum."; + JLOG(j.trace()) << "applyTransaction: Signers failed to meet quorum."; return tefBAD_QUORUM; } diff --git a/src/xrpld/app/tx/detail/Transactor.h b/src/xrpld/app/tx/detail/Transactor.h index 6904ff6a453..c7a0c7e50d0 100644 --- a/src/xrpld/app/tx/detail/Transactor.h +++ b/src/xrpld/app/tx/detail/Transactor.h @@ -136,7 +136,7 @@ class Transactor static NotTEC checkSign(PreclaimContext const& ctx); - + static NotTEC checkBatchSign(PreclaimContext const& ctx); @@ -203,15 +203,13 @@ class Transactor AccountID const& idAccount, std::shared_ptr sleAccount, Rules const& rules, - beast::Journal j - ); + beast::Journal j); static NotTEC checkMultiSign( ReadView const& view, AccountID const& idAccount, STArray const& txSigners, - beast::Journal j - ); + beast::Journal j); void trapTransaction(uint256) const; }; From ff7fa2650b8e8dc7d309b636ef2f4a835b1d1565 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 6 Sep 2024 00:50:15 +0200 Subject: [PATCH 35/71] add `sfBatchResult` --- include/xrpl/protocol/SField.h | 1 + src/libxrpl/protocol/InnerObjectFormats.cpp | 2 +- src/libxrpl/protocol/SField.cpp | 1 + src/test/app/Batch_test.cpp | 2 +- src/xrpld/app/tx/detail/Batch.cpp | 3 ++- 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index 3dcfdae1528..7309ae76a81 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -577,6 +577,7 @@ extern SF_VL const sfHookStateData; extern SF_VL const sfHookReturnString; extern SF_VL const sfHookParameterName; extern SF_VL const sfHookParameterValue; +extern SF_VL const sfBatchResult; // account extern SF_ACCOUNT const sfAccount; diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index a4881f298d9..86b9938497c 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -151,7 +151,7 @@ InnerObjectFormats::InnerObjectFormats() add(sfBatchExecution.jsonName.c_str(), sfBatchExecution.getCode(), {{sfTransactionType, soeREQUIRED}, - {sfTransactionResult, soeREQUIRED}, + {sfBatchResult, soeREQUIRED}, {sfTransactionHash, soeOPTIONAL}}); add(sfBatchTxn.jsonName.c_str(), diff --git a/src/libxrpl/protocol/SField.cpp b/src/libxrpl/protocol/SField.cpp index 0b9941ab238..832259dd21a 100644 --- a/src/libxrpl/protocol/SField.cpp +++ b/src/libxrpl/protocol/SField.cpp @@ -308,6 +308,7 @@ CONSTRUCT_TYPED_SFIELD(sfDIDDocument, "DIDDocument", VL, CONSTRUCT_TYPED_SFIELD(sfData, "Data", VL, 27); CONSTRUCT_TYPED_SFIELD(sfAssetClass, "AssetClass", VL, 28); CONSTRUCT_TYPED_SFIELD(sfProvider, "Provider", VL, 29); +CONSTRUCT_TYPED_SFIELD(sfBatchResult, "BatchResult", VL, 30); // account CONSTRUCT_TYPED_SFIELD(sfAccount, "Account", ACCOUNT, 1); diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 085bb988148..c5ab80fb851 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -64,7 +64,7 @@ class Batch_test : public beast::unit_test::suite { auto const b = _batchTxn[sfBatchExecution.jsonName]; BEAST_EXPECT( - b[sfTransactionResult.jsonName] == batchResults[index].result); + b[sfBatchResult.jsonName] == strHex(batchResults[index].result)); BEAST_EXPECT( b[sfTransactionType.jsonName] == batchResults[index].txType); if (batchResults[index].hash != "") diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 2bf4f24dc7b..a879651b280 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -153,7 +153,8 @@ Batch::doApply() // Add Inner Txn Metadata STObject meta{sfBatchExecution}; - meta.setFieldU8(sfTransactionResult, TERtoInt(ter)); + std::string res = transToken(ter); + meta.setFieldVL(sfBatchResult, ripple::Slice{res.data(), res.size()}); meta.setFieldU16(sfTransactionType, stx.getTxnType()); if (ter == tesSUCCESS || isTecClaim(ter)) meta.setFieldH256(sfTransactionHash, stx.getTransactionID()); From a78da6aa529b980e2da1d5b5499b8638edef4cdd Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 10 Sep 2024 11:00:28 +0200 Subject: [PATCH 36/71] ticket sequence --- include/xrpl/protocol/jss.h | 1 + src/libxrpl/protocol/InnerObjectFormats.cpp | 1 + src/libxrpl/protocol/STTx.cpp | 15 +- src/test/app/Batch_test.cpp | 679 ++++++++------------ src/xrpld/app/tx/detail/ApplyContext.cpp | 72 ++- src/xrpld/app/tx/detail/ApplyContext.h | 6 +- src/xrpld/app/tx/detail/Batch.cpp | 45 +- src/xrpld/app/tx/detail/Transactor.cpp | 2 +- src/xrpld/ledger/detail/ApplyStateTable.cpp | 2 +- 9 files changed, 362 insertions(+), 461 deletions(-) diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index 34a353f2459..e0743e1ba74 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -131,6 +131,7 @@ JSS(Provider); // field. JSS(QuoteAsset); // in: Oracle. JSS(RippleState); // ledger type. JSS(RawTransaction); // in: Batch +JSS(RawTransactions); // in: Batch JSS(SLE_hit_rate); // out: GetCounts. JSS(SetFee); // transaction type. JSS(UNLModify); // transaction type. diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 86b9938497c..36377b89506 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -158,6 +158,7 @@ InnerObjectFormats::InnerObjectFormats() sfBatchTxn.getCode(), {{sfOuterAccount, soeREQUIRED}, {sfSequence, soeOPTIONAL}, + {sfTicketSequence, soeOPTIONAL}, {sfBatchIndex, soeREQUIRED}}); add(sfBatchSigner.jsonName.c_str(), diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 93d61e091c2..f49f9f092fb 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -192,13 +192,16 @@ STTx::getSeqProxy() const STObject const batchTxn = const_cast(*this) .getField(sfBatchTxn) .downcast(); - std::uint32_t const startSequence{batchTxn.getFieldU32(sfSequence)}; + std::uint32_t const batchIndex{batchTxn.getFieldU8(sfBatchIndex)}; - std::uint8_t const sourceAdj = - getAccountID(sfAccount) == batchTxn.getAccountID(sfOuterAccount) - ? 1 - : 0; - return SeqProxy::sequence(startSequence + batchIndex + sourceAdj); + if (batchTxn.isFieldPresent(sfTicketSequence)) + { + std::uint32_t const ticketSeq{ + batchTxn.getFieldU32(sfTicketSequence)}; + return SeqProxy{SeqProxy::ticket, ticketSeq + batchIndex}; + } + std::uint32_t const startSequence{batchTxn.getFieldU32(sfSequence)}; + return SeqProxy::sequence(startSequence + batchIndex); } std::optional const ticketSeq{operator[](~sfTicketSequence)}; diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index c5ab80fb851..2c00af1db21 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -99,32 +99,38 @@ class Batch_test : public beast::unit_test::suite } } - Json::Value + Json::Value addBatchTx( Json::Value jv, Json::Value const& tx, - PublicKey const& pk, - jtx::Account const& account, - std::uint8_t innerIndex, - std::uint32_t outerSequence, - std::uint8_t index) + jtx::Account const& outerAccount, + std::uint8_t batchIndex, + std::uint32_t sequence, + std::optional ticket = std::nullopt) { - jv[sfRawTransactions.jsonName][index] = Json::Value{}; - jv[sfRawTransactions.jsonName][index][jss::RawTransaction] = tx; - jv[sfRawTransactions.jsonName][index][jss::RawTransaction] - [jss::SigningPubKey] = ""; - jv[sfRawTransactions.jsonName][index][jss::RawTransaction] - [sfFee.jsonName] = 0; - jv[sfRawTransactions.jsonName][index][jss::RawTransaction] - [jss::Sequence] = 0; - jv[sfRawTransactions.jsonName][index][jss::RawTransaction] - [sfBatchTxn.jsonName] = Json::Value{}; - jv[sfRawTransactions.jsonName][index][jss::RawTransaction] - [sfBatchTxn.jsonName][jss::OuterAccount] = account.human(); - jv[sfRawTransactions.jsonName][index][jss::RawTransaction] - [sfBatchTxn.jsonName][sfSequence.jsonName] = outerSequence; - jv[sfRawTransactions.jsonName][index][jss::RawTransaction] - [sfBatchTxn.jsonName][sfBatchIndex.jsonName] = innerIndex; + std::uint32_t const index = jv[jss::RawTransactions].size(); + Json::Value& batchTransaction = jv[jss::RawTransactions][index]; + + // Initialize the batch transaction + batchTransaction = Json::Value{}; + batchTransaction[jss::RawTransaction] = tx; + 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] = outerAccount.human(); + batchTxn[sfBatchIndex.jsonName] = batchIndex; + batchTxn[sfSequence.jsonName] = sequence; + + // Optionally set ticket sequence + if (ticket.has_value()) + { + batchTxn[sfTicketSequence.jsonName] = *ticket; + } + return jv; } @@ -211,7 +217,7 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + // jv = addBatchTx(jv, tx1, alice, 0, seq); auto const txResult = withBatch ? ter(tesSUCCESS) : ter(temDISABLED); @@ -280,7 +286,7 @@ class Batch_test : public beast::unit_test::suite for (std::uint8_t i = 0; i < 13; ++i) { Json::Value const tx = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx, alice.pk(), alice, i, seq, i); + // jv = addBatchTx(jv, tx, alice, i, seq); } env(jv, ter(temMALFORMED)); @@ -290,8 +296,7 @@ class Batch_test : public beast::unit_test::suite // temBAD_SIGNATURE: Batch: invalid batch txn signature. { std::vector const signers = {{ - {0, alice}, - {1, carol}, + {0, carol}, }}; Json::Value jv; @@ -309,11 +314,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, XRP(10)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, env.seq(alice), 0); + // jv = addBatchTx(jv, tx1, alice, 0, env.seq(alice)); // Tx 2 Json::Value const tx2 = pay(bob, alice, XRP(5)); - jv = addBatchTx(jv, tx2, bob.pk(), alice, 0, env.seq(bob), 1); + // // jv = addBatchTx(jv, tx2, alice, 1, env.seq(bob)); for (auto const& signer : signers) { @@ -347,7 +352,7 @@ class Batch_test : public beast::unit_test::suite // // Tx 1 // Json::Value tx1 = pay(alice, bob, XRP(10)); - // jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + // // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); // jv[sfRawTransactions.jsonName][0u][jss::RawTransaction].removeMember( // jss::TransactionType); @@ -378,14 +383,14 @@ class Batch_test : public beast::unit_test::suite // bTx 1 Json::Value const btx1 = pay(alice, bob, XRP(1)); - btx = addBatchTx(btx, btx1, alice.pk(), alice, 0, seq, 0); + btx = addBatchTx(btx, btx1, alice, 0, seq, 0); } - jv = addBatchTx(jv, btx, alice.pk(), alice, 0, seq, 0); + // // jv = addBatchTx(jv, btx, alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + // // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); env(jv, ter(temMALFORMED)); env.close(); @@ -394,8 +399,7 @@ class Batch_test : public beast::unit_test::suite // temBAD_SIGNER: Batch: inner txn not signed by the right user. { std::vector const signers = {{ - {0, alice}, - {1, carol}, + {0, carol}, }}; Json::Value jv; @@ -413,11 +417,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, XRP(10)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, env.seq(alice), 0); + // // 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, bob.pk(), alice, 0, env.seq(bob), 1); + // // jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob), 1); jv = addBatchSignatures(jv, signers); env(jv, ter(temBAD_SIGNER)); @@ -459,8 +463,7 @@ class Batch_test : public beast::unit_test::suite auto const preBobUSD = env.balance(bob, USD.issue()); std::vector const signers = {{ - {0, alice}, - {1, bob}, + {0, bob}, }}; Json::Value jv; @@ -477,11 +480,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, USD(10)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, preAliceSeq, 0); + // // jv = addBatchTx(jv, tx1, alice, 0, preAliceSeq, 0); // Tx 2 Json::Value const tx2 = pay(bob, alice, USD(5)); - jv = addBatchTx(jv, tx2, bob.pk(), alice, 10, preBobSeq, 1); + // // jv = addBatchTx(jv, tx2, alice, 10, preBobSeq, 1); jv = addBatchSignatures(jv, signers); @@ -532,8 +535,7 @@ class Batch_test : public beast::unit_test::suite auto const preBobUSD = env.balance(bob, USD.issue()); std::vector const signers = {{ - {0, alice}, - {1, bob}, + {0, bob}, }}; Json::Value jv; @@ -549,11 +551,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, USD(10)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, env.seq(alice), 0); + // // jv = addBatchTx(jv, tx1, alice, 0, env.seq(alice), 0); // Tx 2 Json::Value const tx2 = pay(bob, alice, USD(5)); - jv = addBatchTx(jv, tx2, bob.pk(), alice, 0, env.seq(bob), 1); + // // jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob), 1); jv = addBatchSignatures(jv, signers); @@ -599,16 +601,16 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + // // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = jtx::trust(alice, alice["USD"](1000), tfSetfAuth); - jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + // // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx3, alice.pk(), alice, 2, seq, 2); + // // jv = addBatchTx(jv, tx3, alice, 2, seq, 2); // Internally tefNO_AUTH_REQUIRED env(jv, @@ -631,16 +633,16 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + // // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = jtx::trust(alice, alice["USD"](1000), tfSetfAuth); - jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + // // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx3, alice.pk(), alice, 2, seq, 2); + // // jv = addBatchTx(jv, tx3, alice, 2, seq, 2); // Internally tefNO_AUTH_REQUIRED env(jv, @@ -664,11 +666,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = jtx::trust(alice, alice["USD"](1000), tfSetfAuth); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + // // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + // // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); // Internally tefNO_AUTH_REQUIRED env(jv, @@ -692,11 +694,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = jtx::trust(alice, alice["USD"](1000), tfSetfAuth); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + // // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + // // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); // Internally tefNO_AUTH_REQUIRED env(jv, @@ -740,11 +742,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); env(jv, fee(feeDrops * 2), @@ -804,11 +806,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); env(jv, fee(feeDrops * 2), @@ -862,15 +864,15 @@ class Batch_test : public beast::unit_test::suite // Tx 2 Json::Value const tx1 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx3, alice.pk(), alice, 2, seq, 2); + // jv = addBatchTx(jv, tx3, alice, 2, seq, 2); env(jv, fee(feeDrops * 3), txflags(tfOnlyOne), ter(tesSUCCESS)); env.close(); @@ -932,19 +934,19 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx3, alice.pk(), alice, 2, seq, 2); + // jv = addBatchTx(jv, tx3, alice, 2, seq, 2); // Tx 4 Json::Value const tx4 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx4, alice.pk(), alice, 3, seq, 3); + // jv = addBatchTx(jv, tx4, alice, 3, seq, 3); env(jv, fee(feeDrops * 4), txflags(tfUntilFailure), ter(tesSUCCESS)); env.close(); @@ -1010,19 +1012,19 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(999)); - jv = addBatchTx(jv, tx3, alice.pk(), alice, 2, seq, 2); + // jv = addBatchTx(jv, tx3, alice, 2, seq, 2); // Tx 4 Json::Value const tx4 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx4, alice.pk(), alice, 3, seq, 3); + // jv = addBatchTx(jv, tx4, alice, 3, seq, 3); env(jv, fee(feeDrops * 4), txflags(tfIndependent), ter(tesSUCCESS)); env.close(); @@ -1062,100 +1064,9 @@ class Batch_test : public beast::unit_test::suite } void - testAtomicSwapIOU(FeatureBitset features) + testMultiParty(FeatureBitset features) { - testcase("atomic swap iou"); - - using namespace test::jtx; - using namespace std::literals; - - test::jtx::Env env{*this, envconfig()}; - - auto const feeDrops = env.current()->fees().base; - auto const alice = Account("alice"); - auto const bob = Account("bob"); - auto const gw = Account("gw"); - auto const USD = gw["USD"]; - - env.fund(XRP(1000), alice, bob, gw); - env.close(); - env.trust(USD(1000), alice, bob); - env(pay(gw, alice, USD(100))); - env(pay(gw, bob, USD(100))); - env.close(); - - env(noop(bob), ter(tesSUCCESS)); - env.close(); - - auto const seq = env.seq(alice); - auto const preAlice = env.balance(alice); - auto const preAliceUSD = env.balance(alice, USD.issue()); - 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] = 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, USD(10)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, env.seq(alice), 0); - - // Tx 2 - Json::Value const tx2 = pay(bob, alice, USD(5)); - jv = addBatchTx(jv, tx2, bob.pk(), alice, 0, env.seq(bob), 1); - - jv = addBatchSignatures(jv, signers); - - // env(jv, bsig(alice, bob), ter(tesSUCCESS)); - env(jv, ter(tesSUCCESS)); - env.close(); - - std::vector testCases = {{ - {"tesSUCCESS", - "Payment", - "2071E28FCACC9EBC81E8B94F0A0663F9D808209F2803E02224027C8B9CD57C5" - "3"}, - {"tesSUCCESS", - "Payment", - "F757008AF55CCDE3511016AC2402672A7A08F2F33E1BFA2ED7EE2DD486B6462" - "E"}, - }}; - - Json::Value params; - params[jss::ledger_index] = env.current()->seq() - 1; - params[jss::transactions] = true; - params[jss::expand] = true; - auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; - auto const txn = getTxByIndex(jrr, 2); - validateBatchTxns(txn[jss::metaData], testCases); - validateBatchMeta(txn[jss::metaData], preAlice, seq); - - BEAST_EXPECT(env.seq(alice) == 7); - BEAST_EXPECT(env.seq(bob) == 7); - BEAST_EXPECT(env.balance(alice) == preAlice - (batchFee)); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(5)); - BEAST_EXPECT(env.balance(bob) == preBob); - BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(5)); - } - - void - testAtomicSwapXRP(FeatureBitset features) - { - testcase("atomic swap xrp"); + testcase("multi party"); using namespace test::jtx; using namespace std::literals; @@ -1193,11 +1104,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, XRP(10)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, env.seq(alice), 0); + // 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, bob.pk(), alice, 0, env.seq(bob), 1); + // jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob), 1); jv = addBatchSignatures(jv, signers); @@ -1266,11 +1177,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); env(jv, fee(feeDrops * 2 + (feeDrops * 4)), @@ -1308,9 +1219,9 @@ class Batch_test : public beast::unit_test::suite } void - testMultisignSwap(FeatureBitset features) + testMultisignMultiParty(FeatureBitset features) { - testcase("atomic multisign swap"); + testcase("multisign multi party"); using namespace test::jtx; using namespace std::literals; @@ -1351,11 +1262,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, XRP(10)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, env.seq(alice), 0); + // 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, bob.pk(), alice, 0, env.seq(bob), 1); + // jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob), 1); jv = addBatchMultiSignatures(jv, 0, bob, signers); @@ -1391,74 +1302,70 @@ class Batch_test : public beast::unit_test::suite } void - testAccountSet(FeatureBitset features) + testSubmit(FeatureBitset features) { - testcase("account set"); + testcase("submit"); using namespace test::jtx; using namespace std::literals; test::jtx::Env env{*this, envconfig()}; - auto const feeDrops = env.current()->fees().base; auto const alice = Account("alice"); auto const bob = Account("bob"); - auto const carol = Account("carol"); - env.fund(XRP(1000), alice, bob, carol); - env.close(); - - 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 = fset(alice, asfRequireAuth); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; - // Tx 2 - Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + env.fund(XRP(1000), alice, bob, gw); + env.close(); - env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); + env.trust(USD(1000), alice, bob); + env(pay(gw, alice, USD(100))); + env(pay(gw, bob, USD(100))); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "AccountSet", - "B9BF25231F9923E1F0AD95BFC8F66EED4E76E3B7C36D23326661CB57D7CF5E1" - "3"}, - {"tesSUCCESS", - "Payment", - "44B76513FE9A57E84B837139C1D83A81EB70C88842EC85A561A71F05DF51427" - "3"}, - }}; + { + auto jv = pay(alice, bob, USD(1)); + jv[sfBatchTxn.jsonName] = Json::Value{}; + jv[sfBatchTxn.jsonName][jss::OuterAccount] = alice.human(); + jv[sfBatchTxn.jsonName][sfSequence.jsonName] = 0; + jv[sfBatchTxn.jsonName][sfBatchIndex.jsonName] = 0; - Json::Value params; - params[jss::ledger_index] = env.current()->seq() - 1; - params[jss::transactions] = true; - params[jss::expand] = true; - auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; - auto const txn = getTxByIndex(jrr, 2); - validateBatchTxns(txn[jss::metaData], testCases); + Serializer s; + auto jt = env.jt(jv); + jv.removeMember(sfTxnSignature.jsonName); + s.erase(); + jt.stx->add(s); + auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result]; + // std::cout << jrr << std::endl; + BEAST_EXPECT( + jrr[jss::status] == "error" && + jrr[jss::error] == "invalidTransaction"); - BEAST_EXPECT(env.seq(alice) == 7); - BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - (feeDrops * 2)); - BEAST_EXPECT(env.balance(bob) == preBob + XRP(1)); + env.close(); + } + { + std::string txBlob = + "1200002280000000240000000561D4838D7EA4C68000000000000000000000" + "0000005553440000000000A407AF5856CCF3C42619DAA925813FC955C72983" + "68400000000000000A73210388935426E0D08083314842EDFBB2D517BD4769" + "9F9A4527318A8E10468C97C0528114AE123A8556F3CF91154711376AFB0F89" + "4F832B3D8314F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90FE023240000" + "0000801814AE123A8556F3CF91154711376AFB0F894F832B3D00101400E1"; + auto const jrr = env.rpc("submit", txBlob)[jss::result]; + // std::cout << jrr << std::endl; + BEAST_EXPECT( + jrr[jss::status] == "success" && + jrr[jss::engine_result] == "temINVALID_BATCH"); + + env.close(); + } } void - testBatch(FeatureBitset features) + testNoInnerBatch(FeatureBitset features) { - testcase("batch"); + testcase("no inner batch"); using namespace test::jtx; using namespace std::literals; @@ -1496,34 +1403,31 @@ class Batch_test : public beast::unit_test::suite // bTx 1 Json::Value const btx1 = pay(alice, bob, XRP(1)); - btx = addBatchTx(btx, btx1, alice.pk(), alice, 0, seq, 0); + btx = addBatchTx(btx, btx1, alice, 0, seq, 0); } - jv = addBatchTx(jv, btx, alice.pk(), alice, 0, seq, 0); + // jv = addBatchTx(jv, btx, alice, 0, seq, 0); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(temMALFORMED)); env.close(); } - static uint256 - getCheckIndex(AccountID const& account, std::uint32_t uSequence) - { - return keylet::check(account, uSequence).key; - } - void - testCheckCreate(FeatureBitset features) + testAccountSet(FeatureBitset features) { - testcase("check create"); + testcase("account set"); using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + // 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"); @@ -1545,21 +1449,23 @@ class Batch_test : public beast::unit_test::suite jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; // Tx 1 - Json::Value tx1 = check::create(alice, bob, XRP(10)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + 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.pk(), alice, 1, seq, 1); + jv = addBatchTx(jv, tx2, alice, 2, seq); env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); env.close(); std::vector testCases = {{ {"tesSUCCESS", - "CheckCreate", - "92E8D7C221CAF96B70EDE21E5DD3A3126F73474EAB7ABB639A6FAF5E45C7D13" - "6"}, + "AccountSet", + "B9BF25231F9923E1F0AD95BFC8F66EED4E76E3B7C36D23326661CB57D7CF5E1" + "3"}, {"tesSUCCESS", "Payment", "44B76513FE9A57E84B837139C1D83A81EB70C88842EC85A561A71F05DF51427" @@ -1571,7 +1477,7 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; + std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], testCases); @@ -1580,10 +1486,16 @@ class Batch_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(bob) == preBob + XRP(1)); } + static uint256 + getCheckIndex(AccountID const& account, std::uint32_t uSequence) + { + return keylet::check(account, uSequence).key; + } + void - testCheckCash(FeatureBitset features) + testMultiPartyObjectCreate(FeatureBitset features) { - testcase("check cash"); + testcase("multi party object create"); using namespace test::jtx; using namespace std::literals; @@ -1610,8 +1522,7 @@ class Batch_test : public beast::unit_test::suite auto const preBobUSD = env.balance(bob, USD.issue()); std::vector const signers = {{ - {0, alice}, - {1, bob}, + {0, bob}, }}; auto const seq = env.seq(alice); @@ -1630,11 +1541,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 uint256 const chkId{getCheckIndex(alice, seq + 1)}; Json::Value tx1 = check::create(alice, bob, USD(10)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, env.seq(alice), 0); + // jv = addBatchTx(jv, tx1, alice, 0, env.seq(alice), 0); // Tx 2 Json::Value const tx2 = check::cash(bob, chkId, USD(10)); - jv = addBatchTx(jv, tx2, bob.pk(), alice, 0, env.seq(bob), 1); + // jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob), 1); jv = addBatchSignatures(jv, signers); @@ -1670,9 +1581,9 @@ class Batch_test : public beast::unit_test::suite } void - testCheckCancel(FeatureBitset features) + testOfferCancel(FeatureBitset features) { - testcase("check cancel"); + testcase("offer cancel"); using namespace test::jtx; using namespace std::literals; @@ -1695,50 +1606,36 @@ class Batch_test : public beast::unit_test::suite auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); - auto const preAliceUSD = env.balance(alice, USD.issue()); - auto const preBobUSD = env.balance(bob, USD.issue()); - - std::vector const signers = {{ - {0, alice}, - {1, 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 - uint256 const chkId{getCheckIndex(alice, seq + 1)}; - Json::Value tx1 = check::create(alice, bob, XRP(10)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, env.seq(alice), 0); + Json::Value tx1 = offer(alice, XRP(50), USD(50)); + // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); // Tx 2 - Json::Value const tx2 = check::cancel(bob, chkId); - jv = addBatchTx(jv, tx2, bob.pk(), alice, 0, env.seq(bob), 1); - - jv = addBatchSignatures(jv, signers); + Json::Value const tx2 = offer_cancel(alice, seq + 1); + // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); - env(jv, ter(tesSUCCESS)); + env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); env.close(); std::vector testCases = {{ {"tesSUCCESS", - "CheckCreate", - "7EA52BD67C03CE73EB3621491EA66A3DC1E1CA0B3AEBC2A8E56908329A6C28B" - "1"}, + "OfferCreate", + "145CBCD0C29955E43452EF891979DF63CB9D32CDB6F91FEAEE402D39FC53845" + "5"}, {"tesSUCCESS", - "CheckCancel", - "083804635D4A38BE94D35F4FD900AC4B864294926345594409FD70630AFA963" - "4"}, + "OfferCancel", + "10C5B1A9861230AD0BC9BA9FD957944B2A06F5A5C888557586D96EE58DD5861" + "8"}, }}; Json::Value params; @@ -1747,76 +1644,67 @@ class Batch_test : public beast::unit_test::suite params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; - auto const txn = getTxByIndex(jrr, 2); + auto const txn = getTxByIndex(jrr, 3); validateBatchTxns(txn[jss::metaData], testCases); BEAST_EXPECT(env.seq(alice) == 7); - BEAST_EXPECT(env.seq(bob) == 6); - BEAST_EXPECT(env.balance(alice) == preAlice - (batchFee)); + BEAST_EXPECT(env.balance(alice) == preAlice - (feeDrops * 2)); BEAST_EXPECT(env.balance(bob) == preBob); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD); - BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD); } void - testClawback(FeatureBitset features) + testTicketsOuter(FeatureBitset features) { - testcase("clawback"); + testcase("tickets outer"); using namespace test::jtx; 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"); auto const bob = Account("bob"); - auto const gw = Account("gw"); - auto const USD = gw["USD"]; - - env.fund(XRP(1000), alice, bob, gw); - env.close(); - env(fset(gw, asfAllowTrustLineClawback)); + env.fund(XRP(1000), alice, bob); env.close(); - env.trust(USD(1000), alice, bob); - env(pay(gw, alice, USD(100))); - env(pay(gw, bob, USD(100))); + std::uint32_t aliceTicketSeq{env.seq(alice) + 1}; + env(ticket::create(alice, 10)); env.close(); - auto const preGw = env.balance(gw); + auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); - auto const seq = env.seq(gw); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; - jv[jss::Account] = gw.human(); - jv[jss::Sequence] = seq; + jv[jss::Account] = alice.human(); // Batch Transactions jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; // Tx 1 - Json::Value tx1 = claw(gw, bob["USD"](10)); - jv = addBatchTx(jv, tx1, gw.pk(), gw, 0, seq, 0); + Json::Value const tx1 = pay(alice, bob, XRP(1)); + // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); // Tx 2 - Json::Value const tx2 = pay(gw, bob, XRP(1)); - jv = addBatchTx(jv, tx2, gw.pk(), gw, 1, seq, 1); + Json::Value const tx2 = pay(alice, bob, XRP(1)); + // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); - env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); + env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ticket::use(aliceTicketSeq++), ter(tesSUCCESS)); env.close(); std::vector testCases = {{ {"tesSUCCESS", - "Clawback", - "838F5265749C559B067F5852B98A2B262AA22A88C556E6A51DD7EF9B842FAB1" - "5"}, + "Payment", + "18629D496965A11CAB4454B86DB794BA07DABA3EE154DEFE9259977221E937E5"}, {"tesSUCCESS", "Payment", - "CDD0AF925A52E2D9C9661FDBDD2CD1856FDA058B5E4C262974F9D90698A2800" - "0"}, + "7654B768E091768EB0D43C8EE33B7E72C82BC7A584D578F2646721F69AEEAB72"}, }}; Json::Value params; @@ -1824,37 +1712,38 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; + std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq); - BEAST_EXPECT(env.seq(gw) == 10); - BEAST_EXPECT(env.balance(gw) == preGw - XRP(1) - (feeDrops * 2)); - BEAST_EXPECT(env.balance(bob) == preBob + XRP(1)); + BEAST_EXPECT(env.seq(alice) == 17); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - (feeDrops * 2)); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(2)); } void - testOfferCancel(FeatureBitset features) + testTicketsInner(FeatureBitset features) { - testcase("offer cancel"); + testcase("tickets inner"); using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + // 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"); auto const bob = Account("bob"); - auto const gw = Account("gw"); - auto const USD = gw["USD"]; - env.fund(XRP(1000), alice, bob, gw); + env.fund(XRP(1000), alice, bob); env.close(); - env.trust(USD(1000), alice, bob); - env(pay(gw, alice, USD(100))); - env(pay(gw, bob, USD(100))); + std::uint32_t aliceTicketSeq{env.seq(alice) + 1}; + env(ticket::create(alice, 10)); env.close(); auto const preAlice = env.balance(alice); @@ -1870,25 +1759,23 @@ class Batch_test : public beast::unit_test::suite jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; // Tx 1 - Json::Value tx1 = offer(alice, XRP(50), USD(50)); - jv = addBatchTx(jv, tx1, alice.pk(), alice, 0, seq, 0); + Json::Value const tx1 = pay(alice, bob, XRP(1)); + // jv = addBatchTx(jv, tx1, alice, 0, 0, 0, aliceTicketSeq); // Tx 2 - Json::Value const tx2 = offer_cancel(alice, seq + 1); - jv = addBatchTx(jv, tx2, alice.pk(), alice, 1, seq, 1); + Json::Value const tx2 = pay(alice, bob, XRP(1)); + // jv = addBatchTx(jv, tx2, alice, 1, 0, 1, aliceTicketSeq); env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); env.close(); std::vector testCases = {{ {"tesSUCCESS", - "OfferCreate", - "145CBCD0C29955E43452EF891979DF63CB9D32CDB6F91FEAEE402D39FC53845" - "5"}, + "Payment", + "18629D496965A11CAB4454B86DB794BA07DABA3EE154DEFE9259977221E937E5"}, {"tesSUCCESS", - "OfferCancel", - "10C5B1A9861230AD0BC9BA9FD957944B2A06F5A5C888557586D96EE58DD5861" - "8"}, + "Payment", + "7654B768E091768EB0D43C8EE33B7E72C82BC7A584D578F2646721F69AEEAB72"}, }}; Json::Value params; @@ -1896,104 +1783,108 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; - auto const txn = getTxByIndex(jrr, 3); + std::cout << jrr << std::endl; + auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq); - BEAST_EXPECT(env.seq(alice) == 7); - BEAST_EXPECT(env.balance(alice) == preAlice - (feeDrops * 2)); - BEAST_EXPECT(env.balance(bob) == preBob); + BEAST_EXPECT(env.seq(alice) == 17); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - (feeDrops * 2)); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(2)); } void - testSubmit(FeatureBitset features) + testTicketsOuterInner(FeatureBitset features) { - testcase("submit"); + testcase("tickets outer inner"); using namespace test::jtx; 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"); auto const bob = Account("bob"); - auto const gw = Account("gw"); - auto const USD = gw["USD"]; - env.fund(XRP(1000), alice, bob, gw); + env.fund(XRP(1000), alice, bob); env.close(); - env.trust(USD(1000), alice, bob); - env(pay(gw, alice, USD(100))); - env(pay(gw, bob, USD(100))); + std::uint32_t aliceTicketSeq{env.seq(alice) + 1}; + env(ticket::create(alice, 10)); env.close(); - { - auto jv = pay(alice, bob, USD(1)); - jv[sfBatchTxn.jsonName] = Json::Value{}; - jv[sfBatchTxn.jsonName][jss::OuterAccount] = alice.human(); - jv[sfBatchTxn.jsonName][sfSequence.jsonName] = 0; - jv[sfBatchTxn.jsonName][sfBatchIndex.jsonName] = 0; + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); - Serializer s; - auto jt = env.jt(jv); - jv.removeMember(sfTxnSignature.jsonName); - s.erase(); - jt.stx->add(s); - auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result]; - // std::cout << jrr << std::endl; - BEAST_EXPECT( - jrr[jss::status] == "error" && - jrr[jss::error] == "invalidTransaction"); + auto const seq = env.seq(alice); + Json::Value jv; + jv[jss::TransactionType] = jss::Batch; + jv[jss::Account] = alice.human(); - env.close(); - } - { - std::string txBlob = - "1200002280000000240000000561D4838D7EA4C68000000000000000000000" - "0000005553440000000000A407AF5856CCF3C42619DAA925813FC955C72983" - "68400000000000000A73210388935426E0D08083314842EDFBB2D517BD4769" - "9F9A4527318A8E10468C97C0528114AE123A8556F3CF91154711376AFB0F89" - "4F832B3D8314F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90FE023240000" - "0000801814AE123A8556F3CF91154711376AFB0F894F832B3D00101400E1"; - auto const jrr = env.rpc("submit", txBlob)[jss::result]; - // std::cout << jrr << std::endl; - BEAST_EXPECT( - jrr[jss::status] == "success" && - jrr[jss::engine_result] == "temINVALID_BATCH"); + // Batch Transactions + jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - env.close(); - } - } + // Tx 1 + Json::Value const tx1 = pay(alice, bob, XRP(1)); + // jv = addBatchTx(jv, tx1, alice, 1, 0, 0, aliceTicketSeq); - void - testTTs(FeatureBitset features) - { - testAccountSet(features); - testBatch(features); - testCheckCreate(features); - testCheckCash(features); - testCheckCancel(features); - testClawback(features); - // testOfferCancel(features); + // Tx 2 + Json::Value const tx2 = pay(alice, bob, XRP(1)); + // jv = addBatchTx(jv, tx2, alice, 0, seq, 1); + + env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ticket::use(aliceTicketSeq++), ter(tesSUCCESS)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "Payment", + "CBF12A852B0418FAF406C480BE991CE1EA2D0F16323412BFFA9F89CA7449B21E"}, + {"tesSUCCESS", + "Payment", + "18629D496965A11CAB4454B86DB794BA07DABA3EE154DEFE9259977221E937E5"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + std::cout << jrr << std::endl; + auto const txn = getTxByIndex(jrr, 2); + validateBatchTxns(txn[jss::metaData], testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq); + + BEAST_EXPECT(env.seq(alice) == 16); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - (feeDrops * 2)); + BEAST_EXPECT(env.balance(bob) == preBob + XRP(2)); } void testWithFeats(FeatureBitset features) { - testEnable(features); - testPreflight(features); - testBadSequence(features); - testBadFee(features); - testOutOfSequence(features); - testAllOrNothing(features); - testOnlyOne(features); - testUntilFailure(features); - testIndependent(features); - testAtomicSwapIOU(features); - testAtomicSwapXRP(features); - testMultisign(features); - testMultisignSwap(features); + // testEnable(features); + // testPreflight(features); + // testBadSequence(features); + // testBadFee(features); + // testOutOfSequence(features); + // testAllOrNothing(features); + // testOnlyOne(features); + // testUntilFailure(features); + // testIndependent(features); + // testMultiParty(features); + // testMultisign(features); + // testMultisignMultiParty(features); + // testSubmit(features); + // testNoInnerBatch(features); + testAccountSet(features); + // testMultiPartyObjectCreate(features); + // testTicketsOuter(features); + // testTicketsInner(features); + // testTicketsOuterInner(features); } public: diff --git a/src/xrpld/app/tx/detail/ApplyContext.cpp b/src/xrpld/app/tx/detail/ApplyContext.cpp index a45f105491e..428d6604a7f 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.cpp +++ b/src/xrpld/app/tx/detail/ApplyContext.cpp @@ -73,20 +73,8 @@ ApplyContext::applyOpenView(OpenView& open) open.apply(base_); } -/** - * Applies the fee for the transaction. - * - * This function retrieves the account ID from the transaction and reads the - * corresponding account state from the base ledger. It then updates the balance - * field of the account state with the balance from the base ledger and updates - * the account state in the current view. - * - * @note This function assumes that both the account state in the base ledger - * and the current view exist. If either of them is missing, the function does - * not perform any updates. - */ void -ApplyContext::applyFee() +ApplyContext::applyBatch() { AccountID const account = tx.getAccountID(sfAccount); auto const sleBase = base_.read(keylet::account(account)); @@ -94,8 +82,62 @@ ApplyContext::applyFee() assert(sle != nullptr || sleBase != nullptr || account == beast::zero); if (sle && sleBase) { - sle->setFieldAmount(sfBalance, (*sleBase)[sfBalance].xrp()); - view_->update(sle); + std::cout << "Open: " << base_.open() << std::endl; + std::cout << "sleBase.Balance: " << (*sleBase)[sfBalance] << std::endl; + std::cout << "sleBase.OwnerCount: " << (*sleBase)[sfOwnerCount] << std::endl; + if (sleBase.isFieldPresent(sfTicketCount)) + std::cout << "sleBase.TicketCount: " << (*sleBase)[sfTicketCount] << std::endl; + std::cout << "sleBase.Sequence: " << (*sleBase)[sfSequence] << std::endl; + std::cout << "sle.Balance: " << (*sle)[sfBalance] << std::endl; + std::cout << "sle.OwnerCount: " << (*sle)[sfOwnerCount] << std::endl; + if (sle.isFieldPresent(sfTicketCount)) + std::cout << "sle.TicketCount: " << (*sle)[sfTicketCount] << std::endl; + std::cout << "sle.Sequence: " << (*sle)[sfSequence] << std::endl; + // if (*sleBase[sfBalance] != (*sle)[sfBalance]) + // sle->setFieldAmount(sfBalance, (*sleBase)[sfBalance].xrp()); + // if (*sleBase[sfOwnerCount] != (*sle)[sfOwnerCount]) + // sle->setFieldU32(sfOwnerCount, (*sleBase)[sfOwnerCount]); + // if (*sleBase)[sfSequence] != (*sle)[sfSequence]) + // sle->setFieldU32(sfSequence, (*sleBase)[sfSequence]); + // if (sleBase->isFieldPresent(sfTicketCount) && + // (*sleBase)[sfTicketCount] != (*sle)[sfTicketCount]) + // sle->setFieldU32(sfTicketCount, (*sleBase)[sfTicketCount]); + // sle->setFieldAmount(sfBalance, (*sleBase)[sfBalance].xrp()); + // sle->setFieldU32(sfOwnerCount, (*sleBase)[sfOwnerCount]); + // sle->setFieldU32(sfSequence, (*sleBase)[sfSequence]); + // if (sleBase->isFieldPresent(sfTicketCount)) + // sle->setFieldU32(sfTicketCount, (*sleBase)[sfTicketCount]); + // if (sleBase->isFieldPresent(sfDomain)) + // sle->setFieldVL(sfDomain, (*sleBase)[sfDomain]); + // view_->update(sle); + } +} + +void +ApplyContext::applyPrev(ApplyViewImpl& avi) +{ + AccountID const account = tx.getAccountID(sfAccount); + auto const sleBase = base_.read(keylet::account(account)); + auto const sle = view_->peek(keylet::account(account)); + { + std::cout << "applyPrev.sleBase.Balance: " << (*sleBase)[sfBalance] << std::endl; + std::cout << "applyPrev.sleBase.OwnerCount: " << (*sleBase)[sfOwnerCount] << std::endl; + if (sleBase->isFieldPresent(sfTicketCount)) + std::cout << "applyPrev.sleBase.TicketCount: " << (*sleBase)[sfTicketCount] << std::endl; + std::cout << "applyPrev.sleBase.Sequence: " << (*sleBase)[sfSequence] << std::endl; + // STObject prevFields{sfPreviousFields}; + // if (*sleBase[sfBalance] != (*sle)[sfBalance]) + // prevFields.setFieldAmount(sfBalance, (*sleBase)[sfBalance]); + // if (sleBase->getFieldU32(sfOwnerCount) != sle->getFieldU32(sfOwnerCount)) + // prevFields.setFieldU32(sfOwnerCount, (*sleBase)[sfOwnerCount]); + // if (sleBase->getFieldU32(sfSequence) != sle->getFieldU32(sfSequence)) + // prevFields.setFieldU32(sfSequence, (*sleBase)[sfSequence]); + // if (sleBase->isFieldPresent(sfTicketCount) && + // sleBase->getFieldU32(sfTicketCount) != sle->getFieldU32(sfTicketCount)) + // prevFields.setFieldU32(sfTicketCount, (*sleBase)[sfTicketCount]); + + // prevFields.setFieldAmount(sfBalance, (*sleBase)[sfBalance]); + // avi.addBatchPrevMetaData(std::move(prevFields)); } } diff --git a/src/xrpld/app/tx/detail/ApplyContext.h b/src/xrpld/app/tx/detail/ApplyContext.h index 21e62cd0cc4..861c984be82 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.h +++ b/src/xrpld/app/tx/detail/ApplyContext.h @@ -89,7 +89,11 @@ class ApplyContext /** Apply the fee to the account. */ void - applyFee(); + applyBatch(); + + /** Apply the fee to the account. */ + void + applyPrev(ApplyViewImpl& avi); /** Get the number of unapplied changes. */ std::size_t diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index a879651b280..a127509b6b3 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -135,7 +135,6 @@ Batch::doApply() TER result = tesSUCCESS; ApplyViewImpl& avi = dynamic_cast(ctx_.view()); OpenView subView(&sb); - std::map accountCount; auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions); for (STObject txn : txns) @@ -160,9 +159,6 @@ Batch::doApply() meta.setFieldH256(sfTransactionHash, stx.getTransactionID()); avi.addBatchExecutionMetaData(std::move(meta)); - // Update Account:Count Map - accountCount[stx.getAccountID(sfAccount)] += 1; - if (ter != tesSUCCESS) { // Atomic Revert on non tec failure @@ -199,51 +195,14 @@ Batch::doApply() } } - // Apply SubView + // Apply SubView & PreviousFields if (changed) { + ctx_.applyPrev(avi); ctx_.applyOpenView(subView); } - // Clean Up - // 1. Update the account_ sfSequence to include any tes/tec inner txns - - // Reason: The sequence (1) is consumed before the inner batch txns - // however we dont know how many of the inner txns will succeed depending - // on the batch type. (This could be moved to `getSeqProxy()`) - - // 2. Set the batch prevFields so they are included in metadata - - // Reason: When the outer batch is applied (at the end), the Sequence and - // Balance have already been updated, therefore there are no PreviousFields - // when adding the metadata. This adds them. - { - auto const sleSrcAcc = sb.peek(keylet::account(account_)); - if (!sleSrcAcc) - return tefINTERNAL; - - STAmount const txFee = ctx_.tx.getFieldAmount(sfFee); - std::uint32_t const txSeq = ctx_.tx.getFieldU32(sfSequence); - STAmount const accBal = sleSrcAcc->getFieldAmount(sfBalance); - std::uint32_t const accSeq = sleSrcAcc->getFieldU32(sfSequence); - - // only update if the account_ batch has tes/tec inner txns - uint32_t const count = accountCount[account_]; - if (count != 0) - { - sleSrcAcc->setFieldU32(sfSequence, accSeq + count); - sb.update(sleSrcAcc); - } - - // update the batch prev fields - STObject prevFields{sfPreviousFields}; - prevFields.setFieldU32(sfSequence, txSeq); - prevFields.setFieldAmount(sfBalance, accBal + txFee); - avi.addBatchPrevMetaData(std::move(prevFields)); - } - sb.apply(ctx_.rawView()); - return result; } diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index cb30d3ee877..480208c8db3 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -1052,7 +1052,7 @@ Transactor::operator()() // If the transaction is a batch transaction, the fee is already // deducted from the account balance before executing the inner txns. // So, we need to "re" apply the fee again. - ctx_.applyFee(); + ctx_.applyBatch(); } if (applied) diff --git a/src/xrpld/ledger/detail/ApplyStateTable.cpp b/src/xrpld/ledger/detail/ApplyStateTable.cpp index 46c142c3881..badb97b94b4 100644 --- a/src/xrpld/ledger/detail/ApplyStateTable.cpp +++ b/src/xrpld/ledger/detail/ApplyStateTable.cpp @@ -214,7 +214,7 @@ ApplyStateTable::apply( prevs.emplace_back(obj); } - if (tx.getTxnType() == ttBATCH && nodeType == ltACCOUNT_ROOT) + if (tx.getTxnType() == ttBATCH && nodeType == ltACCOUNT_ROOT && batchPrev) { // TODO: This could fail if the fields already exist for (auto const& obj : *batchPrev) From d3a2554328bb43f256a7eceba01a3ec7e5718f56 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 10 Sep 2024 12:18:35 +0200 Subject: [PATCH 37/71] fix metadata --- src/test/app/Batch_test.cpp | 8 +-- src/xrpld/app/tx/detail/ApplyContext.cpp | 66 +++------------------ src/xrpld/app/tx/detail/ApplyContext.h | 4 -- src/xrpld/app/tx/detail/Batch.cpp | 2 - src/xrpld/app/tx/detail/Transactor.cpp | 9 --- src/xrpld/ledger/detail/ApplyStateTable.cpp | 5 +- 6 files changed, 15 insertions(+), 79 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 2c00af1db21..b9cfdbdd8db 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -1424,10 +1424,10 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - // test::jtx::Env env{*this, envconfig()}; - Env env{*this, envconfig(), features, nullptr, - beast::severities::kTrace - }; + 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"); diff --git a/src/xrpld/app/tx/detail/ApplyContext.cpp b/src/xrpld/app/tx/detail/ApplyContext.cpp index 428d6604a7f..cb242c100a9 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.cpp +++ b/src/xrpld/app/tx/detail/ApplyContext.cpp @@ -73,46 +73,6 @@ ApplyContext::applyOpenView(OpenView& open) open.apply(base_); } -void -ApplyContext::applyBatch() -{ - AccountID const account = tx.getAccountID(sfAccount); - auto const sleBase = base_.read(keylet::account(account)); - auto const sle = view_->peek(keylet::account(account)); - assert(sle != nullptr || sleBase != nullptr || account == beast::zero); - if (sle && sleBase) - { - std::cout << "Open: " << base_.open() << std::endl; - std::cout << "sleBase.Balance: " << (*sleBase)[sfBalance] << std::endl; - std::cout << "sleBase.OwnerCount: " << (*sleBase)[sfOwnerCount] << std::endl; - if (sleBase.isFieldPresent(sfTicketCount)) - std::cout << "sleBase.TicketCount: " << (*sleBase)[sfTicketCount] << std::endl; - std::cout << "sleBase.Sequence: " << (*sleBase)[sfSequence] << std::endl; - std::cout << "sle.Balance: " << (*sle)[sfBalance] << std::endl; - std::cout << "sle.OwnerCount: " << (*sle)[sfOwnerCount] << std::endl; - if (sle.isFieldPresent(sfTicketCount)) - std::cout << "sle.TicketCount: " << (*sle)[sfTicketCount] << std::endl; - std::cout << "sle.Sequence: " << (*sle)[sfSequence] << std::endl; - // if (*sleBase[sfBalance] != (*sle)[sfBalance]) - // sle->setFieldAmount(sfBalance, (*sleBase)[sfBalance].xrp()); - // if (*sleBase[sfOwnerCount] != (*sle)[sfOwnerCount]) - // sle->setFieldU32(sfOwnerCount, (*sleBase)[sfOwnerCount]); - // if (*sleBase)[sfSequence] != (*sle)[sfSequence]) - // sle->setFieldU32(sfSequence, (*sleBase)[sfSequence]); - // if (sleBase->isFieldPresent(sfTicketCount) && - // (*sleBase)[sfTicketCount] != (*sle)[sfTicketCount]) - // sle->setFieldU32(sfTicketCount, (*sleBase)[sfTicketCount]); - // sle->setFieldAmount(sfBalance, (*sleBase)[sfBalance].xrp()); - // sle->setFieldU32(sfOwnerCount, (*sleBase)[sfOwnerCount]); - // sle->setFieldU32(sfSequence, (*sleBase)[sfSequence]); - // if (sleBase->isFieldPresent(sfTicketCount)) - // sle->setFieldU32(sfTicketCount, (*sleBase)[sfTicketCount]); - // if (sleBase->isFieldPresent(sfDomain)) - // sle->setFieldVL(sfDomain, (*sleBase)[sfDomain]); - // view_->update(sle); - } -} - void ApplyContext::applyPrev(ApplyViewImpl& avi) { @@ -120,24 +80,14 @@ ApplyContext::applyPrev(ApplyViewImpl& avi) auto const sleBase = base_.read(keylet::account(account)); auto const sle = view_->peek(keylet::account(account)); { - std::cout << "applyPrev.sleBase.Balance: " << (*sleBase)[sfBalance] << std::endl; - std::cout << "applyPrev.sleBase.OwnerCount: " << (*sleBase)[sfOwnerCount] << std::endl; - if (sleBase->isFieldPresent(sfTicketCount)) - std::cout << "applyPrev.sleBase.TicketCount: " << (*sleBase)[sfTicketCount] << std::endl; - std::cout << "applyPrev.sleBase.Sequence: " << (*sleBase)[sfSequence] << std::endl; - // STObject prevFields{sfPreviousFields}; - // if (*sleBase[sfBalance] != (*sle)[sfBalance]) - // prevFields.setFieldAmount(sfBalance, (*sleBase)[sfBalance]); - // if (sleBase->getFieldU32(sfOwnerCount) != sle->getFieldU32(sfOwnerCount)) - // prevFields.setFieldU32(sfOwnerCount, (*sleBase)[sfOwnerCount]); - // if (sleBase->getFieldU32(sfSequence) != sle->getFieldU32(sfSequence)) - // prevFields.setFieldU32(sfSequence, (*sleBase)[sfSequence]); - // if (sleBase->isFieldPresent(sfTicketCount) && - // sleBase->getFieldU32(sfTicketCount) != sle->getFieldU32(sfTicketCount)) - // prevFields.setFieldU32(sfTicketCount, (*sleBase)[sfTicketCount]); - - // prevFields.setFieldAmount(sfBalance, (*sleBase)[sfBalance]); - // avi.addBatchPrevMetaData(std::move(prevFields)); + STObject prevFields{sfPreviousFields}; + for (auto const& obj : *sleBase) + { + if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) && + !sle->hasMatchingEntry(obj)) + prevFields.emplace_back(obj); + } + avi.addBatchPrevMetaData(std::move(prevFields)); } } diff --git a/src/xrpld/app/tx/detail/ApplyContext.h b/src/xrpld/app/tx/detail/ApplyContext.h index 861c984be82..dccce3d5b16 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.h +++ b/src/xrpld/app/tx/detail/ApplyContext.h @@ -87,10 +87,6 @@ class ApplyContext void applyOpenView(OpenView& open); - /** Apply the fee to the account. */ - void - applyBatch(); - /** Apply the fee to the account. */ void applyPrev(ApplyViewImpl& avi); diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index a127509b6b3..a7eb492e953 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -164,7 +164,6 @@ Batch::doApply() // Atomic Revert on non tec failure if (!isTecClaim(ter)) { - accountCount.clear(); result = tecBATCH_FAILURE; changed = false; break; @@ -181,7 +180,6 @@ Batch::doApply() } if (flags & tfAllOrNothing) { - accountCount.clear(); result = tecBATCH_FAILURE; changed = false; break; diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 480208c8db3..6b225441244 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -1046,15 +1046,6 @@ Transactor::operator()() applied = isTecClaim(result); } - // Apply fee for batch transaction if it was successfully applied - if (applied && ctx_.tx.getTxnType() == ttBATCH && result == tesSUCCESS) - { - // If the transaction is a batch transaction, the fee is already - // deducted from the account balance before executing the inner txns. - // So, we need to "re" apply the fee again. - ctx_.applyBatch(); - } - if (applied) { // Check invariants: if `tecINVARIANT_FAILED` is not returned, we can diff --git a/src/xrpld/ledger/detail/ApplyStateTable.cpp b/src/xrpld/ledger/detail/ApplyStateTable.cpp index badb97b94b4..66d259f67e7 100644 --- a/src/xrpld/ledger/detail/ApplyStateTable.cpp +++ b/src/xrpld/ledger/detail/ApplyStateTable.cpp @@ -119,6 +119,7 @@ ApplyStateTable::apply( std::optional const& batchPrev, beast::Journal j) { + bool const isBatch = tx.getTxnType() == ttBATCH; // Build metadata and insert auto const sTx = std::make_shared(); tx.add(*sTx); @@ -206,7 +207,7 @@ ApplyStateTable::apply( threadItem(meta, curNode); STObject prevs(sfPreviousFields); - for (auto const& obj : *origNode) + for (auto const& obj : isBatch ? *curNode : *origNode) { // search the original node for values saved on modify if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) && @@ -226,7 +227,7 @@ ApplyStateTable::apply( .emplace_back(std::move(prevs)); STObject finals(sfFinalFields); - for (auto const& obj : *curNode) + for (auto const& obj : isBatch ? *origNode : *curNode) { // search the final node for values saved always if (obj.getFName().shouldMeta( From e099e0398525a96bb7e0ef452d679216f1e0754b Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 10 Sep 2024 13:32:22 +0200 Subject: [PATCH 38/71] fix metadata --- src/test/app/Batch_test.cpp | 10 ++++++---- src/xrpld/app/tx/detail/ApplyContext.cpp | 10 ++++++++++ src/xrpld/app/tx/detail/ApplyContext.h | 4 ++++ src/xrpld/app/tx/detail/Transactor.cpp | 9 +++++++++ src/xrpld/ledger/detail/ApplyStateTable.cpp | 8 ++++---- 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index b9cfdbdd8db..42ef265648e 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -1464,12 +1464,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "AccountSet", - "B9BF25231F9923E1F0AD95BFC8F66EED4E76E3B7C36D23326661CB57D7CF5E1" - "3"}, + "6B6B225E26F2F4811A651D7FD1E4F675D3E9F677C0167F8AAE707E2CB9B508A6"}, {"tesSUCCESS", "Payment", - "44B76513FE9A57E84B837139C1D83A81EB70C88842EC85A561A71F05DF51427" - "3"}, + "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607DA"}, }}; Json::Value params; @@ -1480,6 +1478,10 @@ class Batch_test : public beast::unit_test::suite std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq); + + std::cout << "seq: " << env.seq(alice) << std::endl; + std::cout << "amount: " << env.balance(alice) << std::endl; BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - (feeDrops * 2)); diff --git a/src/xrpld/app/tx/detail/ApplyContext.cpp b/src/xrpld/app/tx/detail/ApplyContext.cpp index cb242c100a9..8a726cd6836 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.cpp +++ b/src/xrpld/app/tx/detail/ApplyContext.cpp @@ -73,12 +73,22 @@ ApplyContext::applyOpenView(OpenView& open) open.apply(base_); } +void +ApplyContext::applyBatch() +{ + AccountID const account = tx.getAccountID(sfAccount); + auto const sleBase = base_.read(keylet::account(account)); + if (sleBase) + view_->rawReplace(std::make_shared(*sleBase)); +} + void ApplyContext::applyPrev(ApplyViewImpl& avi) { AccountID const account = tx.getAccountID(sfAccount); auto const sleBase = base_.read(keylet::account(account)); auto const sle = view_->peek(keylet::account(account)); + if (sle && sleBase) { STObject prevFields{sfPreviousFields}; for (auto const& obj : *sleBase) diff --git a/src/xrpld/app/tx/detail/ApplyContext.h b/src/xrpld/app/tx/detail/ApplyContext.h index dccce3d5b16..861c984be82 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.h +++ b/src/xrpld/app/tx/detail/ApplyContext.h @@ -87,6 +87,10 @@ class ApplyContext void applyOpenView(OpenView& open); + /** Apply the fee to the account. */ + void + applyBatch(); + /** Apply the fee to the account. */ void applyPrev(ApplyViewImpl& avi); diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 6b225441244..480208c8db3 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -1046,6 +1046,15 @@ Transactor::operator()() applied = isTecClaim(result); } + // Apply fee for batch transaction if it was successfully applied + if (applied && ctx_.tx.getTxnType() == ttBATCH && result == tesSUCCESS) + { + // If the transaction is a batch transaction, the fee is already + // deducted from the account balance before executing the inner txns. + // So, we need to "re" apply the fee again. + ctx_.applyBatch(); + } + if (applied) { // Check invariants: if `tecINVARIANT_FAILED` is not returned, we can diff --git a/src/xrpld/ledger/detail/ApplyStateTable.cpp b/src/xrpld/ledger/detail/ApplyStateTable.cpp index 66d259f67e7..54534480e39 100644 --- a/src/xrpld/ledger/detail/ApplyStateTable.cpp +++ b/src/xrpld/ledger/detail/ApplyStateTable.cpp @@ -159,7 +159,7 @@ ApplyStateTable::apply( } auto const origNode = to.read(keylet::unchecked(item.first)); auto curNode = item.second.second; - if ((type == &sfModifiedNode) && (*curNode == *origNode)) + if ((type == &sfModifiedNode) && (*curNode == *origNode) && !isBatch) continue; std::uint16_t nodeType = curNode ? curNode->getFieldU16(sfLedgerEntryType) @@ -207,7 +207,7 @@ ApplyStateTable::apply( threadItem(meta, curNode); STObject prevs(sfPreviousFields); - for (auto const& obj : isBatch ? *curNode : *origNode) + for (auto const& obj : *origNode) { // search the original node for values saved on modify if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) && @@ -215,7 +215,7 @@ ApplyStateTable::apply( prevs.emplace_back(obj); } - if (tx.getTxnType() == ttBATCH && nodeType == ltACCOUNT_ROOT && batchPrev) + if (isBatch && nodeType == ltACCOUNT_ROOT && batchPrev) { // TODO: This could fail if the fields already exist for (auto const& obj : *batchPrev) @@ -227,7 +227,7 @@ ApplyStateTable::apply( .emplace_back(std::move(prevs)); STObject finals(sfFinalFields); - for (auto const& obj : isBatch ? *origNode : *curNode) + for (auto const& obj : *curNode) { // search the final node for values saved always if (obj.getFName().shouldMeta( From 84a69efca5c711f92e164bd61580696d39073c50 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 10 Sep 2024 16:19:01 +0200 Subject: [PATCH 39/71] fix metadata --- src/test/app/Batch_test.cpp | 422 ++++++++++++++--------- src/xrpld/app/tx/detail/ApplyContext.cpp | 6 +- src/xrpld/app/tx/detail/Batch.cpp | 38 +- 3 files changed, 299 insertions(+), 167 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 42ef265648e..3c622d36fe7 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -56,15 +56,17 @@ class Batch_test : public beast::unit_test::suite void validateBatchTxns( Json::Value meta, + std::uint32_t const& txns, std::vector const& batchResults) { - BEAST_EXPECT(meta[sfBatchExecutions.jsonName].size() > 0); + BEAST_EXPECT(meta[sfBatchExecutions.jsonName].size() != txns); size_t index = 0; for (auto const& _batchTxn : meta[sfBatchExecutions.jsonName]) { auto const b = _batchTxn[sfBatchExecution.jsonName]; BEAST_EXPECT( - b[sfBatchResult.jsonName] == strHex(batchResults[index].result)); + b[sfBatchResult.jsonName] == + strHex(batchResults[index].result)); BEAST_EXPECT( b[sfTransactionType.jsonName] == batchResults[index].txType); if (batchResults[index].hash != "") @@ -78,7 +80,9 @@ class Batch_test : public beast::unit_test::suite validateBatchMeta( Json::Value meta, STAmount const& balance, - std::uint32_t const& sequence) + std::uint32_t const& sequence, + std::optional ownerCount = std::nullopt, + std::optional ticketCount = std::nullopt) { for (Json::Value const& node : meta[sfAffectedNodes.jsonName]) { @@ -94,12 +98,20 @@ class Batch_test : public beast::unit_test::suite std::uint32_t const prevSeq = previousFields[sfSequence.jsonName].asUInt(); BEAST_EXPECT(prevSeq == sequence); + if (ownerCount.has_value()) + BEAST_EXPECT( + previousFields[sfOwnerCount.jsonName].asUInt() == + *ownerCount); + if (ticketCount.has_value()) + BEAST_EXPECT( + previousFields[sfTicketCount.jsonName].asUInt() == + *ticketCount); } } } } - Json::Value + Json::Value addBatchTx( Json::Value jv, Json::Value const& tx, @@ -119,7 +131,8 @@ class Batch_test : public beast::unit_test::suite batchTransaction[jss::RawTransaction][jss::Sequence] = 0; // Set batch transaction details - auto& batchTxn = batchTransaction[jss::RawTransaction][sfBatchTxn.jsonName]; + auto& batchTxn = + batchTransaction[jss::RawTransaction][sfBatchTxn.jsonName]; batchTxn = Json::Value{}; batchTxn[sfOuterAccount.jsonName] = outerAccount.human(); batchTxn[sfBatchIndex.jsonName] = batchIndex; @@ -206,7 +219,7 @@ 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"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -217,7 +230,7 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx1, alice, 0, seq); + jv = addBatchTx(jv, tx1, alice, 1, seq); auto const txResult = withBatch ? ter(tesSUCCESS) : ter(temDISABLED); @@ -248,7 +261,7 @@ class Batch_test : public beast::unit_test::suite // temINVALID_FLAG: Batch: invalid flags. { - auto const seq = env.seq("alice"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -262,7 +275,7 @@ class Batch_test : public beast::unit_test::suite // temMALFORMED: Batch: txns array empty. { - auto const seq = env.seq("alice"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -276,17 +289,17 @@ class Batch_test : public beast::unit_test::suite // temMALFORMED: Batch: txns array exceeds 12 entries. { - auto const seq = env.seq("alice"); + 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 = 0; i < 13; ++i) + 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); + jv = addBatchTx(jv, tx, alice, i, seq); } env(jv, ter(temMALFORMED)); @@ -314,11 +327,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, XRP(10)); - // jv = addBatchTx(jv, tx1, alice, 0, env.seq(alice)); + 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, 1, env.seq(bob)); + jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob)); for (auto const& signer : signers) { @@ -340,9 +353,9 @@ class Batch_test : public beast::unit_test::suite env.close(); } - // // temMALFORMED: Batch: TransactionType missing in array entry. + // temMALFORMED: Batch: TransactionType missing in array entry. // { - // auto const seq = env.seq("alice"); + // auto const seq = env.seq(alice); // Json::Value jv; // jv[jss::TransactionType] = jss::Batch; // jv[jss::Account] = alice.human(); @@ -362,7 +375,7 @@ class Batch_test : public beast::unit_test::suite // temMALFORMED: Batch: batch cannot have inner batch txn. { - auto const seq = env.seq("alice"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -386,11 +399,47 @@ class Batch_test : public beast::unit_test::suite btx = addBatchTx(btx, btx1, alice, 0, seq, 0); } - // // jv = addBatchTx(jv, btx, 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, 1, seq, 1); + jv = addBatchTx(jv, tx2, alice, 2, seq); + + env(jv, ter(temMALFORMED)); + env.close(); + } + + // temMALFORMED: 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 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)); env.close(); @@ -417,11 +466,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, XRP(10)); - // // jv = addBatchTx(jv, tx1, alice, 0, env.seq(alice), 0); + 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 = addBatchTx(jv, tx2, alice, 0, env.seq(bob), 1); jv = addBatchSignatures(jv, signers); env(jv, ter(temBAD_SIGNER)); @@ -480,11 +529,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, USD(10)); - // // jv = addBatchTx(jv, tx1, alice, 0, preAliceSeq, 0); + 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, 1); + jv = addBatchTx(jv, tx2, alice, 10, preBobSeq); jv = addBatchSignatures(jv, signers); @@ -551,11 +600,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, USD(10)); - // // jv = addBatchTx(jv, tx1, alice, 0, env.seq(alice), 0); + 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), 1); + jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob)); jv = addBatchSignatures(jv, signers); @@ -590,7 +639,7 @@ class Batch_test : public beast::unit_test::suite // tfAllOrNothing { - auto const seq = env.seq("alice"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -601,16 +650,16 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - // // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); + 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, 1, seq, 1); + jv = addBatchTx(jv, tx2, alice, 2, seq); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(1)); - // // jv = addBatchTx(jv, tx3, alice, 2, seq, 2); + jv = addBatchTx(jv, tx3, alice, 3, seq); // Internally tefNO_AUTH_REQUIRED env(jv, @@ -622,7 +671,7 @@ class Batch_test : public beast::unit_test::suite // tfUntilFailure { - auto const seq = env.seq("alice"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -633,16 +682,16 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - // // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); + 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, 1, seq, 1); + jv = addBatchTx(jv, tx2, alice, 2, seq); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(1)); - // // jv = addBatchTx(jv, tx3, alice, 2, seq, 2); + jv = addBatchTx(jv, tx3, alice, 3, seq); // Internally tefNO_AUTH_REQUIRED env(jv, @@ -654,7 +703,7 @@ class Batch_test : public beast::unit_test::suite // tfOnlyOne { - auto const seq = env.seq("alice"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -666,11 +715,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = jtx::trust(alice, alice["USD"](1000), tfSetfAuth); - // // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); + jv = addBatchTx(jv, tx1, alice, 1, seq); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - // // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); + jv = addBatchTx(jv, tx2, alice, 2, seq); // Internally tefNO_AUTH_REQUIRED env(jv, @@ -682,7 +731,7 @@ class Batch_test : public beast::unit_test::suite // tfIndependent { - auto const seq = env.seq("alice"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -694,11 +743,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = jtx::trust(alice, alice["USD"](1000), tfSetfAuth); - // // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); + jv = addBatchTx(jv, tx1, alice, 1, seq); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - // // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); + jv = addBatchTx(jv, tx2, alice, 2, seq); // Internally tefNO_AUTH_REQUIRED env(jv, @@ -731,7 +780,7 @@ 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"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -742,11 +791,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); + jv = addBatchTx(jv, tx1, alice, 1, seq); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); + jv = addBatchTx(jv, tx2, alice, 2, seq); env(jv, fee(feeDrops * 2), @@ -757,12 +806,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "3FC47334C663DB77520598095095A7C3AB85C9863E56F5687AD1E78132530" - "0FF"}, + "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE5"}, {"tesSUCCESS", "Payment", - "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5" - "BE5"}, + "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607DA"}, }}; Json::Value params; @@ -772,7 +819,7 @@ class Batch_test : public beast::unit_test::suite auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); - validateBatchTxns(txn[jss::metaData], testCases); + validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 7); @@ -795,7 +842,7 @@ 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"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -806,11 +853,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); + jv = addBatchTx(jv, tx1, alice, 1, seq); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(999)); - // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); + jv = addBatchTx(jv, tx2, alice, 2, seq); env(jv, fee(feeDrops * 2), @@ -818,13 +865,23 @@ class Batch_test : public beast::unit_test::suite ter(tecBATCH_FAILURE)); env.close(); + std::vector testCases = {{ + {"tesSUCCESS", + "Payment", + "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE5"}, + {"tecUNFUNDED_PAYMENT", + "Payment", + "68803BEF141614DBBB34FA34BE0E485D79A43328891A9A8BDC461B6F22836A5C"}, + }}; + Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; - auto const txn = getTxByIndex(jrr, 2); + auto const txn = getTxByIndex(jrr, 1); + validateBatchTxns(txn[jss::metaData], 1, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 5); @@ -853,7 +910,7 @@ 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"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -864,15 +921,15 @@ class Batch_test : public beast::unit_test::suite // Tx 2 Json::Value const tx1 = pay(alice, bob, XRP(999)); - // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); + jv = addBatchTx(jv, tx1, alice, 1, seq); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); + jv = addBatchTx(jv, tx2, alice, 2, seq); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx3, alice, 2, seq, 2); + jv = addBatchTx(jv, tx3, alice, 3, seq); env(jv, fee(feeDrops * 3), txflags(tfOnlyOne), ter(tesSUCCESS)); env.close(); @@ -880,12 +937,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tecUNFUNDED_PAYMENT", "Payment", - "1C9CBF5AF5D0AA97CDF7AE7175D7BA27FA4DD274CF0B4C650475C635F5DBEFC" - "0"}, + "093B51856BA4C111D626D933AC8D8EF8CCEB16B754EFE8A03819043E4927F503"}, {"tesSUCCESS", "Payment", - "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE" - "5"}, + "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607DA"}, }}; Json::Value params; @@ -895,7 +950,7 @@ class Batch_test : public beast::unit_test::suite auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); - validateBatchTxns(txn[jss::metaData], testCases); + validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 7); @@ -923,7 +978,7 @@ 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"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -934,19 +989,19 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); + jv = addBatchTx(jv, tx1, alice, 1, seq); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); + jv = addBatchTx(jv, tx2, alice, 2, seq); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(999)); - // jv = addBatchTx(jv, tx3, alice, 2, seq, 2); + jv = addBatchTx(jv, tx3, alice, 3, seq); // Tx 4 Json::Value const tx4 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx4, alice, 3, seq, 3); + jv = addBatchTx(jv, tx4, alice, 4, seq); env(jv, fee(feeDrops * 4), txflags(tfUntilFailure), ter(tesSUCCESS)); env.close(); @@ -954,16 +1009,13 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "3FC47334C663DB77520598095095A7C3AB85C9863E56F5687AD1E781325300F" - "F"}, + "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE5"}, {"tesSUCCESS", "Payment", - "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE" - "5"}, + "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607DA"}, {"tecUNFUNDED_PAYMENT", "Payment", - "68803BEF141614DBBB34FA34BE0E485D79A43328891A9A8BDC461B6F22836A5" - "C"}, + "E6FC37AF2B22F398E7D32B89C73D2443DF8BE7A2F35CA8B0B6AF6E9A504A67F4"}, }}; Json::Value params; @@ -973,7 +1025,7 @@ class Batch_test : public beast::unit_test::suite auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 3); - validateBatchTxns(txn[jss::metaData], testCases); + validateBatchTxns(txn[jss::metaData], 4, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 8); @@ -1001,7 +1053,7 @@ 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"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -1012,19 +1064,19 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); + jv = addBatchTx(jv, tx1, alice, 1, seq); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); + jv = addBatchTx(jv, tx2, alice, 2, seq); // Tx 3 Json::Value const tx3 = pay(alice, bob, XRP(999)); - // jv = addBatchTx(jv, tx3, alice, 2, seq, 2); + jv = addBatchTx(jv, tx3, alice, 3, seq); // Tx 4 Json::Value const tx4 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx4, alice, 3, seq, 3); + jv = addBatchTx(jv, tx4, alice, 4, seq); env(jv, fee(feeDrops * 4), txflags(tfIndependent), ter(tesSUCCESS)); env.close(); @@ -1032,20 +1084,16 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "3FC47334C663DB77520598095095A7C3AB85C9863E56F5687AD1E781325300F" - "F"}, + "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE5"}, {"tesSUCCESS", "Payment", - "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE" - "5"}, + "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607DA"}, {"tecUNFUNDED_PAYMENT", "Payment", - "68803BEF141614DBBB34FA34BE0E485D79A43328891A9A8BDC461B6F22836A5" - "C"}, + "E6FC37AF2B22F398E7D32B89C73D2443DF8BE7A2F35CA8B0B6AF6E9A504A67F4"}, {"tesSUCCESS", "Payment", - "37A717146557951C8B1271843A3255C6A3B3465D2DD2E48FF7EB2670168E784" - "1"}, + "19E953305CF8D48C481ED35A577196432463AE420D52D68463BD5724492C7E96"}, }}; Json::Value params; @@ -1055,7 +1103,7 @@ class Batch_test : public beast::unit_test::suite auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 4); - validateBatchTxns(txn[jss::metaData], testCases); + validateBatchTxns(txn[jss::metaData], 5, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 9); @@ -1083,6 +1131,7 @@ 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); @@ -1093,7 +1142,7 @@ class Batch_test : public beast::unit_test::suite Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); - jv[jss::Sequence] = env.seq(alice); + jv[jss::Sequence] = seq; auto const batchFee = ((signers.size() + 2) * feeDrops) + feeDrops * 2; jv[jss::Fee] = to_string(batchFee); jv[jss::Flags] = tfAllOrNothing; @@ -1104,11 +1153,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, XRP(10)); - // jv = addBatchTx(jv, tx1, alice, 0, env.seq(alice), 0); + 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), 1); + jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob)); jv = addBatchSignatures(jv, signers); @@ -1119,8 +1168,7 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "CE9F2CC015613C5A11D1E2B0F9340EDDA977C2AD1321A8C1358EC66E3710BA2" - "4"}, + "F74D7914EB0CB080E7004AA11AAFCA7687558F1B43C0B34A0438BBC9AE708571"}, {"tesSUCCESS", "Payment", "9464BBDA1E5893486507DDA75D702739B9FE3DA94D9D002A2DBD3840688AF76" @@ -1134,8 +1182,8 @@ class Batch_test : public beast::unit_test::suite auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); - validateBatchTxns(txn[jss::metaData], testCases); - validateBatchMeta(txn[jss::metaData], STAmount(XRP(1000)), 4); + validateBatchTxns(txn[jss::metaData], 3, testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 6); BEAST_EXPECT(env.seq(bob) == 6); @@ -1166,7 +1214,7 @@ 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"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -1177,11 +1225,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); + jv = addBatchTx(jv, tx1, alice, 1, seq); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); + jv = addBatchTx(jv, tx2, alice, 2, seq); env(jv, fee(feeDrops * 2 + (feeDrops * 4)), @@ -1193,12 +1241,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "9A6D5FD4DB3EBC179D51F9DA2950474DA093E853E7D0C7446413F5101F8C84E" - "5"}, + "21131DBC8CD39D1A514939F988B56235F33A38BD58762CE0CAF8EFA9489DB327"}, {"tesSUCCESS", "Payment", - "21131DBC8CD39D1A514939F988B56235F33A38BD58762CE0CAF8EFA9489DB32" - "7"}, + "1C25CCB1FF8A57B53B39B9287BA48DCD62DF3F213D125FF22C8A891FAC955C32"}, }}; Json::Value params; @@ -1208,7 +1254,7 @@ class Batch_test : public beast::unit_test::suite auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); - validateBatchTxns(txn[jss::metaData], testCases); + validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 8); @@ -1262,11 +1308,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, XRP(10)); - // jv = addBatchTx(jv, tx1, alice, 0, env.seq(alice), 0); + 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), 1); + jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob)); jv = addBatchMultiSignatures(jv, 0, bob, signers); @@ -1277,12 +1323,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "CE9F2CC015613C5A11D1E2B0F9340EDDA977C2AD1321A8C1358EC66E3710BA2" - "4"}, + "F74D7914EB0CB080E7004AA11AAFCA7687558F1B43C0B34A0438BBC9AE708571"}, {"tesSUCCESS", "Payment", - "9464BBDA1E5893486507DDA75D702739B9FE3DA94D9D002A2DBD3840688AF76" - "6"}, + "9464BBDA1E5893486507DDA75D702739B9FE3DA94D9D002A2DBD3840688AF766"}, }}; Json::Value params; @@ -1292,7 +1336,7 @@ class Batch_test : public beast::unit_test::suite auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); - validateBatchTxns(txn[jss::metaData], testCases); + validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], STAmount(XRP(1000)), 4); BEAST_EXPECT(env.seq(alice) == 6); @@ -1382,7 +1426,7 @@ 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"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -1406,16 +1450,54 @@ class Batch_test : public beast::unit_test::suite btx = addBatchTx(btx, btx1, alice, 0, seq, 0); } - // jv = addBatchTx(jv, btx, alice, 0, seq, 0); + jv = addBatchTx(jv, btx, alice, 0, seq); // Tx 2 Json::Value const tx2 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); + jv = addBatchTx(jv, tx2, alice, 1, seq); env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(temMALFORMED)); env.close(); } + void + testNoInnerAccountDelete(FeatureBitset features) + { + testcase("no inner account delete"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + 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; + + // Tx 1 + // Json::Value const tx1 = acctdelete(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, 1, seq); + + // env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(temMALFORMED)); + // env.close(); + } + void testAccountSet(FeatureBitset features) { @@ -1425,9 +1507,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"); @@ -1439,7 +1518,7 @@ 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"); + auto const seq = env.seq(alice); Json::Value jv; jv[jss::TransactionType] = jss::Batch; jv[jss::Account] = alice.human(); @@ -1464,10 +1543,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "AccountSet", - "6B6B225E26F2F4811A651D7FD1E4F675D3E9F677C0167F8AAE707E2CB9B508A6"}, + "6B6B225E26F2F4811A651D7FD1E4F675D3E9F677C0167F8AAE707E2CB9B508A" + "6"}, {"tesSUCCESS", "Payment", - "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607DA"}, + "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607D" + "A"}, }}; Json::Value params; @@ -1477,11 +1558,13 @@ class Batch_test : public beast::unit_test::suite auto const jrr = env.rpc("json", "ledger", to_string(params)); std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); - validateBatchTxns(txn[jss::metaData], testCases); + validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); - std::cout << "seq: " << env.seq(alice) << std::endl; - std::cout << "amount: " << env.balance(alice) << std::endl; + auto const sle = env.le(keylet::account(alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT( + sle->getFieldVL(sfDomain) == Blob(domain.begin(), domain.end())); BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - (feeDrops * 2)); @@ -1572,7 +1655,7 @@ class Batch_test : public beast::unit_test::suite auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); - validateBatchTxns(txn[jss::metaData], testCases); + validateBatchTxns(txn[jss::metaData], 3, testCases); BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.seq(bob) == 6); @@ -1647,7 +1730,7 @@ class Batch_test : public beast::unit_test::suite auto const jrr = env.rpc("json", "ledger", to_string(params)); // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 3); - validateBatchTxns(txn[jss::metaData], testCases); + validateBatchTxns(txn[jss::metaData], 3, testCases); BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.balance(alice) == preAlice - (feeDrops * 2)); @@ -1663,9 +1746,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"); @@ -1691,22 +1771,28 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); + 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, 1); + jv = addBatchTx(jv, tx2, alice, 1, seq); - env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ticket::use(aliceTicketSeq++), ter(tesSUCCESS)); + env(jv, + fee(feeDrops * 2), + txflags(tfAllOrNothing), + ticket::use(aliceTicketSeq++), + ter(tesSUCCESS)); env.close(); std::vector testCases = {{ {"tesSUCCESS", "Payment", - "18629D496965A11CAB4454B86DB794BA07DABA3EE154DEFE9259977221E937E5"}, + "18629D496965A11CAB4454B86DB794BA07DABA3EE154DEFE9259977221E937E" + "5"}, {"tesSUCCESS", "Payment", - "7654B768E091768EB0D43C8EE33B7E72C82BC7A584D578F2646721F69AEEAB72"}, + "7654B768E091768EB0D43C8EE33B7E72C82BC7A584D578F2646721F69AEEAB7" + "2"}, }}; Json::Value params; @@ -1714,10 +1800,15 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - std::cout << jrr << std::endl; + // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); - validateBatchTxns(txn[jss::metaData], testCases); - validateBatchMeta(txn[jss::metaData], preAlice, seq); + validateBatchTxns(txn[jss::metaData], 3, testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq, 10, 10); + + auto const sle = env.le(keylet::account(alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->getFieldU32(sfOwnerCount) == 9); + BEAST_EXPECT(sle->getFieldU32(sfTicketCount) == 9); BEAST_EXPECT(env.seq(alice) == 17); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - (feeDrops * 2)); @@ -1732,10 +1823,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - // test::jtx::Env env{*this, envconfig()}; - Env env{*this, envconfig(), features, nullptr, - beast::severities::kTrace - }; + test::jtx::Env env{*this, envconfig()}; auto const feeDrops = env.current()->fees().base; auto const alice = Account("alice"); @@ -1762,11 +1850,11 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx1, alice, 0, 0, 0, aliceTicketSeq); + 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, 1, aliceTicketSeq); + jv = addBatchTx(jv, tx2, alice, 1, 0, aliceTicketSeq); env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); env.close(); @@ -1774,10 +1862,10 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "18629D496965A11CAB4454B86DB794BA07DABA3EE154DEFE9259977221E937E5"}, + "684C0FE631535577FE8BE663848AB3AFE71C6CD688101E4FEB43B9C13374DBB2"}, {"tesSUCCESS", "Payment", - "7654B768E091768EB0D43C8EE33B7E72C82BC7A584D578F2646721F69AEEAB72"}, + "CBF12A852B0418FAF406C480BE991CE1EA2D0F16323412BFFA9F89CA7449B21E"}, }}; Json::Value params; @@ -1785,12 +1873,17 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - std::cout << jrr << std::endl; + // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); - validateBatchTxns(txn[jss::metaData], testCases); - validateBatchMeta(txn[jss::metaData], preAlice, seq); + validateBatchTxns(txn[jss::metaData], 3, testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq, 10, 10); - BEAST_EXPECT(env.seq(alice) == 17); + auto const sle = env.le(keylet::account(alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->getFieldU32(sfOwnerCount) == 8); + BEAST_EXPECT(sle->getFieldU32(sfTicketCount) == 8); + + BEAST_EXPECT(env.seq(alice) == 16); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - (feeDrops * 2)); BEAST_EXPECT(env.balance(bob) == preBob + XRP(2)); } @@ -1804,9 +1897,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"); @@ -1832,22 +1922,28 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value const tx1 = pay(alice, bob, XRP(1)); - // jv = addBatchTx(jv, tx1, alice, 1, 0, 0, aliceTicketSeq); + 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, 1); + jv = addBatchTx(jv, tx2, alice, 0, seq); - env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ticket::use(aliceTicketSeq++), ter(tesSUCCESS)); + env(jv, + fee(feeDrops * 2), + txflags(tfAllOrNothing), + ticket::use(aliceTicketSeq++), + ter(tesSUCCESS)); env.close(); std::vector testCases = {{ {"tesSUCCESS", "Payment", - "CBF12A852B0418FAF406C480BE991CE1EA2D0F16323412BFFA9F89CA7449B21E"}, + "CBF12A852B0418FAF406C480BE991CE1EA2D0F16323412BFFA9F89CA7449B21" + "E"}, {"tesSUCCESS", "Payment", - "18629D496965A11CAB4454B86DB794BA07DABA3EE154DEFE9259977221E937E5"}, + "18629D496965A11CAB4454B86DB794BA07DABA3EE154DEFE9259977221E937E" + "5"}, }}; Json::Value params; @@ -1855,10 +1951,15 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - std::cout << jrr << std::endl; + // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); - validateBatchTxns(txn[jss::metaData], testCases); - validateBatchMeta(txn[jss::metaData], preAlice, seq); + validateBatchTxns(txn[jss::metaData], 3, testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq, 10, 10); + + auto const sle = env.le(keylet::account(alice)); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->getFieldU32(sfOwnerCount) == 8); + BEAST_EXPECT(sle->getFieldU32(sfTicketCount) == 8); BEAST_EXPECT(env.seq(alice) == 16); BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - (feeDrops * 2)); @@ -1882,11 +1983,18 @@ class Batch_test : public beast::unit_test::suite // testMultisignMultiParty(features); // testSubmit(features); // testNoInnerBatch(features); + // testNoInnerAccountDelete(features); testAccountSet(features); - // testMultiPartyObjectCreate(features); + // // testMultiPartyObjectCreate(features); // testTicketsOuter(features); // testTicketsInner(features); // testTicketsOuterInner(features); + + // AccountDelete + // MultiPartyObject + // prevFields on Failure + // prevFields repeat owner directory + // Invariant Failure (Revert) } public: diff --git a/src/xrpld/app/tx/detail/ApplyContext.cpp b/src/xrpld/app/tx/detail/ApplyContext.cpp index 8a726cd6836..793438d80d2 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.cpp +++ b/src/xrpld/app/tx/detail/ApplyContext.cpp @@ -94,8 +94,12 @@ ApplyContext::applyPrev(ApplyViewImpl& avi) for (auto const& obj : *sleBase) { if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) && - !sle->hasMatchingEntry(obj)) + (!sle->hasMatchingEntry(obj) || obj.getFName() == sfSequence || + obj.getFName() == sfOwnerCount || + obj.getFName() == sfTicketCount)) + { prevFields.emplace_back(obj); + } } avi.addBatchPrevMetaData(std::move(prevFields)); } diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index a7eb492e953..913787dfede 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -48,7 +48,7 @@ Batch::preflight(PreflightContext const& ctx) if (tx.getFlags() & tfBatchMask) { - JLOG(ctx.j.debug()) << "Batch: invalid flags."; + JLOG(ctx.j.warn()) << "Batch: invalid flags."; return temINVALID_FLAG; } @@ -57,13 +57,13 @@ Batch::preflight(PreflightContext const& ctx) auto const& txns = tx.getFieldArray(sfRawTransactions); if (txns.empty()) { - JLOG(ctx.j.debug()) << "Batch: txns array empty."; + JLOG(ctx.j.warn()) << "Batch: txns array empty."; return temMALFORMED; } if (txns.size() > 8) { - JLOG(ctx.j.debug()) << "Batch: txns array exceeds 12 entries."; + JLOG(ctx.j.warn()) << "Batch: txns array exceeds 12 entries."; return temMALFORMED; } @@ -77,27 +77,36 @@ Batch::preflight(PreflightContext const& ctx) ctx.tx.checkBatchSign(requireCanonicalSig, ctx.rules); if (!sigResult) { - JLOG(ctx.j.debug()) << "Batch: invalid batch txn signature."; + JLOG(ctx.j.warn()) << "Batch: invalid batch txn signature."; return temBAD_SIGNATURE; } } for (STObject txn : txns) { + auto const innerAccount = txn.getAccountID(sfAccount); if (!txn.isFieldPresent(sfTransactionType)) { - JLOG(ctx.j.debug()) + JLOG(ctx.j.warn()) << "Batch: TransactionType missing in array entry."; return temMALFORMED; } if (txn.getFieldU16(sfTransactionType) == ttBATCH) { - JLOG(ctx.j.debug()) << "Batch: batch cannot have inner batch txn."; + JLOG(ctx.j.warn()) << "Batch: batch cannot have inner batch txn."; return temMALFORMED; } - if (auto const innerAccount = txn.getAccountID(sfAccount); - innerAccount != outerAccount) + if (txn.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; + } + + if (innerAccount != outerAccount) { if (tx.getFieldArray(sfBatchSigners).end() == std::find_if( @@ -107,7 +116,7 @@ Batch::preflight(PreflightContext const& ctx) return signer.getAccountID(sfAccount) == innerAccount; })) { - JLOG(ctx.j.debug()) + JLOG(ctx.j.warn()) << "Batch: inner txn not signed by the right user."; return temBAD_SIGNER; } @@ -122,6 +131,16 @@ Batch::preclaim(PreclaimContext const& ctx) if (!ctx.view.rules().enabled(featureBatch)) return temDISABLED; + for (STObject txn : ctx.tx.getFieldArray(sfRawTransactions)) + { + auto const innerAccount = txn.getAccountID(sfAccount); + auto const sle = ctx.view.read(keylet::account(innerAccount)); + if (!sle) + { + JLOG(ctx.j.warn()) << "Batch: delay: inner account does not exist."; + return terNO_ACCOUNT; + } + } return tesSUCCESS; } @@ -164,6 +183,7 @@ Batch::doApply() // Atomic Revert on non tec failure if (!isTecClaim(ter)) { + JLOG(ctx_.journal.warn()) << "Batch: Inner txn failed." << ter; result = tecBATCH_FAILURE; changed = false; break; From f9fe787514ecc8eee3e4f51c83d0442f8ec55f16 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 10 Sep 2024 16:26:56 +0200 Subject: [PATCH 40/71] update tests --- src/test/app/Batch_test.cpp | 186 +++++++++--------------------------- 1 file changed, 46 insertions(+), 140 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 3c622d36fe7..aa03814c625 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -409,41 +409,41 @@ class Batch_test : public beast::unit_test::suite env.close(); } - // temMALFORMED: 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; + // // temMALFORMED: 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}; + // // 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; + // // 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}; + // // 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); - } + // // 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); + // 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); + // // Tx 2 + // Json::Value const tx2 = pay(alice, bob, XRP(1)); + // jv = addBatchTx(jv, tx2, alice, 2, seq); - env(jv, ter(temMALFORMED)); - env.close(); - } + // env(jv, ter(temMALFORMED)); + // env.close(); + // } // temBAD_SIGNER: Batch: inner txn not signed by the right user. { @@ -1406,98 +1406,6 @@ class Batch_test : public beast::unit_test::suite } } - void - testNoInnerBatch(FeatureBitset features) - { - testcase("no inner batch"); - - using namespace test::jtx; - using namespace std::literals; - - test::jtx::Env env{*this, envconfig()}; - - auto const feeDrops = env.current()->fees().base; - auto const alice = Account("alice"); - auto const bob = Account("bob"); - auto const carol = Account("carol"); - env.fund(XRP(1000), alice, bob, carol); - env.close(); - - 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 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, 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), ter(temMALFORMED)); - env.close(); - } - - void - testNoInnerAccountDelete(FeatureBitset features) - { - testcase("no inner account delete"); - - using namespace test::jtx; - using namespace std::literals; - - test::jtx::Env env{*this, envconfig()}; - - auto const feeDrops = env.current()->fees().base; - auto const alice = Account("alice"); - auto const bob = Account("bob"); - auto const carol = Account("carol"); - env.fund(XRP(1000), alice, bob, carol); - env.close(); - - 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; - - // Tx 1 - // Json::Value const tx1 = acctdelete(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, 1, seq); - - // env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(temMALFORMED)); - // env.close(); - } - void testAccountSet(FeatureBitset features) { @@ -1556,7 +1464,7 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - std::cout << jrr << std::endl; + // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); @@ -1969,31 +1877,29 @@ class Batch_test : public beast::unit_test::suite void testWithFeats(FeatureBitset features) { - // testEnable(features); - // testPreflight(features); - // testBadSequence(features); - // testBadFee(features); - // testOutOfSequence(features); - // testAllOrNothing(features); - // testOnlyOne(features); - // testUntilFailure(features); - // testIndependent(features); - // testMultiParty(features); - // testMultisign(features); - // testMultisignMultiParty(features); - // testSubmit(features); - // testNoInnerBatch(features); - // testNoInnerAccountDelete(features); + testEnable(features); + testPreflight(features); + testBadSequence(features); + testBadFee(features); + testOutOfSequence(features); + testAllOrNothing(features); + testOnlyOne(features); + testUntilFailure(features); + testIndependent(features); + testMultiParty(features); + testMultisign(features); + testMultisignMultiParty(features); + testSubmit(features); testAccountSet(features); // // testMultiPartyObjectCreate(features); - // testTicketsOuter(features); - // testTicketsInner(features); - // testTicketsOuterInner(features); + testTicketsOuter(features); + testTicketsInner(features); + testTicketsOuterInner(features); // AccountDelete // MultiPartyObject // prevFields on Failure - // prevFields repeat owner directory + // prevFields repeat owner count // Invariant Failure (Revert) } From 0deea7450559d896b68bf28d89622abbe104bb20 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 10 Sep 2024 16:29:50 +0200 Subject: [PATCH 41/71] clang-format --- src/test/app/Batch_test.cpp | 63 ++++++++++++++------- src/xrpld/app/tx/detail/Batch.cpp | 2 +- src/xrpld/ledger/detail/ApplyStateTable.cpp | 3 +- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index aa03814c625..940a58fe15b 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -428,7 +428,8 @@ class Batch_test : public beast::unit_test::suite // btx[jss::Sequence] = seq; // // Batch Transactions - // btx[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; + // btx[sfRawTransactions.jsonName] = + // Json::Value{Json::arrayValue}; // // bTx 1 // Json::Value const btx1 = pay(alice, bob, XRP(1)); @@ -806,10 +807,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE5"}, + "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5" + "BE5"}, {"tesSUCCESS", "Payment", - "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607DA"}, + "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C1360" + "7DA"}, }}; Json::Value params; @@ -868,10 +871,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE5"}, + "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5" + "BE5"}, {"tecUNFUNDED_PAYMENT", "Payment", - "68803BEF141614DBBB34FA34BE0E485D79A43328891A9A8BDC461B6F22836A5C"}, + "68803BEF141614DBBB34FA34BE0E485D79A43328891A9A8BDC461B6F22836" + "A5C"}, }}; Json::Value params; @@ -937,10 +942,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tecUNFUNDED_PAYMENT", "Payment", - "093B51856BA4C111D626D933AC8D8EF8CCEB16B754EFE8A03819043E4927F503"}, + "093B51856BA4C111D626D933AC8D8EF8CCEB16B754EFE8A03819043E4927F50" + "3"}, {"tesSUCCESS", "Payment", - "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607DA"}, + "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607D" + "A"}, }}; Json::Value params; @@ -1009,13 +1016,16 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE5"}, + "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE" + "5"}, {"tesSUCCESS", "Payment", - "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607DA"}, + "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607D" + "A"}, {"tecUNFUNDED_PAYMENT", "Payment", - "E6FC37AF2B22F398E7D32B89C73D2443DF8BE7A2F35CA8B0B6AF6E9A504A67F4"}, + "E6FC37AF2B22F398E7D32B89C73D2443DF8BE7A2F35CA8B0B6AF6E9A504A67F" + "4"}, }}; Json::Value params; @@ -1084,16 +1094,20 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE5"}, + "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE" + "5"}, {"tesSUCCESS", "Payment", - "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607DA"}, + "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607D" + "A"}, {"tecUNFUNDED_PAYMENT", "Payment", - "E6FC37AF2B22F398E7D32B89C73D2443DF8BE7A2F35CA8B0B6AF6E9A504A67F4"}, + "E6FC37AF2B22F398E7D32B89C73D2443DF8BE7A2F35CA8B0B6AF6E9A504A67F" + "4"}, {"tesSUCCESS", "Payment", - "19E953305CF8D48C481ED35A577196432463AE420D52D68463BD5724492C7E96"}, + "19E953305CF8D48C481ED35A577196432463AE420D52D68463BD5724492C7E9" + "6"}, }}; Json::Value params; @@ -1168,7 +1182,8 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "F74D7914EB0CB080E7004AA11AAFCA7687558F1B43C0B34A0438BBC9AE708571"}, + "F74D7914EB0CB080E7004AA11AAFCA7687558F1B43C0B34A0438BBC9AE70857" + "1"}, {"tesSUCCESS", "Payment", "9464BBDA1E5893486507DDA75D702739B9FE3DA94D9D002A2DBD3840688AF76" @@ -1241,10 +1256,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "21131DBC8CD39D1A514939F988B56235F33A38BD58762CE0CAF8EFA9489DB327"}, + "21131DBC8CD39D1A514939F988B56235F33A38BD58762CE0CAF8EFA9489DB32" + "7"}, {"tesSUCCESS", "Payment", - "1C25CCB1FF8A57B53B39B9287BA48DCD62DF3F213D125FF22C8A891FAC955C32"}, + "1C25CCB1FF8A57B53B39B9287BA48DCD62DF3F213D125FF22C8A891FAC955C3" + "2"}, }}; Json::Value params; @@ -1323,10 +1340,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "F74D7914EB0CB080E7004AA11AAFCA7687558F1B43C0B34A0438BBC9AE708571"}, + "F74D7914EB0CB080E7004AA11AAFCA7687558F1B43C0B34A0438BBC9AE70857" + "1"}, {"tesSUCCESS", "Payment", - "9464BBDA1E5893486507DDA75D702739B9FE3DA94D9D002A2DBD3840688AF766"}, + "9464BBDA1E5893486507DDA75D702739B9FE3DA94D9D002A2DBD3840688AF76" + "6"}, }}; Json::Value params; @@ -1770,10 +1789,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "684C0FE631535577FE8BE663848AB3AFE71C6CD688101E4FEB43B9C13374DBB2"}, + "684C0FE631535577FE8BE663848AB3AFE71C6CD688101E4FEB43B9C13374DBB" + "2"}, {"tesSUCCESS", "Payment", - "CBF12A852B0418FAF406C480BE991CE1EA2D0F16323412BFFA9F89CA7449B21E"}, + "CBF12A852B0418FAF406C480BE991CE1EA2D0F16323412BFFA9F89CA7449B21" + "E"}, }}; Json::Value params; diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 913787dfede..cde793309f4 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -97,7 +97,7 @@ Batch::preflight(PreflightContext const& ctx) return temMALFORMED; } - if (txn.getFieldU16(sfTransactionType) == ttACCOUNT_DELETE && + if (txn.getFieldU16(sfTransactionType) == ttACCOUNT_DELETE && innerAccount == outerAccount) { JLOG(ctx.j.warn()) diff --git a/src/xrpld/ledger/detail/ApplyStateTable.cpp b/src/xrpld/ledger/detail/ApplyStateTable.cpp index 54534480e39..12529051cd7 100644 --- a/src/xrpld/ledger/detail/ApplyStateTable.cpp +++ b/src/xrpld/ledger/detail/ApplyStateTable.cpp @@ -159,7 +159,8 @@ ApplyStateTable::apply( } auto const origNode = to.read(keylet::unchecked(item.first)); auto curNode = item.second.second; - if ((type == &sfModifiedNode) && (*curNode == *origNode) && !isBatch) + if ((type == &sfModifiedNode) && (*curNode == *origNode) && + !isBatch) continue; std::uint16_t nodeType = curNode ? curNode->getFieldU16(sfLedgerEntryType) From 9266676ea8b1312e2c7e9d989ae94f4f765f25da Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 11 Sep 2024 01:24:07 +0200 Subject: [PATCH 42/71] remove unused variable --- src/libxrpl/protocol/STTx.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index f49f9f092fb..305c1d74fc7 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -395,7 +395,7 @@ STTx::checkBatchSingleSign( RequireFullyCanonicalSig requireCanonicalSig) const { // We don't allow both a non-empty sfSigningPubKey and an sfSigners. - // That would allow the transaction to be signed two ways. So if both + // That would allow the transaction to be signed two ways. So if both // fields are present the signature is invalid. if (batchSigner.isFieldPresent(sfSigners)) return Unexpected("Cannot both single- and multi-sign."); @@ -414,11 +414,9 @@ STTx::checkBatchSingleSign( (requireCanonicalSig == RequireFullyCanonicalSig::yes); auto const spk = batchSigner.getFieldVL(sfSigningPubKey); - if (publicKeyType(makeSlice(spk))) { Blob const signature = batchSigner.getFieldVL(sfTxnSignature); - Blob const data = getSigningData(*this); validSig = verify( PublicKey(makeSlice(spk)), From 3fff4caeddc7f75c96c0f0fad930855c7771bf12 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 11 Sep 2024 01:24:55 +0200 Subject: [PATCH 43/71] rename func/field names --- include/xrpl/protocol/SField.h | 2 +- src/libxrpl/protocol/InnerObjectFormats.cpp | 2 +- src/libxrpl/protocol/SField.cpp | 2 +- src/test/app/Batch_test.cpp | 242 ++++++++++++++------ src/xrpld/app/tx/detail/ApplyContext.cpp | 25 +- src/xrpld/app/tx/detail/ApplyContext.h | 8 +- src/xrpld/app/tx/detail/Batch.cpp | 16 +- src/xrpld/app/tx/detail/Transactor.cpp | 19 +- 8 files changed, 232 insertions(+), 84 deletions(-) diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index 7309ae76a81..7bfc2b27d70 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -577,7 +577,7 @@ extern SF_VL const sfHookStateData; extern SF_VL const sfHookReturnString; extern SF_VL const sfHookParameterName; extern SF_VL const sfHookParameterValue; -extern SF_VL const sfBatchResult; +extern SF_VL const sfInnerResult; // account extern SF_ACCOUNT const sfAccount; diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 36377b89506..54fd88feb41 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -151,7 +151,7 @@ InnerObjectFormats::InnerObjectFormats() add(sfBatchExecution.jsonName.c_str(), sfBatchExecution.getCode(), {{sfTransactionType, soeREQUIRED}, - {sfBatchResult, soeREQUIRED}, + {sfInnerResult, soeREQUIRED}, {sfTransactionHash, soeOPTIONAL}}); add(sfBatchTxn.jsonName.c_str(), diff --git a/src/libxrpl/protocol/SField.cpp b/src/libxrpl/protocol/SField.cpp index 832259dd21a..3b1e1ca7b9e 100644 --- a/src/libxrpl/protocol/SField.cpp +++ b/src/libxrpl/protocol/SField.cpp @@ -308,7 +308,7 @@ CONSTRUCT_TYPED_SFIELD(sfDIDDocument, "DIDDocument", VL, CONSTRUCT_TYPED_SFIELD(sfData, "Data", VL, 27); CONSTRUCT_TYPED_SFIELD(sfAssetClass, "AssetClass", VL, 28); CONSTRUCT_TYPED_SFIELD(sfProvider, "Provider", VL, 29); -CONSTRUCT_TYPED_SFIELD(sfBatchResult, "BatchResult", VL, 30); +CONSTRUCT_TYPED_SFIELD(sfInnerResult, "InnerResult", VL, 30); // account CONSTRUCT_TYPED_SFIELD(sfAccount, "Account", ACCOUNT, 1); diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 940a58fe15b..b86308777c9 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -65,7 +65,7 @@ class Batch_test : public beast::unit_test::suite { auto const b = _batchTxn[sfBatchExecution.jsonName]; BEAST_EXPECT( - b[sfBatchResult.jsonName] == + b[sfInnerResult.jsonName] == strHex(batchResults[index].result)); BEAST_EXPECT( b[sfTransactionType.jsonName] == batchResults[index].txType); @@ -409,42 +409,28 @@ class Batch_test : public beast::unit_test::suite env.close(); } - // // temMALFORMED: 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 btx; - // { - // btx[jss::TransactionType] = jss::Batch; - // btx[jss::Account] = alice.human(); - // btx[jss::Sequence] = seq; - - // // Batch Transactions - // btx[sfRawTransactions.jsonName] = - // Json::Value{Json::arrayValue}; + // temMALFORMED: 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; - // // bTx 1 - // Json::Value const btx1 = pay(alice, bob, XRP(1)); - // btx = addBatchTx(btx, btx1, alice, 0, seq, 0); - // } + // Batch Transactions + jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; - // jv = addBatchTx(jv, btx, alice, 1, seq); + // 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); + // Tx 2 + Json::Value const tx2 = pay(alice, bob, XRP(1)); + jv = addBatchTx(jv, tx2, alice, 2, seq); - // env(jv, ter(temMALFORMED)); - // env.close(); - // } + env(jv, ter(temMALFORMED)); + env.close(); + } // temBAD_SIGNER: Batch: inner txn not signed by the right user. { @@ -1505,9 +1491,9 @@ class Batch_test : public beast::unit_test::suite } void - testMultiPartyObjectCreate(FeatureBitset features) + testObjectCreateSequence(FeatureBitset features) { - testcase("multi party object create"); + testcase("object create w/ sequence"); using namespace test::jtx; using namespace std::literals; @@ -1551,13 +1537,13 @@ class Batch_test : public beast::unit_test::suite jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; // Tx 1 - uint256 const chkId{getCheckIndex(alice, seq + 1)}; - Json::Value tx1 = check::create(alice, bob, USD(10)); - // jv = addBatchTx(jv, tx1, alice, 0, env.seq(alice), 0); + 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(bob, chkId, USD(10)); - // jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob), 1); + Json::Value const tx2 = check::cash(alice, chkId, USD(10)); + jv = addBatchTx(jv, tx2, alice, 1, env.seq(alice)); jv = addBatchSignatures(jv, signers); @@ -1567,12 +1553,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "CheckCreate", - "4C63C1F06AE7429CB29D24E32E395C5CEFAB392730B002AF0597E04FCA3651A" - "0"}, + "6443C49FA0E30F09AD6EF418EABC031E70AE854D62D0543F34214A3F1BADB5C" + "1"}, {"tesSUCCESS", "CheckCash", - "4923A3865BFA7DDD2F43CB4C15B461505CA0605079CC99DCBBCEFE210E57B17" - "C"}, + "ABFC2892253C19A9312F5BEF9BDA7399498A9650F3F64777EE5A5C498B6BCFB" + "6"}, }}; Json::Value params; @@ -1583,19 +1569,20 @@ class Batch_test : public beast::unit_test::suite // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.seq(bob) == 6); BEAST_EXPECT(env.balance(alice) == preAlice - (batchFee)); BEAST_EXPECT(env.balance(bob) == preBob); - BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD - USD(10)); - BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD + USD(10)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD + USD(10)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD - USD(10)); } void - testOfferCancel(FeatureBitset features) + testObjectCreateTicket(FeatureBitset features) { - testcase("offer cancel"); + testcase("object create w/ ticket"); using namespace test::jtx; using namespace std::literals; @@ -1616,38 +1603,55 @@ class Batch_test : public beast::unit_test::suite env(pay(gw, bob, USD(100))); env.close(); + std::uint32_t bobTicketSeq{env.seq(bob) + 1}; + env(ticket::create(bob, 10)); + env.close(); + auto const preAlice = env.balance(alice); auto const preBob = env.balance(bob); + 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 - Json::Value tx1 = offer(alice, XRP(50), USD(50)); - // jv = addBatchTx(jv, tx1, alice, 0, seq, 0); + 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 = offer_cancel(alice, seq + 1); - // jv = addBatchTx(jv, tx2, alice, 1, seq, 1); + Json::Value const tx2 = check::cash(alice, chkId, USD(10)); + jv = addBatchTx(jv, tx2, alice, 1, env.seq(alice)); - env(jv, fee(feeDrops * 2), txflags(tfAllOrNothing), ter(tesSUCCESS)); + jv = addBatchSignatures(jv, signers); + + env(jv, ter(tesSUCCESS)); env.close(); std::vector testCases = {{ {"tesSUCCESS", - "OfferCreate", - "145CBCD0C29955E43452EF891979DF63CB9D32CDB6F91FEAEE402D39FC53845" - "5"}, + "CheckCreate", + "3D06827C2B17BAA07887C8E101DC6779EC7A3807E79E88D64022BA14DC0B252" + "C"}, {"tesSUCCESS", - "OfferCancel", - "10C5B1A9861230AD0BC9BA9FD957944B2A06F5A5C888557586D96EE58DD5861" - "8"}, + "CheckCash", + "B276687A136BD0FFE4B03F84ABB5C5F7A72C3D015968CEE83A27A7881E87127" + "F"}, }}; Json::Value params; @@ -1655,13 +1659,112 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; - auto const txn = getTxByIndex(jrr, 3); + std::cout << jrr << std::endl; + auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 7); - BEAST_EXPECT(env.balance(alice) == preAlice - (feeDrops * 2)); + BEAST_EXPECT(env.seq(bob) == 16); + BEAST_EXPECT(env.balance(alice) == preAlice - (batchFee)); + BEAST_EXPECT(env.balance(bob) == preBob); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD + USD(10)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD - USD(10)); + } + + void + testObjectCreate3rdParty(FeatureBitset features) + { + testcase("object create w/ 3rd party"); + + using namespace test::jtx; + 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"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + env.fund(XRP(1000), alice, bob, carol, gw); + env.close(); + + env.trust(USD(1000), alice, bob); + env(pay(gw, alice, USD(100))); + env(pay(gw, bob, USD(100))); + env.close(); + + auto const preAlice = env.balance(alice); + auto const preBob = env.balance(bob); + auto const preCarol = env.balance(carol); + 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 + 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.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "CheckCreate", + "74EE0A770F85E93055072F4BD8286D235AE6B333AF41C7AA44A45DD63643752E"}, + {"tesSUCCESS", + "CheckCash", + "9CFCBABC4729B388C265A45F5B6C13ED2AF67942EC21FE6064FDBBF5F1316093"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + auto const txn = getTxByIndex(jrr, 2); + validateBatchTxns(txn[jss::metaData], 3, testCases); + validateBatchMeta(txn[jss::metaData], preCarol, seq); + + BEAST_EXPECT(env.seq(alice) == 6); + BEAST_EXPECT(env.seq(bob) == 6); + BEAST_EXPECT(env.seq(carol) == 5); + BEAST_EXPECT(env.balance(alice) == preAlice); BEAST_EXPECT(env.balance(bob) == preBob); + BEAST_EXPECT(env.balance(carol) == preCarol - (batchFee)); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD + USD(10)); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD - USD(10)); } void @@ -1912,16 +2015,19 @@ class Batch_test : public beast::unit_test::suite testMultisignMultiParty(features); testSubmit(features); testAccountSet(features); - // // testMultiPartyObjectCreate(features); + testObjectCreateSequence(features); + testObjectCreateTicket(features); + testObjectCreate3rdParty(features); testTicketsOuter(features); testTicketsInner(features); testTicketsOuterInner(features); - // AccountDelete - // MultiPartyObject - // prevFields on Failure - // prevFields repeat owner count - // Invariant Failure (Revert) + // TODO: previousFields repeat `sfOwnerCount` even if there was no + // update to `OwnerCount` + // TODO: PreviousTxnID on the outer batch txn is last inner batch txn? + // TODO: tecINVARIANT_FAILED + // You cannot check the invariants without applying the transactions but + // you cannot revert the transactions after they have been applied. } public: diff --git a/src/xrpld/app/tx/detail/ApplyContext.cpp b/src/xrpld/app/tx/detail/ApplyContext.cpp index 793438d80d2..c2188e7d529 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.cpp +++ b/src/xrpld/app/tx/detail/ApplyContext.cpp @@ -73,8 +73,20 @@ ApplyContext::applyOpenView(OpenView& open) open.apply(base_); } +/** + * Update the AccountRoot ledger entry associated with the batch transaction + * to ensure that the final entry accurately reflects all modifications made + * by inner transactions that affect the same account. + * + * This function retrieves the current AccountRoot entry for the account + * associated with the batch transaction and replaces it in the view. + * This is necessary because inner transactions are processed first, and + * their changes may impact the overall entry of the account. By updating + * the AccountRoot entry, we ensure that any changes made by these inner + * transactions are accounted for in the final entry of the batch transaction. + */ void -ApplyContext::applyBatch() +ApplyContext::updateAccountRootEntry() { AccountID const account = tx.getAccountID(sfAccount); auto const sleBase = base_.read(keylet::account(account)); @@ -82,8 +94,17 @@ ApplyContext::applyBatch() view_->rawReplace(std::make_shared(*sleBase)); } +/** + * Capture the previous state of the AccountRoot ledger entry associated + * with the batch transaction before applying inner transactions. This + * function retrieves the current AccountRoot entry and prepares metadata + * that reflects any changes made by inner transactions that may affect + * the account's overall state. + * @param avi The ApplyViewImpl instance to which the previous metadata + * will be added. + */ void -ApplyContext::applyPrev(ApplyViewImpl& avi) +ApplyContext::batchPrevious(ApplyViewImpl& avi) { AccountID const account = tx.getAccountID(sfAccount); auto const sleBase = base_.read(keylet::account(account)); diff --git a/src/xrpld/app/tx/detail/ApplyContext.h b/src/xrpld/app/tx/detail/ApplyContext.h index 861c984be82..e983780879d 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.h +++ b/src/xrpld/app/tx/detail/ApplyContext.h @@ -87,13 +87,13 @@ class ApplyContext void applyOpenView(OpenView& open); - /** Apply the fee to the account. */ + /** Updates the batch txn account root. */ void - applyBatch(); + updateAccountRootEntry(); - /** Apply the fee to the account. */ + /** Sets the batch prev fields in the metadata. */ void - applyPrev(ApplyViewImpl& avi); + batchPrevious(ApplyViewImpl& avi); /** Get the number of unapplied changes. */ std::size_t diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index cde793309f4..90c6539f885 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -151,11 +151,20 @@ Batch::doApply() bool changed = false; auto const flags = ctx_.tx.getFlags(); + AccountID const outerAccount = ctx_.tx.getAccountID(sfAccount); + TER result = tesSUCCESS; ApplyViewImpl& avi = dynamic_cast(ctx_.view()); OpenView subView(&sb); auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions); + bool const not3rdParty = std::any_of( + txns.begin(), + txns.end(), + [outerAccount](STObject const& txn) { + return txn.getAccountID(sfAccount) == outerAccount; + }); + for (STObject txn : txns) { OpenView innerView(&subView); @@ -172,7 +181,7 @@ Batch::doApply() // Add Inner Txn Metadata STObject meta{sfBatchExecution}; std::string res = transToken(ter); - meta.setFieldVL(sfBatchResult, ripple::Slice{res.data(), res.size()}); + meta.setFieldVL(sfInnerResult, ripple::Slice{res.data(), res.size()}); meta.setFieldU16(sfTransactionType, stx.getTxnType()); if (ter == tesSUCCESS || isTecClaim(ter)) meta.setFieldH256(sfTransactionHash, stx.getTransactionID()); @@ -216,7 +225,10 @@ Batch::doApply() // Apply SubView & PreviousFields if (changed) { - ctx_.applyPrev(avi); + // Only required when not 3rd Party + if (not3rdParty) + ctx_.batchPrevious(avi); + ctx_.applyOpenView(subView); } diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 480208c8db3..9c5f027f2c8 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -1046,13 +1046,22 @@ Transactor::operator()() applied = isTecClaim(result); } - // Apply fee for batch transaction if it was successfully applied + // Update the AccountRoot entry if the batch transaction was successfull if (applied && ctx_.tx.getTxnType() == ttBATCH && result == tesSUCCESS) { - // If the transaction is a batch transaction, the fee is already - // deducted from the account balance before executing the inner txns. - // So, we need to "re" apply the fee again. - ctx_.applyBatch(); + auto const outerAccount = ctx_.tx.getAccountID(sfAccount); + auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions); + bool const not3rdParty = std::any_of( + txns.begin(), + txns.end(), + [outerAccount](STObject const& txn) { + return txn.getAccountID(sfAccount) == outerAccount; + }); + + // Only update the account root entry if the batch transaction was + // not a 3rd party transaction + if (not3rdParty) + ctx_.updateAccountRootEntry(); } if (applied) From ecfc64b67006f431be05a1c46924c8ba71c4ffaa Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 11 Sep 2024 01:28:41 +0200 Subject: [PATCH 44/71] clang-format --- src/test/app/Batch_test.cpp | 6 ++++-- src/xrpld/app/tx/detail/Batch.cpp | 6 ++---- src/xrpld/app/tx/detail/Transactor.cpp | 4 +--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index b86308777c9..61322a5b2bb 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -1741,10 +1741,12 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "CheckCreate", - "74EE0A770F85E93055072F4BD8286D235AE6B333AF41C7AA44A45DD63643752E"}, + "74EE0A770F85E93055072F4BD8286D235AE6B333AF41C7AA44A45DD63643752" + "E"}, {"tesSUCCESS", "CheckCash", - "9CFCBABC4729B388C265A45F5B6C13ED2AF67942EC21FE6064FDBBF5F1316093"}, + "9CFCBABC4729B388C265A45F5B6C13ED2AF67942EC21FE6064FDBBF5F131609" + "3"}, }}; Json::Value params; diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 90c6539f885..668daebee6d 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -159,12 +159,10 @@ Batch::doApply() auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions); bool const not3rdParty = std::any_of( - txns.begin(), - txns.end(), - [outerAccount](STObject const& txn) { + txns.begin(), txns.end(), [outerAccount](STObject const& txn) { return txn.getAccountID(sfAccount) == outerAccount; }); - + for (STObject txn : txns) { OpenView innerView(&subView); diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 9c5f027f2c8..40662492df3 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -1052,9 +1052,7 @@ Transactor::operator()() auto const outerAccount = ctx_.tx.getAccountID(sfAccount); auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions); bool const not3rdParty = std::any_of( - txns.begin(), - txns.end(), - [outerAccount](STObject const& txn) { + txns.begin(), txns.end(), [outerAccount](STObject const& txn) { return txn.getAccountID(sfAccount) == outerAccount; }); From 096f4bf49ac8be8f6d4f324035ec2f0694bc146e Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 11 Sep 2024 01:30:33 +0200 Subject: [PATCH 45/71] [fold] remove log --- src/test/app/Batch_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 61322a5b2bb..e6021b12097 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -1659,7 +1659,7 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - std::cout << jrr << std::endl; + // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); From 274660b6a03f31463bdbc7b9f0dbb58e19ea2a7d Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 18 Sep 2024 21:44:22 +0200 Subject: [PATCH 46/71] add sfTxIDs and update signing --- include/xrpl/protocol/Batch.h | 37 ++ include/xrpl/protocol/HashPrefix.h | 3 + include/xrpl/protocol/SField.h | 1 + src/libxrpl/protocol/SField.cpp | 1 + src/libxrpl/protocol/STTx.cpp | 26 +- src/libxrpl/protocol/TER.cpp | 2 +- src/libxrpl/protocol/TxFormats.cpp | 1 + src/test/app/Batch_test.cpp | 875 ++++++++--------------------- src/test/jtx.h | 1 + src/test/jtx/batch.h | 192 +++++++ src/test/jtx/impl/batch.cpp | 190 +++++++ src/test/jtx/impl/multisign.cpp | 44 -- src/test/jtx/multisign.h | 53 -- src/xrpld/app/tx/detail/Batch.cpp | 59 +- 14 files changed, 695 insertions(+), 790 deletions(-) create mode 100644 include/xrpl/protocol/Batch.h create mode 100644 src/test/jtx/batch.h create mode 100644 src/test/jtx/impl/batch.cpp 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); } From 04c5bde0e37fc0f514ffbe2d1f12e8e797d74216 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 18 Sep 2024 21:46:04 +0200 Subject: [PATCH 47/71] clang-format --- include/xrpl/protocol/Batch.h | 2 +- src/libxrpl/protocol/STTx.cpp | 2 +- src/test/app/Batch_test.cpp | 3 ++- src/test/jtx/batch.h | 8 ++++---- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/include/xrpl/protocol/Batch.h b/include/xrpl/protocol/Batch.h index ca830dd4f8f..2b4144313e8 100644 --- a/include/xrpl/protocol/Batch.h +++ b/include/xrpl/protocol/Batch.h @@ -16,8 +16,8 @@ //============================================================================== #include -#include #include +#include namespace ripple { diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 786b2dc2037..3a5e8aac634 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -29,8 +29,8 @@ #include #include #include -#include #include +#include #include #include #include diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 0e3ca760ae5..6cadb3fbbad 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -329,7 +329,8 @@ class Batch_test : public beast::unit_test::suite env.close(); } - // TODO: #40 failed: unhandled exception: Field not found: TransactionType + // TODO: #40 failed: unhandled exception: Field not found: + // TransactionType // // temINVALID_BATCH: Batch: TransactionType missing in array entry. // { // auto const seq = env.seq(alice); diff --git a/src/test/jtx/batch.h b/src/test/jtx/batch.h index f09f66826a9..38d91b905e6 100644 --- a/src/test/jtx/batch.h +++ b/src/test/jtx/batch.h @@ -112,8 +112,9 @@ class sig sig(std::vector signers_); template - requires std::convertible_to - explicit sig(AccountType&& a0, Accounts&&... aN) + requires std::convertible_to explicit sig( + AccountType&& a0, + Accounts&&... aN) : sig{std::vector{ std::forward(a0), std::forward(aN)...}} @@ -165,8 +166,7 @@ class msig msig(Account const& masterAccount, std::vector signers_); template - requires std::convertible_to - explicit msig( + requires std::convertible_to explicit msig( Account const& masterAccount, AccountType&& a0, Accounts&&... aN) From 659e7053f3b7d1d0a8a8d94ac7ac9d5bbd6bb49d Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 18 Sep 2024 22:23:12 +0200 Subject: [PATCH 48/71] [fixup] rerun actions --- src/test/app/Batch_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 6cadb3fbbad..9d611deac4e 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -1535,7 +1535,7 @@ class Batch_test : public beast::unit_test::suite auto const seq = env.seq(alice); auto const batchFee = feeDrops * 2; - env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + env(batch::batch(alice, 0, 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++)); @@ -1557,7 +1557,7 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; + std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq, 10, 10); From 597e7fe05a4a6b205ac00fa470cc3d9a94b74f20 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 18 Sep 2024 23:01:04 +0200 Subject: [PATCH 49/71] [fixup] remove comments --- src/test/app/Batch_test.cpp | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 9d611deac4e..3b0a4761e47 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -625,7 +625,6 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); @@ -674,7 +673,6 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 1); validateBatchTxns(txn[jss::metaData], 1, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); @@ -730,7 +728,6 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); @@ -790,7 +787,6 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 3); validateBatchTxns(txn[jss::metaData], 4, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); @@ -854,7 +850,6 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 4); validateBatchTxns(txn[jss::metaData], 5, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); @@ -911,7 +906,6 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); @@ -969,7 +963,6 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); @@ -1030,7 +1023,6 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], STAmount(XRP(1000)), 4); @@ -1077,7 +1069,6 @@ class Batch_test : public beast::unit_test::suite s.erase(); jt.stx->add(s); auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result]; - // std::cout << jrr << std::endl; BEAST_EXPECT( jrr[jss::status] == "error" && jrr[jss::error] == "invalidTransaction"); @@ -1093,7 +1084,6 @@ class Batch_test : public beast::unit_test::suite "4F832B3D8314F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90FE023240000" "0000801814AE123A8556F3CF91154711376AFB0F894F832B3D00101400E1"; auto const jrr = env.rpc("submit", txBlob)[jss::result]; - // std::cout << jrr << std::endl; BEAST_EXPECT( jrr[jss::status] == "success" && jrr[jss::engine_result] == "temINVALID_BATCH"); @@ -1150,7 +1140,6 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); @@ -1226,7 +1215,6 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); @@ -1298,7 +1286,6 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq); @@ -1369,7 +1356,6 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preCarol, seq); @@ -1432,7 +1418,6 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq, 10, 10); @@ -1494,7 +1479,6 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - // std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq, 10, 10); @@ -1538,7 +1522,7 @@ class Batch_test : public beast::unit_test::suite env(batch::batch(alice, 0, 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++)); + ticket::use(aliceTicketSeq)); env.close(); std::vector testCases = {{ From 639f8b7b83a68455f83626834b735518c8affd7d Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sun, 22 Sep 2024 10:58:08 +0200 Subject: [PATCH 50/71] fix no account --- src/test/app/Batch_test.cpp | 60 +++++++++++++++++++++++++- src/xrpld/app/tx/detail/Batch.cpp | 18 +++----- src/xrpld/app/tx/detail/Transactor.cpp | 5 ++- 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 3b0a4761e47..9f9e216c3dc 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -1092,6 +1092,64 @@ class Batch_test : public beast::unit_test::suite } } + void + testNoAccount(FeatureBitset features) + { + testcase("no account"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.fund(XRP(10000), alice); + env.close(); + env.memoize(bob); + + auto const preAlice = env.balance(alice); + + // Tx 1 + Json::Value tx1 = noop(bob); + tx1[sfSetFlag.fieldName] = asfAllowTrustLineClawback; + + auto const ledSeq = env.current()->seq(); + 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(1000)), alice, 1, seq), + batch::add(tx1, alice, 0, ledSeq), + batch::sig(bob)); + env.close(); + + std::vector testCases = {{ + {"tesSUCCESS", + "Payment", + "A2639F4AC0E57B007A8EEFAEDD00DBD608588A34ECB29A2A55139F0147AA7C9" + "9"}, + {"tesSUCCESS", + "AccountSet", + "16515DD535232F8D9B5993539CBFB6DC49C0274B8DD18E0C7199BFE30511F0C" + "1"}, + }}; + + Json::Value params; + params[jss::ledger_index] = env.current()->seq() - 1; + params[jss::transactions] = true; + params[jss::expand] = true; + auto const jrr = env.rpc("json", "ledger", to_string(params)); + auto const txn = getTxByIndex(jrr, 3); + validateBatchTxns(txn[jss::metaData], 3, testCases); + validateBatchMeta(txn[jss::metaData], preAlice, seq); + + BEAST_EXPECT(env.seq(alice) == 6); + BEAST_EXPECT(env.seq(bob) == 5); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1000) - batchFee); + BEAST_EXPECT(env.balance(bob) == XRP(1000)); + } + void testAccountSet(FeatureBitset features) { @@ -1541,7 +1599,6 @@ class Batch_test : public beast::unit_test::suite params[jss::transactions] = true; params[jss::expand] = true; auto const jrr = env.rpc("json", "ledger", to_string(params)); - std::cout << jrr << std::endl; auto const txn = getTxByIndex(jrr, 2); validateBatchTxns(txn[jss::metaData], 3, testCases); validateBatchMeta(txn[jss::metaData], preAlice, seq, 10, 10); @@ -1572,6 +1629,7 @@ class Batch_test : public beast::unit_test::suite testMultisign(features); testMultisignMultiParty(features); testSubmit(features); + testNoAccount(features); testAccountSet(features); testObjectCreateSequence(features); testObjectCreateTicket(features); diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 42ceef06797..f8639fa0754 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -83,7 +83,6 @@ Batch::preflight(PreflightContext const& ctx) return temINVALID; } - auto const innerAccount = stx.getAccountID(sfAccount); if (!stx.isFieldPresent(sfTransactionType)) { JLOG(ctx.j.warn()) @@ -96,6 +95,7 @@ Batch::preflight(PreflightContext const& ctx) return temINVALID_BATCH; } + auto const innerAccount = stx.getAccountID(sfAccount); if (stx.getFieldU16(sfTransactionType) == ttACCOUNT_DELETE && innerAccount == outerAccount) { @@ -107,6 +107,12 @@ Batch::preflight(PreflightContext const& ctx) if (innerAccount != outerAccount) { + if (!tx.isFieldPresent(sfBatchSigners)) + { + JLOG(ctx.j.warn()) << "Batch: missing batch signers."; + return temBAD_SIGNER; + } + if (tx.getFieldArray(sfBatchSigners).end() == std::find_if( tx.getFieldArray(sfBatchSigners).begin(), @@ -146,16 +152,6 @@ Batch::preclaim(PreclaimContext const& ctx) if (!ctx.view.rules().enabled(featureBatch)) return temDISABLED; - for (STObject txn : ctx.tx.getFieldArray(sfRawTransactions)) - { - auto const innerAccount = txn.getAccountID(sfAccount); - auto const sle = ctx.view.read(keylet::account(innerAccount)); - if (!sle) - { - JLOG(ctx.j.warn()) << "Batch: delay: inner account does not exist."; - return terNO_ACCOUNT; - } - } return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index a592840dd4b..6c0dcd9ab81 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -555,8 +555,11 @@ Transactor::checkBatchSign(PreclaimContext const& ctx) auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner))); auto const sleAccount = ctx.view.read(keylet::account(idAccount)); + + // We dont need to check the regular key or multisign here + // because the account does not exist. if (!sleAccount) - ret = terNO_ACCOUNT; + return tesSUCCESS; ret = checkSingleSign( idSigner, idAccount, sleAccount, ctx.view.rules(), ctx.j); From 75fd76bc5e6155c6ef6b2eebfd3e5d95cbf52f5f Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Sun, 22 Sep 2024 11:00:48 +0200 Subject: [PATCH 51/71] clang-format --- src/xrpld/app/tx/detail/Transactor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 6c0dcd9ab81..f6b7f698b2f 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -556,7 +556,7 @@ Transactor::checkBatchSign(PreclaimContext const& ctx) auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner))); auto const sleAccount = ctx.view.read(keylet::account(idAccount)); - // We dont need to check the regular key or multisign here + // We dont need to check the regular key or multisign here // because the account does not exist. if (!sleAccount) return tesSUCCESS; From f185d3852e7dbf1d636c3b3b2197fc30a9dfde70 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 30 Oct 2024 22:08:45 +0100 Subject: [PATCH 52/71] [fold] remove comment --- src/xrpld/app/tx/detail/InvariantCheck.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index a26a324da50..8e92ef81309 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -143,7 +143,6 @@ XRPNotCreated::finalize( ReadView const&, beast::Journal const& j) { - // DA TODO: FIX THIS if (tx.getTxnType() == ttBATCH && res == tesSUCCESS) { drops_ = -fee.drops(); From 767a88e7c2011b0c926b676219a7b5873e84601a Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 30 Oct 2024 22:09:05 +0100 Subject: [PATCH 53/71] [fold] update headers --- src/test/jtx/batch.h | 4 ++-- src/test/jtx/impl/batch.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/jtx/batch.h b/src/test/jtx/batch.h index 38d91b905e6..dafebf9ec33 100644 --- a/src/test/jtx/batch.h +++ b/src/test/jtx/batch.h @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2018 Ripple Labs Inc. + 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 @@ -44,7 +44,7 @@ batch( STAmount const& fee, std::uint32_t flags); -// /** Adds a new Batch Txn on a JTx. */ +/** Adds a new Batch Txn on a JTx. */ class add { private: diff --git a/src/test/jtx/impl/batch.cpp b/src/test/jtx/impl/batch.cpp index 37a324b26a3..694127aa9ad 100644 --- a/src/test/jtx/impl/batch.cpp +++ b/src/test/jtx/impl/batch.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2018 Ripple Labs Inc. + 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 From ee0381172f46fabce7b85be3a065a755d129051c Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 30 Oct 2024 22:09:31 +0100 Subject: [PATCH 54/71] [fold] change error response text --- src/libxrpl/protocol/TER.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index d6c32887d14..179faf72849 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -115,7 +115,7 @@ transResults() MAKE_ERROR(tecTOKEN_PAIR_NOT_FOUND, "Token pair is not found in Oracle object."), MAKE_ERROR(tecARRAY_EMPTY, "Array is empty."), MAKE_ERROR(tecARRAY_TOO_LARGE, "Array is too large."), - MAKE_ERROR(tecBATCH_FAILURE, "Tx Batch Failure."), + MAKE_ERROR(tecBATCH_FAILURE, "Batch transaction failure."), MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."), MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."), @@ -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, "Invalid inner batch transaction type."), + MAKE_ERROR(temINVALID_BATCH, "Malformed: Invalid inner batch transaction type."), MAKE_ERROR(terRETRY, "Retry transaction."), MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."), From 9d8368deb99026be8a5c18d7677b4f8d43174151 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 30 Oct 2024 22:09:47 +0100 Subject: [PATCH 55/71] [fold] change flags bit --- include/xrpl/protocol/TxFlags.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 3db4406bf13..6ed5b13707d 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -186,10 +186,10 @@ constexpr std::uint32_t tfClearAccountCreateAmount = 0x00010000; constexpr std::uint32_t tfBridgeModifyMask = ~(tfUniversal | tfClearAccountCreateAmount); // Batch Flags -constexpr std::uint32_t tfAllOrNothing = 0x00000001; -constexpr std::uint32_t tfOnlyOne = 0x00000002; -constexpr std::uint32_t tfUntilFailure = 0x00000004; -constexpr std::uint32_t tfIndependent = 0x00000008; +constexpr std::uint32_t tfAllOrNothing = 0x00010000; +constexpr std::uint32_t tfOnlyOne = 0x00020000; +constexpr std::uint32_t tfUntilFailure = 0x00040000; +constexpr std::uint32_t tfIndependent = 0x00080000; constexpr std::uint32_t const tfBatchMask = ~(tfUniversal | tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent); From b408b8058d56bbf0bd7fdcae1e895f905452ecdf Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 30 Oct 2024 22:09:58 +0100 Subject: [PATCH 56/71] [fold] fix formatting --- include/xrpl/protocol/TxMeta.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/xrpl/protocol/TxMeta.h b/include/xrpl/protocol/TxMeta.h index 98d60d85d96..844c49ad7a8 100644 --- a/include/xrpl/protocol/TxMeta.h +++ b/include/xrpl/protocol/TxMeta.h @@ -137,6 +137,7 @@ class TxMeta { mBatchExecutions = batchExecutions; } + bool hasBatchExecutions() const { From 27d46852f6b26ca84c33ca6f1a57efb7fe1d5928 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 31 Oct 2024 00:40:38 +0100 Subject: [PATCH 57/71] [fold] normal consequences --- src/xrpld/app/tx/detail/Batch.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/xrpld/app/tx/detail/Batch.h b/src/xrpld/app/tx/detail/Batch.h index 3bd1d3e0182..14f12ca01fb 100644 --- a/src/xrpld/app/tx/detail/Batch.h +++ b/src/xrpld/app/tx/detail/Batch.h @@ -30,7 +30,7 @@ namespace ripple { class Batch : public Transactor { public: - static constexpr ConsequencesFactoryType ConsequencesFactory{Custom}; + static constexpr ConsequencesFactoryType ConsequencesFactory{Normal}; explicit Batch(ApplyContext& ctx) : Transactor(ctx) { @@ -39,9 +39,6 @@ class Batch : public Transactor static XRPAmount calculateBaseFee(ReadView const& view, STTx const& tx); - static TxConsequences - makeTxConsequences(PreflightContext const& ctx); - static NotTEC preflight(PreflightContext const& ctx); From 419b14dc225c5ae71b1d0edf8153f5c97d57cbe2 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 31 Oct 2024 00:41:13 +0100 Subject: [PATCH 58/71] [fold] refactor single/multi sign --- src/libxrpl/protocol/STTx.cpp | 268 +++++++++++++--------------------- 1 file changed, 105 insertions(+), 163 deletions(-) diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 3a5e8aac634..d5f0ab9a504 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -191,7 +191,7 @@ STTx::getSeqProxy() const if (isFieldPresent(sfBatchTxn)) { - STObject const batchTxn = const_cast(*this) + STObject const& batchTxn = const_cast(*this) .getField(sfBatchTxn) .downcast(); @@ -246,6 +246,32 @@ STTx::checkSign( return Unexpected("Internal signature check failure."); } +Expected +STTx::checkBatchSign( + RequireFullyCanonicalSig requireCanonicalSig, + Rules const& rules) const +{ + try + { + STArray const& signers{getFieldArray(sfBatchSigners)}; + for (auto const& signer : signers) + { + Blob const& signingPubKey = signer.getFieldVL(sfSigningPubKey); + auto const result = signingPubKey.empty() + ? checkBatchMultiSign(signer, requireCanonicalSig, rules) + : checkBatchSingleSign(signer, requireCanonicalSig); + + if (!result) + return result; + } + return {}; + } + catch (std::exception const&) + { + } + return Unexpected("Internal signature check failure."); +} + Json::Value STTx::getJson(JsonOptions options) const { @@ -325,70 +351,49 @@ STTx::getMetaSQL( getFieldU32(sfSequence) % inLedger % status % rTxn % escapedMetaData); } -Expected -STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const +static Expected +singleSignHelper( + STObject const& signer, + Slice const& data, + STTx::RequireFullyCanonicalSig requireCanonicalSig, + std::uint32_t flags) { - // We don't allow both a non-empty sfSigningPubKey and an sfSigners. - // That would allow the transaction to be signed two ways. So if both - // fields are present the signature is invalid. - if (isFieldPresent(sfSigners)) + if (signer.isFieldPresent(sfSigners)) return Unexpected("Cannot both single- and multi-sign."); bool validSig = false; try { - bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) || - (requireCanonicalSig == RequireFullyCanonicalSig::yes); - - auto const spk = getFieldVL(sfSigningPubKey); + bool const fullyCanonical = (flags & tfFullyCanonicalSig) || + (requireCanonicalSig == STTx::RequireFullyCanonicalSig::yes); + auto const spk = signer.getFieldVL(sfSigningPubKey); if (publicKeyType(makeSlice(spk))) { - Blob const signature = getFieldVL(sfTxnSignature); - Blob const data = getSigningData(*this); - + Blob const signature = signer.getFieldVL(sfTxnSignature); validSig = verify( PublicKey(makeSlice(spk)), - makeSlice(data), + data, makeSlice(signature), fullyCanonical); } } catch (std::exception const&) { - // Assume it was a signature failure. validSig = false; } - if (validSig == false) + + if (!validSig) return Unexpected("Invalid signature."); - // Signature was verified. + return {}; } Expected -STTx::checkBatchSign( - RequireFullyCanonicalSig requireCanonicalSig, - Rules const& rules) const +STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const { - try - { - STArray const& signers{getFieldArray(sfBatchSigners)}; - for (auto const& signer : signers) - { - Blob const& signingPubKey = signer.getFieldVL(sfSigningPubKey); - auto const result = signingPubKey.empty() - ? checkBatchMultiSign(signer, requireCanonicalSig, rules) - : checkBatchSingleSign(signer, requireCanonicalSig); - - if (!result) - return result; - } - return {}; - } - catch (std::exception const&) - { - } - return Unexpected("Internal signature check failure."); + auto const data = getSigningData(*this); + return singleSignHelper(*this, makeSlice(data), requireCanonicalSig, getFlags()); } Expected @@ -396,75 +401,18 @@ STTx::checkBatchSingleSign( STObject const& batchSigner, RequireFullyCanonicalSig requireCanonicalSig) const { - // We don't allow both a non-empty sfSigningPubKey and an sfSigners. - // That would allow the transaction to be signed two ways. So if both - // fields are present the signature is invalid. - if (batchSigner.isFieldPresent(sfSigners)) - return Unexpected("Cannot both single- and multi-sign."); - - bool validSig = false; - try - { - Serializer msg; - serializeBatch(msg, getFlags(), getFieldV256(sfTxIDs)); - bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) || - (requireCanonicalSig == RequireFullyCanonicalSig::yes); - - auto const spk = batchSigner.getFieldVL(sfSigningPubKey); - if (publicKeyType(makeSlice(spk))) - { - Blob const signature = batchSigner.getFieldVL(sfTxnSignature); - validSig = verify( - PublicKey(makeSlice(spk)), - msg.slice(), - makeSlice(signature), - fullyCanonical); - } - } - catch (std::exception const&) - { - // Assume it was a signature failure. - validSig = false; - } - if (validSig == false) - return Unexpected("Invalid signature."); - // Signature was verified. - return {}; + Serializer msg; + serializeBatch(msg, getFlags(), getFieldV256(sfTxIDs)); + return singleSignHelper(batchSigner, msg.slice(), requireCanonicalSig, getFlags()); } Expected -STTx::checkBatchMultiSign( - STObject const& batchSigner, - RequireFullyCanonicalSig requireCanonicalSig, - Rules const& rules) const +multiSignHelper( + STArray const& signers, + AccountID const& txnAccountID, + bool const fullyCanonical, + std::function(AccountID const&)> makeMsg) { - // Make sure the MultiSigners are present. Otherwise they are not - // attempting multi-signing and we just have a bad SigningPubKey. - if (!batchSigner.isFieldPresent(sfSigners)) - return Unexpected("Empty SigningPubKey."); - - // We don't allow both an sfSigners and an sfTxnSignature. Both fields - // being present would indicate that the transaction is signed both ways. - if (batchSigner.isFieldPresent(sfTxnSignature)) - return Unexpected("Cannot both single- and multi-sign."); - - STArray const& signers{batchSigner.getFieldArray(sfSigners)}; - - // There are well known bounds that the number of signers must be within. - if (signers.size() < minMultiSigners || - signers.size() > maxMultiSigners(&rules)) - return Unexpected("Invalid Signers array size."); - - 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); - - // Determine whether signatures must be full canonical. - bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) || - (requireCanonicalSig == RequireFullyCanonicalSig::yes); - // Signers must be in sorted order by AccountID. AccountID lastAccountID(beast::zero); @@ -491,6 +439,8 @@ STTx::checkBatchMultiSign( bool validSig = false; try { + std::vector msgData = makeMsg(accountID); + Slice msgSlice(msgData.data(), msgData.size()); auto spk = signer.getFieldVL(sfSigningPubKey); if (publicKeyType(makeSlice(spk))) @@ -498,12 +448,12 @@ STTx::checkBatchMultiSign( Blob const signature = signer.getFieldVL(sfTxnSignature); validSig = verify( PublicKey(makeSlice(spk)), - msg.slice(), + msgSlice, makeSlice(signature), fullyCanonical); } } - catch (std::exception const&) + catch (std::exception const& e) { // We assume any problem lies with the signature. validSig = false; @@ -517,96 +467,88 @@ STTx::checkBatchMultiSign( return {}; } + Expected -STTx::checkMultiSign( +STTx::checkBatchMultiSign( + STObject const& batchSigner, RequireFullyCanonicalSig requireCanonicalSig, Rules const& rules) const { // Make sure the MultiSigners are present. Otherwise they are not // attempting multi-signing and we just have a bad SigningPubKey. - if (!isFieldPresent(sfSigners)) + if (!batchSigner.isFieldPresent(sfSigners)) return Unexpected("Empty SigningPubKey."); // We don't allow both an sfSigners and an sfTxnSignature. Both fields // being present would indicate that the transaction is signed both ways. - if (isFieldPresent(sfTxnSignature)) + if (batchSigner.isFieldPresent(sfTxnSignature)) return Unexpected("Cannot both single- and multi-sign."); - STArray const& signers{getFieldArray(sfSigners)}; + STArray const& signers{batchSigner.getFieldArray(sfSigners)}; // There are well known bounds that the number of signers must be within. if (signers.size() < minMultiSigners || 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)}; - // We also use the sfAccount field inside the loop. Get it once. - auto const txnAccountID = getAccountID(sfAccount); + auto const txnAccountID = batchSigner.getAccountID(sfAccount); // Determine whether signatures must be full canonical. bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) || (requireCanonicalSig == RequireFullyCanonicalSig::yes); - // Signers must be in sorted order by AccountID. - AccountID lastAccountID(beast::zero); - - for (auto const& signer : signers) - { - auto const accountID = signer.getAccountID(sfAccount); + Serializer msg; + serializeBatch(msg, getFlags(), getFieldV256(sfTxIDs)); - // The account owner may not multisign for themselves. - if (accountID == txnAccountID) - return Unexpected("Invalid multisigner."); + return multiSignHelper( + signers, + txnAccountID, + fullyCanonical, + [&msg](AccountID const&) -> std::vector { return msg.getData(); }); +} - // No duplicate signers allowed. - if (lastAccountID == accountID) - return Unexpected("Duplicate Signers not allowed."); +Expected +STTx::checkMultiSign( + RequireFullyCanonicalSig requireCanonicalSig, + Rules const& rules) const +{ + // Make sure the MultiSigners are present. Otherwise they are not + // attempting multi-signing and we just have a bad SigningPubKey. + if (!isFieldPresent(sfSigners)) + return Unexpected("Empty SigningPubKey."); - // Accounts must be in order by account ID. No duplicates allowed. - if (lastAccountID > accountID) - return Unexpected("Unsorted Signers array."); + // We don't allow both an sfSigners and an sfTxnSignature. Both fields + // being present would indicate that the transaction is signed both ways. + if (isFieldPresent(sfTxnSignature)) + return Unexpected("Cannot both single- and multi-sign."); - // The next signature must be greater than this one. - lastAccountID = accountID; + STArray const& signers{getFieldArray(sfSigners)}; - // Verify the signature. - bool validSig = false; - try - { - Serializer s = dataStart; - finishMultiSigningData(accountID, s); + // There are well known bounds that the number of signers must be within. + if (signers.size() < minMultiSigners || + signers.size() > maxMultiSigners(&rules)) + return Unexpected("Invalid Signers array size."); - auto spk = signer.getFieldVL(sfSigningPubKey); + // We also use the sfAccount field inside the loop. Get it once. + auto const txnAccountID = getAccountID(sfAccount); - if (publicKeyType(makeSlice(spk))) - { - Blob const signature = signer.getFieldVL(sfTxnSignature); + // Determine whether signatures must be full canonical. + bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) || + (requireCanonicalSig == RequireFullyCanonicalSig::yes); - validSig = verify( - PublicKey(makeSlice(spk)), - s.slice(), - makeSlice(signature), - fullyCanonical); - } - } - catch (std::exception const&) - { - // We assume any problem lies with the signature. - validSig = false; - } - if (!validSig) - return Unexpected( - std::string("Invalid signature on account ") + - toBase58(accountID) + "."); - } - // All signatures verified. - return {}; + return multiSignHelper( + signers, + txnAccountID, + fullyCanonical, + [this](AccountID const& accountID) -> std::vector { + Serializer dataStart = startMultiSigningData(*this); + finishMultiSigningData(accountID, dataStart); + return dataStart.getData(); + }); } + //------------------------------------------------------------------------------ static bool From 6c87e709a22337e567fbf1fd21bdd48ec945ae9f Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 31 Oct 2024 01:27:46 +0100 Subject: [PATCH 59/71] [fold] review comments - allow only one flag - validate batch signers array (max and valid) - move `calculateBaseFee` - move `preflight2` - no duplicate TxIDs - no duplicate BatchSigners --- include/xrpl/protocol/TxFlags.h | 2 + src/test/app/Batch_test.cpp | 182 +++++++++++++++++++------- src/test/jtx/impl/batch.cpp | 2 + src/xrpld/app/tx/detail/Batch.cpp | 211 ++++++++++++++++++------------ 4 files changed, 264 insertions(+), 133 deletions(-) diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 6ed5b13707d..667dbeec723 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -192,6 +192,8 @@ constexpr std::uint32_t tfUntilFailure = 0x00040000; constexpr std::uint32_t tfIndependent = 0x00080000; constexpr std::uint32_t const tfBatchMask = ~(tfUniversal | tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent); +constexpr std::uint32_t const tfBatchSubTx = + tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent; // clang-format on diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 9f9e216c3dc..e61c3ef3382 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -230,11 +230,50 @@ class Batch_test : public beast::unit_test::suite auto const seq = env.seq(alice); auto const batchFee = feeDrops * 2; env(batch::batch(alice, seq, batchFee, tfAllOrNothing), - txflags(tfRequireAuth), + txflags(tfDisallowXRP), ter(temINVALID_FLAG)); env.close(); } + // temMALFORMED: Batch: too many flags. + { + auto const seq = env.seq(alice); + auto const batchFee = feeDrops * 2; + env(batch::batch(alice, seq, batchFee, tfAllOrNothing), + txflags(tfAllOrNothing | tfOnlyOne), + ter(temMALFORMED)); + env.close(); + } + + // temMALFORMED: Batch: hashes array size does not match txns. + { + auto const batchFee = ((1 + 2) * feeDrops) + feeDrops * 2; + Json::Value jv = + batch::batch(alice, env.seq(alice), batchFee, tfAllOrNothing); + + // 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())); + + // Add another txn hash to the TxIDs array + jv[sfTxIDs.jsonName].append(to_string(stx2.getTransactionID())); + + env(jv, batch::sig(bob), ter(temMALFORMED)); + env.close(); + } + // temARRAY_EMPTY: Batch: txns array empty. { auto const seq = env.seq(alice); @@ -244,7 +283,7 @@ class Batch_test : public beast::unit_test::suite env.close(); } - // temARRAY_TOO_LARGE: Batch: txns array exceeds 12 entries. + // temARRAY_TOO_LARGE: Batch: txns array exceeds 8 entries. { auto const seq = env.seq(alice); auto const batchFee = feeDrops * 2; @@ -258,10 +297,28 @@ class Batch_test : public beast::unit_test::suite 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(); + } + + // temARRAY_TOO_LARGE: Batch: signers array exceeds 8 entries. + { + auto const seq = env.seq(alice); + auto const batchFee = ((9 + 2) * feeDrops) + 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::sig( + bob, + carol, + alice, + bob, + carol, + alice, + bob, + carol, + alice, + alice), ter(temARRAY_TOO_LARGE)); env.close(); } @@ -272,19 +329,10 @@ class Batch_test : public beast::unit_test::suite {0, bob}, }}; - 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}; - jv[sfTxIDs.jsonName] = Json::Value{Json::arrayValue}; + Json::Value jv = + batch::batch(alice, env.seq(alice), batchFee, tfAllOrNothing); // Tx 1 Json::Value tx1 = pay(alice, bob, XRP(10)); @@ -329,37 +377,61 @@ class Batch_test : public beast::unit_test::suite env.close(); } - // 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 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: duplicate TxID found. + { + auto const batchFee = ((1 + 2) * feeDrops) + feeDrops * 2; + Json::Value jv = + batch::batch(alice, env.seq(alice), batchFee, tfAllOrNothing); + + // 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())}; + + // Tx 2 + Json::Value const tx2 = pay(bob, alice, XRP(5)); + jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob)); + + // Add a duplicate hash + jv[sfTxIDs.jsonName].append(to_string(stx1.getTransactionID())); + jv[sfTxIDs.jsonName].append(to_string(stx1.getTransactionID())); + + env(jv, batch::sig(bob), ter(temMALFORMED)); + env.close(); + } + + // temMALFORMED: Batch: txn hash does not match TxIDs hash. + { + auto const batchFee = ((1 + 2) * feeDrops) + feeDrops * 2; + Json::Value jv = + batch::batch(alice, env.seq(alice), batchFee, tfAllOrNothing); + + // 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())}; + + // 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())}; + + // Add the hashes out of order + jv[sfTxIDs.jsonName].append(to_string(stx2.getTransactionID())); + jv[sfTxIDs.jsonName].append(to_string(stx1.getTransactionID())); + + env(jv, batch::sig(bob), ter(temMALFORMED)); + env.close(); + } + + // temINVALID_BATCH: Batch: TransactionType missing in array entry. + // DA: Impossible Test // temINVALID_BATCH: Batch: batch cannot have inner batch txn. { @@ -387,7 +459,7 @@ class Batch_test : public beast::unit_test::suite env.close(); } - // temBAD_SIGNER: Batch: inner txn not signed by the right user. + // temBAD_SIGNER: Batch: no account signature for inner txn. { auto const seq = env.seq(alice); auto const batchFee = ((1 + 2) * feeDrops) + feeDrops * 2; @@ -398,6 +470,18 @@ class Batch_test : public beast::unit_test::suite ter(temBAD_SIGNER)); env.close(); } + + // temBAD_SIGNER: Batch: unique signers does not match batch signers. + { + 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(bob, carol), + ter(temBAD_SIGNER)); + env.close(); + } } void diff --git a/src/test/jtx/impl/batch.cpp b/src/test/jtx/impl/batch.cpp index 694127aa9ad..85e8f3e80d7 100644 --- a/src/test/jtx/impl/batch.cpp +++ b/src/test/jtx/impl/batch.cpp @@ -49,6 +49,8 @@ batch( jv[jss::Flags] = flags; jv[jss::Fee] = to_string(fee); jv[jss::SigningPubKey] = strHex(account.pk()); + jv[sfRawTransactions.jsonName] = Json::Value{Json::arrayValue}; + jv[sfTxIDs.jsonName] = Json::Value{Json::arrayValue}; return jv; } diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index f8639fa0754..fe50db0a7ab 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -29,10 +29,31 @@ namespace ripple { -TxConsequences -Batch::makeTxConsequences(PreflightContext const& ctx) +XRPAmount +Batch::calculateBaseFee(ReadView const& view, STTx const& tx) { - return TxConsequences{ctx.tx, TxConsequences::normal}; + // Calculate the Inner Txn Fees + XRPAmount extraFee{0}; + if (tx.isFieldPresent(sfRawTransactions)) + { + XRPAmount txFees{0}; + auto const& txns = tx.getFieldArray(sfRawTransactions); + for (STObject txn : txns) + { + STTx const stx = STTx{std::move(txn)}; + txFees += Transactor::calculateBaseFee(view, tx); + } + extraFee += txFees; + } + + // Calculate the BatchSigners Fees + if (tx.isFieldPresent(sfBatchSigners)) + { + auto const signers = tx.getFieldArray(sfBatchSigners); + extraFee += (signers.size() + 2) * view.fees().base; + } + + return extraFee; } NotTEC @@ -44,54 +65,115 @@ Batch::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; - auto& tx = ctx.tx; - - if (tx.getFlags() & tfBatchMask) + auto const flags = ctx.tx.getFlags(); + if (flags & tfBatchMask) { - JLOG(ctx.j.warn()) << "Batch: invalid flags."; + JLOG(ctx.j.trace()) << "Batch: invalid flags."; return temINVALID_FLAG; } - AccountID const outerAccount = tx.getAccountID(sfAccount); + if (std::popcount(flags & tfBatchSubTx) != 1) + { + JLOG(ctx.j.trace()) << "Batch: too many flags."; + return temMALFORMED; + } - auto const& txns = tx.getFieldArray(sfRawTransactions); - STVector256 const hashes = tx.getFieldV256(sfTxIDs); + AccountID const outerAccount = ctx.tx.getAccountID(sfAccount); + + auto const& txns = ctx.tx.getFieldArray(sfRawTransactions); + STVector256 const& hashes = ctx.tx.getFieldV256(sfTxIDs); if (hashes.size() != txns.size()) { - JLOG(ctx.j.warn()) << "Batch: Hashes array size does not match txns."; - return temINVALID; + JLOG(ctx.j.trace()) << "Batch: hashes array size does not match txns."; + return temMALFORMED; } if (txns.empty()) { - JLOG(ctx.j.warn()) << "Batch: txns array empty."; + JLOG(ctx.j.trace()) << "Batch: txns array empty."; return temARRAY_EMPTY; } if (txns.size() > 8) { - JLOG(ctx.j.warn()) << "Batch: txns array exceeds 12 entries."; + JLOG(ctx.j.trace()) << "Batch: txns array exceeds 8 entries."; return temARRAY_TOO_LARGE; } + auto const ret = preflight2(ctx); + if (!isTesSuccess(ret)) + return ret; + + std::set batchSignersSet; + if (ctx.tx.isFieldPresent(sfBatchSigners)) + { + STArray const signers = ctx.tx.getFieldArray(sfBatchSigners); + + // Check that the batch signers array is not too large. + if (signers.size() > 8) + { + JLOG(ctx.j.trace()) << "Batch: signers array exceeds 8 entries."; + return temARRAY_TOO_LARGE; + } + + // Add the batch signers to the set. + for (auto const& signer : signers) + { + AccountID const innerAccount = signer.getAccountID(sfAccount); + if (!batchSignersSet.insert(innerAccount).second) + { + JLOG(ctx.j.trace()) + << "Batch: Duplicate signer found: " << innerAccount; + return temINVALID_BATCH; + } + } + + // Check the batch signers signatures. + 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.trace()) << "Batch: invalid batch txn signature."; + return temBAD_SIGNATURE; + } + } + + std::set uniqueSigners; + std::set uniqueHashes; for (int i = 0; i < txns.size(); ++i) { + if (!uniqueHashes.insert(hashes[i]).second) + { + JLOG(ctx.j.trace()) << "Batch: duplicate TxID found."; + return temMALFORMED; + } + STTx const stx = STTx{STObject(txns[i])}; if (stx.getTransactionID() != hashes[i]) { - JLOG(ctx.j.warn()) << "Batch: Hashes array does not match txns."; - return temINVALID; + JLOG(ctx.j.trace()) << "Batch: txn hash does not match TxIDs hash." + << "index: " << i; + return temMALFORMED; } if (!stx.isFieldPresent(sfTransactionType)) { - JLOG(ctx.j.warn()) - << "Batch: TransactionType missing in array entry."; + JLOG(ctx.j.trace()) + << "Batch: TransactionType missing in inner txn." + << "index: " << i; return temINVALID_BATCH; } + if (stx.getFieldU16(sfTransactionType) == ttBATCH) { - JLOG(ctx.j.warn()) << "Batch: batch cannot have inner batch txn."; + JLOG(ctx.j.trace()) + << "Batch: batch cannot have an inner batch txn." + << "index: " << i; return temINVALID_BATCH; } @@ -99,59 +181,47 @@ Batch::preflight(PreflightContext const& ctx) if (stx.getFieldU16(sfTransactionType) == ttACCOUNT_DELETE && innerAccount == outerAccount) { - JLOG(ctx.j.warn()) + JLOG(ctx.j.trace()) << "Batch: inner txn cannot be account delete when inner and " - "outer accounts are the same."; + "outer accounts are the same." + << "index: " << i; return temINVALID_BATCH; } - if (innerAccount != outerAccount) - { - if (!tx.isFieldPresent(sfBatchSigners)) - { - JLOG(ctx.j.warn()) << "Batch: missing batch signers."; - return temBAD_SIGNER; - } + // If the inner account is the same as the outer account, continue. + // 1. We do not add it to the unique signers set. + // 2. We do not check a signature for the inner account exist. + if (innerAccount == outerAccount) + continue; - if (tx.getFieldArray(sfBatchSigners).end() == - std::find_if( - tx.getFieldArray(sfBatchSigners).begin(), - tx.getFieldArray(sfBatchSigners).end(), - [innerAccount](STObject const& signer) { - return signer.getAccountID(sfAccount) == innerAccount; - })) - { - JLOG(ctx.j.warn()) - << "Batch: inner txn not signed by the right user."; - return temBAD_SIGNER; - } + // Add the inner account to the unique signers set. + uniqueSigners.insert(innerAccount); + + // Validate that the account for this (inner) txn has a signature in the + // batch signers array. + if (ctx.tx.isFieldPresent(sfBatchSigners) && + batchSignersSet.find(innerAccount) == batchSignersSet.end()) + { + JLOG(ctx.j.trace()) << "Batch: no account signature for inner txn." + << "index: " << i; + return temBAD_SIGNER; } } - if (tx.isFieldPresent(sfBatchSigners)) + if (ctx.tx.isFieldPresent(sfBatchSigners) && + uniqueSigners.size() != ctx.tx.getFieldArray(sfBatchSigners).size()) { - 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; - } + JLOG(ctx.j.trace()) + << "Batch: unique signers does not match batch signers."; + return temBAD_SIGNER; } - return preflight2(ctx); + return tesSUCCESS; } TER Batch::preclaim(PreclaimContext const& ctx) { - if (!ctx.view.rules().enabled(featureBatch)) - return temDISABLED; - return tesSUCCESS; } @@ -201,7 +271,7 @@ Batch::doApply() // Atomic Revert on non tec failure if (!isTecClaim(ter)) { - JLOG(ctx_.journal.warn()) << "Batch: Inner txn failed." << ter; + JLOG(ctx_.journal.trace()) << "Batch: Inner txn failed." << ter; result = tecBATCH_FAILURE; changed = false; break; @@ -245,31 +315,4 @@ Batch::doApply() return result; } -XRPAmount -Batch::calculateBaseFee(ReadView const& view, STTx const& tx) -{ - // Calculate the Inner Txn Fees - XRPAmount extraFee{0}; - if (tx.isFieldPresent(sfRawTransactions)) - { - XRPAmount txFees{0}; - auto const& txns = tx.getFieldArray(sfRawTransactions); - for (STObject txn : txns) - { - STTx const stx = STTx{std::move(txn)}; - txFees += Transactor::calculateBaseFee(view, tx); - } - extraFee += txFees; - } - - // Calculate the BatchSigners Fees - if (tx.isFieldPresent(sfBatchSigners)) - { - auto const signers = tx.getFieldArray(sfBatchSigners); - extraFee += (signers.size() + 2) * view.fees().base; - } - - return extraFee; -} - } // namespace ripple \ No newline at end of file From a75cf0eb5f11ea368c5dd5c0c4461a937fa7a055 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 31 Oct 2024 01:28:08 +0100 Subject: [PATCH 60/71] [fold] clang-format --- src/libxrpl/protocol/STTx.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index d5f0ab9a504..1b0d28e34f7 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -192,8 +192,8 @@ STTx::getSeqProxy() const if (isFieldPresent(sfBatchTxn)) { STObject const& batchTxn = const_cast(*this) - .getField(sfBatchTxn) - .downcast(); + .getField(sfBatchTxn) + .downcast(); std::uint32_t const batchIndex{batchTxn.getFieldU8(sfBatchIndex)}; if (batchTxn.isFieldPresent(sfTicketSequence)) @@ -385,7 +385,7 @@ singleSignHelper( if (!validSig) return Unexpected("Invalid signature."); - + return {}; } @@ -393,7 +393,8 @@ Expected STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const { auto const data = getSigningData(*this); - return singleSignHelper(*this, makeSlice(data), requireCanonicalSig, getFlags()); + return singleSignHelper( + *this, makeSlice(data), requireCanonicalSig, getFlags()); } Expected @@ -403,7 +404,8 @@ STTx::checkBatchSingleSign( { Serializer msg; serializeBatch(msg, getFlags(), getFieldV256(sfTxIDs)); - return singleSignHelper(batchSigner, msg.slice(), requireCanonicalSig, getFlags()); + return singleSignHelper( + batchSigner, msg.slice(), requireCanonicalSig, getFlags()); } Expected @@ -467,7 +469,6 @@ multiSignHelper( return {}; } - Expected STTx::checkBatchMultiSign( STObject const& batchSigner, @@ -505,7 +506,9 @@ STTx::checkBatchMultiSign( signers, txnAccountID, fullyCanonical, - [&msg](AccountID const&) -> std::vector { return msg.getData(); }); + [&msg](AccountID const&) -> std::vector { + return msg.getData(); + }); } Expected @@ -548,7 +551,6 @@ STTx::checkMultiSign( }); } - //------------------------------------------------------------------------------ static bool From e6021bf61334e088c0f9aba4101979242d7ef56f Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 31 Oct 2024 01:41:00 +0100 Subject: [PATCH 61/71] [fold] tickets are not chronological --- src/libxrpl/protocol/STTx.cpp | 2 +- src/test/app/Batch_test.cpp | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 1b0d28e34f7..c11712cc527 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -200,7 +200,7 @@ STTx::getSeqProxy() const { std::uint32_t const ticketSeq{ batchTxn.getFieldU32(sfTicketSequence)}; - return SeqProxy{SeqProxy::ticket, ticketSeq + batchIndex}; + return SeqProxy{SeqProxy::ticket, ticketSeq}; } std::uint32_t const startSequence{batchTxn.getFieldU32(sfSequence)}; return SeqProxy::sequence(startSequence + batchIndex); diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index e61c3ef3382..314ab1e8531 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -1602,7 +1602,8 @@ class Batch_test : public beast::unit_test::suite 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)); + batch::add( + pay(alice, bob, XRP(1)), alice, 1, 0, aliceTicketSeq + 1)); env.close(); std::vector testCases = {{ @@ -1612,8 +1613,8 @@ class Batch_test : public beast::unit_test::suite "2"}, {"tesSUCCESS", "Payment", - "CBF12A852B0418FAF406C480BE991CE1EA2D0F16323412BFFA9F89CA7449B21" - "E"}, + "AF3029D2951DEFB2A6A644A15DB3689FFA5FEA31C6923067DC43E9C31DFA915" + "B"}, }}; Json::Value params; @@ -1662,7 +1663,8 @@ class Batch_test : public beast::unit_test::suite auto const seq = env.seq(alice); auto const batchFee = feeDrops * 2; env(batch::batch(alice, 0, batchFee, tfAllOrNothing), - batch::add(pay(alice, bob, XRP(1)), alice, 1, 0, aliceTicketSeq), + batch::add( + pay(alice, bob, XRP(1)), alice, 1, 0, aliceTicketSeq + 1), batch::add(pay(alice, bob, XRP(1)), alice, 0, seq), ticket::use(aliceTicketSeq)); env.close(); @@ -1670,8 +1672,8 @@ class Batch_test : public beast::unit_test::suite std::vector testCases = {{ {"tesSUCCESS", "Payment", - "CBF12A852B0418FAF406C480BE991CE1EA2D0F16323412BFFA9F89CA7449B21" - "E"}, + "AF3029D2951DEFB2A6A644A15DB3689FFA5FEA31C6923067DC43E9C31DFA915" + "B"}, {"tesSUCCESS", "Payment", "18629D496965A11CAB4454B86DB794BA07DABA3EE154DEFE9259977221E937E" From fd0acb7a16d2e6765e9ea11487f1c08d737c0685 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 31 Oct 2024 02:12:15 +0100 Subject: [PATCH 62/71] [fold] fix fee calculation --- src/test/app/Batch_test.cpp | 120 +++++++++++++++++++++--------- src/xrpld/app/tx/detail/Batch.cpp | 14 ++-- 2 files changed, 90 insertions(+), 44 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 314ab1e8531..02001c5b82a 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -193,7 +193,7 @@ class Batch_test : public beast::unit_test::suite auto const preBob = env.balance(bob); auto const seq = env.seq(alice); - auto const batchFee = feeDrops * 2; + auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 1; auto const txResult = withBatch ? ter(tesSUCCESS) : ter(temDISABLED); @@ -534,9 +534,9 @@ class Batch_test : public beast::unit_test::suite } void - testBadFee(FeatureBitset features) + testBadFeeNoSigner(FeatureBitset features) { - testcase("bad fee"); + testcase("bad fee no signer"); using namespace test::jtx; using namespace std::literals; @@ -583,6 +583,56 @@ class Batch_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD); } + void + testBadFeeSigner(FeatureBitset features) + { + testcase("bad fee signer"); + + using namespace test::jtx; + using namespace std::literals; + + test::jtx::Env env{*this, envconfig()}; + + auto const feeDrops = env.current()->fees().base; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + + env.fund(XRP(1000), alice, bob, gw); + env.close(); + env.trust(USD(1000), alice, bob); + env(pay(gw, alice, USD(100))); + env(pay(gw, bob, USD(100))); + env.close(); + + env(noop(bob), ter(tesSUCCESS)); + env.close(); + + auto const preAliceSeq = env.seq(alice); + auto const preAlice = env.balance(alice); + auto const preAliceUSD = env.balance(alice, USD.issue()); + auto const preBobSeq = env.seq(bob); + auto const preBob = env.balance(bob); + auto const preBobUSD = env.balance(bob, USD.issue()); + + auto const batchFee = ((0 + 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, 0, preBobSeq), + batch::sig(bob), + ter(telINSUF_FEE_P)); + env.close(); + + // Alice & Bob should not be affected. + BEAST_EXPECT(env.seq(alice) == preAliceSeq); + BEAST_EXPECT(env.balance(alice) == preAlice); + BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD); + BEAST_EXPECT(env.seq(bob) == preBobSeq); + BEAST_EXPECT(env.balance(bob) == preBob); + BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD); + } + void testOutOfSequence(FeatureBitset features) { @@ -602,7 +652,7 @@ class Batch_test : public beast::unit_test::suite // tfAllOrNothing { - auto const batchFee = feeDrops * 3; + auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 3; auto const seq = env.seq(alice); env(batch::batch(alice, seq, batchFee, tfAllOrNothing), batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), @@ -618,7 +668,7 @@ class Batch_test : public beast::unit_test::suite // tfUntilFailure { - auto const batchFee = feeDrops * 3; + auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 3; auto const seq = env.seq(alice); env(batch::batch(alice, seq, batchFee, tfUntilFailure), batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), @@ -634,7 +684,7 @@ class Batch_test : public beast::unit_test::suite // tfOnlyOne { - auto const batchFee = feeDrops * 2; + auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 3; auto const seq = env.seq(alice); env(batch::batch(alice, seq, batchFee, tfOnlyOne), batch::add( @@ -649,7 +699,7 @@ class Batch_test : public beast::unit_test::suite // tfIndependent { - auto const batchFee = feeDrops * 2; + auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 3; auto const seq = env.seq(alice); env(batch::batch(alice, seq, batchFee, tfIndependent), batch::add( @@ -685,7 +735,7 @@ 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 batchFee = ((0 + 2) * feeDrops) + feeDrops * 2; auto const seq = env.seq(alice); env(batch::batch(alice, seq, batchFee, tfAllOrNothing), batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), @@ -714,8 +764,7 @@ class Batch_test : public beast::unit_test::suite validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 7); - BEAST_EXPECT( - env.balance(alice) == preAlice - XRP(2) - (feeDrops * 2)); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - batchFee); BEAST_EXPECT(env.balance(bob) == preBob + XRP(2)); } @@ -733,7 +782,7 @@ 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 batchFee = ((0 + 2) * feeDrops) + feeDrops * 2; auto const seq = env.seq(alice); env(batch::batch(alice, seq, batchFee, tfAllOrNothing), batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), @@ -762,7 +811,7 @@ class Batch_test : public beast::unit_test::suite validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 5); - BEAST_EXPECT(env.balance(alice) == preAlice - (feeDrops * 2)); + BEAST_EXPECT(env.balance(alice) == preAlice - batchFee); BEAST_EXPECT(env.balance(bob) == preBob); } } @@ -787,7 +836,7 @@ 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 batchFee = ((0 + 2) * feeDrops) + feeDrops * 3; auto const seq = env.seq(alice); env(batch::batch(alice, seq, batchFee, tfOnlyOne), batch::add(pay(alice, bob, XRP(999)), alice, 1, seq), @@ -817,7 +866,7 @@ class Batch_test : public beast::unit_test::suite validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 7); - BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - (feeDrops * 3)); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - batchFee); BEAST_EXPECT(env.balance(bob) == preBob + XRP(1)); } @@ -841,7 +890,7 @@ 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 batchFee = ((0 + 2) * feeDrops) + feeDrops * 4; auto const seq = env.seq(alice); env(batch::batch(alice, seq, batchFee, tfUntilFailure), batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), @@ -876,7 +925,7 @@ class Batch_test : public beast::unit_test::suite validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 8); - BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - (feeDrops * 4)); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - batchFee); BEAST_EXPECT(env.balance(bob) == preBob + XRP(2)); } @@ -900,7 +949,7 @@ 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 batchFee = ((0 + 2) * feeDrops) + feeDrops * 4; auto const seq = env.seq(alice); env(batch::batch(alice, seq, batchFee, tfIndependent), batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), @@ -939,7 +988,7 @@ class Batch_test : public beast::unit_test::suite validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 9); - BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - (feeDrops * 4)); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee); BEAST_EXPECT(env.balance(bob) == preBob + XRP(3)); } @@ -996,7 +1045,7 @@ class Batch_test : public beast::unit_test::suite BEAST_EXPECT(env.seq(alice) == 6); BEAST_EXPECT(env.seq(bob) == 6); - BEAST_EXPECT(env.balance(alice) == preAlice - XRP(5) - (batchFee)); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(5) - batchFee); BEAST_EXPECT(env.balance(bob) == preBob + XRP(5)); } @@ -1024,7 +1073,7 @@ class Batch_test : public beast::unit_test::suite auto const preBob = env.balance(bob); auto const seq = env.seq(alice); - auto const batchFee = feeDrops * 2 + (feeDrops * 4); + auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 6; 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), @@ -1052,9 +1101,7 @@ class Batch_test : public beast::unit_test::suite validateBatchMeta(txn[jss::metaData], preAlice, seq); BEAST_EXPECT(env.seq(alice) == 8); - BEAST_EXPECT( - env.balance(alice) == - preAlice - XRP(2) - (feeDrops * 2 + (feeDrops * 4))); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - batchFee); BEAST_EXPECT(env.balance(bob) == preBob + XRP(2)); } @@ -1113,7 +1160,7 @@ class Batch_test : public beast::unit_test::suite BEAST_EXPECT(env.seq(alice) == 6); BEAST_EXPECT(env.seq(bob) == 6); - BEAST_EXPECT(env.balance(alice) == preAlice - XRP(5) - (batchFee)); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(5) - batchFee); BEAST_EXPECT(env.balance(bob) == preBob + XRP(5)); } @@ -1260,7 +1307,7 @@ class Batch_test : public beast::unit_test::suite tx1[sfDomain.fieldName] = strHex(domain); auto const seq = env.seq(alice); - auto const batchFee = feeDrops * 2; + auto const batchFee = ((0 + 2) * feeDrops) + 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)); @@ -1292,7 +1339,7 @@ class Batch_test : public beast::unit_test::suite sle->getFieldVL(sfDomain) == Blob(domain.begin(), domain.end())); BEAST_EXPECT(env.seq(alice) == 7); - BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - (feeDrops * 2)); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - batchFee); BEAST_EXPECT(env.balance(bob) == preBob + XRP(1)); } @@ -1363,7 +1410,7 @@ class Batch_test : public beast::unit_test::suite BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.seq(bob) == 6); - BEAST_EXPECT(env.balance(alice) == preAlice - (batchFee)); + BEAST_EXPECT(env.balance(alice) == preAlice - batchFee); BEAST_EXPECT(env.balance(bob) == preBob); BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD + USD(10)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD - USD(10)); @@ -1434,7 +1481,7 @@ class Batch_test : public beast::unit_test::suite BEAST_EXPECT(env.seq(alice) == 7); BEAST_EXPECT(env.seq(bob) == 16); - BEAST_EXPECT(env.balance(alice) == preAlice - (batchFee)); + BEAST_EXPECT(env.balance(alice) == preAlice - batchFee); BEAST_EXPECT(env.balance(bob) == preBob); BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD + USD(10)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD - USD(10)); @@ -1507,7 +1554,7 @@ class Batch_test : public beast::unit_test::suite BEAST_EXPECT(env.seq(carol) == 5); BEAST_EXPECT(env.balance(alice) == preAlice); BEAST_EXPECT(env.balance(bob) == preBob); - BEAST_EXPECT(env.balance(carol) == preCarol - (batchFee)); + BEAST_EXPECT(env.balance(carol) == preCarol - batchFee); BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD + USD(10)); BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD - USD(10)); } @@ -1537,7 +1584,7 @@ class Batch_test : public beast::unit_test::suite auto const preBob = env.balance(bob); auto const seq = env.seq(alice); - auto const batchFee = feeDrops * 2; + auto const batchFee = ((0 + 2) * feeDrops) + 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), @@ -1570,7 +1617,7 @@ class Batch_test : public beast::unit_test::suite BEAST_EXPECT(sle->getFieldU32(sfTicketCount) == 9); BEAST_EXPECT(env.seq(alice) == 17); - BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - (feeDrops * 2)); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - batchFee); BEAST_EXPECT(env.balance(bob) == preBob + XRP(2)); } @@ -1599,7 +1646,7 @@ class Batch_test : public beast::unit_test::suite auto const preBob = env.balance(bob); auto const seq = env.seq(alice); - auto const batchFee = feeDrops * 2; + auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 2; env(batch::batch(alice, seq, batchFee, tfAllOrNothing), batch::add(pay(alice, bob, XRP(1)), alice, 0, 0, aliceTicketSeq), batch::add( @@ -1632,7 +1679,7 @@ class Batch_test : public beast::unit_test::suite BEAST_EXPECT(sle->getFieldU32(sfTicketCount) == 8); BEAST_EXPECT(env.seq(alice) == 16); - BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - (feeDrops * 2)); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - batchFee); BEAST_EXPECT(env.balance(bob) == preBob + XRP(2)); } @@ -1661,7 +1708,7 @@ class Batch_test : public beast::unit_test::suite auto const preBob = env.balance(bob); auto const seq = env.seq(alice); - auto const batchFee = feeDrops * 2; + auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 2; env(batch::batch(alice, 0, batchFee, tfAllOrNothing), batch::add( pay(alice, bob, XRP(1)), alice, 1, 0, aliceTicketSeq + 1), @@ -1695,7 +1742,7 @@ class Batch_test : public beast::unit_test::suite BEAST_EXPECT(sle->getFieldU32(sfTicketCount) == 8); BEAST_EXPECT(env.seq(alice) == 16); - BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - (feeDrops * 2)); + BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - batchFee); BEAST_EXPECT(env.balance(bob) == preBob + XRP(2)); } @@ -1705,7 +1752,8 @@ class Batch_test : public beast::unit_test::suite testEnable(features); testPreflight(features); testBadSequence(features); - testBadFee(features); + testBadFeeNoSigner(features); + testBadFeeSigner(features); testOutOfSequence(features); testAllOrNothing(features); testOnlyOne(features); diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index fe50db0a7ab..17c9a1d003e 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -33,7 +33,7 @@ XRPAmount Batch::calculateBaseFee(ReadView const& view, STTx const& tx) { // Calculate the Inner Txn Fees - XRPAmount extraFee{0}; + XRPAmount txnFees{0}; if (tx.isFieldPresent(sfRawTransactions)) { XRPAmount txFees{0}; @@ -43,17 +43,15 @@ Batch::calculateBaseFee(ReadView const& view, STTx const& tx) STTx const stx = STTx{std::move(txn)}; txFees += Transactor::calculateBaseFee(view, tx); } - extraFee += txFees; + txnFees += txFees; } // Calculate the BatchSigners Fees - if (tx.isFieldPresent(sfBatchSigners)) - { - auto const signers = tx.getFieldArray(sfBatchSigners); - extraFee += (signers.size() + 2) * view.fees().base; - } + std::int32_t signerCount = tx.isFieldPresent(sfBatchSigners) + ? tx.getFieldArray(sfBatchSigners).size() + : 0; - return extraFee; + return ((signerCount + 2) * view.fees().base) + txnFees; } NotTEC From dd77e03d4f32447f9e71c603e9bf76a720cc3342 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 31 Oct 2024 02:50:20 +0100 Subject: [PATCH 63/71] [fold] fix merge issues --- include/xrpl/protocol/Feature.h | 2 +- include/xrpl/protocol/detail/features.macro | 1 + include/xrpl/protocol/detail/sfields.macro | 11 +++++++++++ include/xrpl/protocol/detail/transactions.macro | 7 +++++++ src/xrpld/app/tx/detail/Batch.h | 2 ++ 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/include/xrpl/protocol/Feature.h b/include/xrpl/protocol/Feature.h index eb975f39ae0..a2510c63000 100644 --- a/include/xrpl/protocol/Feature.h +++ b/include/xrpl/protocol/Feature.h @@ -80,7 +80,7 @@ namespace detail { // Feature.cpp. Because it's only used to reserve storage, and determine how // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // the actual number of amendments. A LogicError on startup will verify this. -static constexpr std::size_t numFeatures = 80; +static constexpr std::size_t numFeatures = 81; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 3a8d77e2bab..9d8dacffe6b 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -31,6 +31,7 @@ // InvariantsV1_1 will be changes to Supported::yes when all the // invariants expected to be included under it are complete. +XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(MPTokensV1, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(InvariantsV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (NFTokenPageLinks, Supported::yes, VoteBehavior::DefaultNo) diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index e3a93fc7f46..89848b957b1 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -42,6 +42,7 @@ TYPED_SFIELD(sfTickSize, UINT8, 16) TYPED_SFIELD(sfUNLModifyDisabling, UINT8, 17) TYPED_SFIELD(sfHookResult, UINT8, 18) TYPED_SFIELD(sfWasLockingChainSend, UINT8, 19) +TYPED_SFIELD(sfBatchIndex, UINT8, 20) // 16-bit integers (common) TYPED_SFIELD(sfLedgerEntryType, UINT16, 1, SField::sMD_Never) @@ -258,6 +259,7 @@ TYPED_SFIELD(sfData, VL, 27) TYPED_SFIELD(sfAssetClass, VL, 28) TYPED_SFIELD(sfProvider, VL, 29) TYPED_SFIELD(sfMPTokenMetadata, VL, 30) +TYPED_SFIELD(sfInnerResult, VL, 31) // account (common) TYPED_SFIELD(sfAccount, ACCOUNT, 1) @@ -280,12 +282,14 @@ TYPED_SFIELD(sfAttestationSignerAccount, ACCOUNT, 20) TYPED_SFIELD(sfAttestationRewardAccount, ACCOUNT, 21) TYPED_SFIELD(sfLockingChainDoor, ACCOUNT, 22) TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23) +TYPED_SFIELD(sfOuterAccount, ACCOUNT, 24) // vector of 256-bit TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::sMD_Never) TYPED_SFIELD(sfHashes, VECTOR256, 2) TYPED_SFIELD(sfAmendments, VECTOR256, 3) TYPED_SFIELD(sfNFTokenOffers, VECTOR256, 4) +TYPED_SFIELD(sfTxIDs, VECTOR256, 5) // path set UNTYPED_SFIELD(sfPaths, PATHSET, 1) @@ -337,6 +341,10 @@ UNTYPED_SFIELD(sfXChainCreateAccountProofSig, OBJECT, 29) UNTYPED_SFIELD(sfXChainClaimAttestationCollectionElement, OBJECT, 30) UNTYPED_SFIELD(sfXChainCreateAccountAttestationCollectionElement, OBJECT, 31) UNTYPED_SFIELD(sfPriceData, OBJECT, 32) +UNTYPED_SFIELD(sfRawTransaction, OBJECT, 33) +UNTYPED_SFIELD(sfBatchExecution, OBJECT, 34) +UNTYPED_SFIELD(sfBatchTxn, OBJECT, 35) +UNTYPED_SFIELD(sfBatchSigner, OBJECT, 36) // array of objects (common) // ARRAY/1 is reserved for end of array @@ -364,3 +372,6 @@ UNTYPED_SFIELD(sfXChainCreateAccountAttestations, ARRAY, 22) // 23 unused UNTYPED_SFIELD(sfPriceDataSeries, ARRAY, 24) UNTYPED_SFIELD(sfAuthAccounts, ARRAY, 25) +UNTYPED_SFIELD(sfBatchExecutions, ARRAY, 26) +UNTYPED_SFIELD(sfRawTransactions, ARRAY, 27) +UNTYPED_SFIELD(sfBatchSigners, ARRAY, 28) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 30e27da4167..6aaad79ed40 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -412,6 +412,13 @@ TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, ({ {sfHolder, soeOPTIONAL}, })) +/** This transaction type wraps inner transactions for batch. */ +TRANSACTION(ttBATCH, 58, Batch, ({ + {sfRawTransactions, soeREQUIRED}, + {sfTxIDs, soeREQUIRED}, + {sfBatchSigners, soeOPTIONAL}, +})) + /** This system-generated transaction type is used to update the status of the various amendments. For details, see: https://xrpl.org/amendments.html diff --git a/src/xrpld/app/tx/detail/Batch.h b/src/xrpld/app/tx/detail/Batch.h index 14f12ca01fb..e232d2f9562 100644 --- a/src/xrpld/app/tx/detail/Batch.h +++ b/src/xrpld/app/tx/detail/Batch.h @@ -49,6 +49,8 @@ class Batch : public Transactor doApply() override; }; +using Batch = Batch; + } // namespace ripple #endif \ No newline at end of file From 6cb23e38af32dbc8c3c01c9dc441df74804a8c5e Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 31 Oct 2024 02:52:40 +0100 Subject: [PATCH 64/71] [fold] clang-format --- src/test/jtx/batch.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/jtx/batch.h b/src/test/jtx/batch.h index dafebf9ec33..3703fdb2d21 100644 --- a/src/test/jtx/batch.h +++ b/src/test/jtx/batch.h @@ -112,9 +112,8 @@ class sig sig(std::vector signers_); template - requires std::convertible_to explicit sig( - AccountType&& a0, - Accounts&&... aN) + requires std::convertible_to + explicit sig(AccountType&& a0, Accounts&&... aN) : sig{std::vector{ std::forward(a0), std::forward(aN)...}} @@ -166,7 +165,8 @@ class msig msig(Account const& masterAccount, std::vector signers_); template - requires std::convertible_to explicit msig( + requires std::convertible_to + explicit msig( Account const& masterAccount, AccountType&& a0, Accounts&&... aN) From 88c40e4c60ddb14dfa8e3121e51ca340ca985088 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Wed, 6 Nov 2024 17:43:21 +0100 Subject: [PATCH 65/71] [fold] addressing review - Update Comments - Update/Change function names - Update/Change argument names - Remove unused preclaim - Fix exit out of signature check (multi & single) --- src/xrpld/app/misc/NetworkOPs.cpp | 14 +++++++------- src/xrpld/app/tx/detail/ApplyContext.cpp | 14 +++++++------- src/xrpld/app/tx/detail/ApplyContext.h | 2 +- src/xrpld/app/tx/detail/Batch.cpp | 19 ++++++------------- src/xrpld/app/tx/detail/Batch.h | 3 --- src/xrpld/app/tx/detail/Transactor.cpp | 18 +++++++++++------- src/xrpld/ledger/ApplyViewImpl.h | 6 +++--- src/xrpld/ledger/detail/ApplyViewImpl.cpp | 3 ++- 8 files changed, 37 insertions(+), 42 deletions(-) diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 210d4433dbb..3a0b54aabef 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -1137,8 +1137,8 @@ NetworkOPsImp::submitTransaction(std::shared_ptr const& iTrans) } // Enforce Network bar for batch txn - auto const view = m_ledgerMaster.getCurrentLedger(); - if (view->rules().enabled(featureBatch) && + if (auto const view = m_ledgerMaster.getCurrentLedger(); + view->rules().enabled(featureBatch) && iTrans->isFieldPresent(sfBatchTxn)) { JLOG(m_journal.error()) @@ -1216,8 +1216,8 @@ NetworkOPsImp::processTransaction( auto const view = m_ledgerMaster.getCurrentLedger(); // This function is called by several different parts of the codebase - // under no circumstances will we ever accept a batch txn from the - // network. + // under no circumstances will we ever accept an inner txn within a batch + // txn from the network. auto const tx = *transaction->getSTransaction(); if (view->rules().enabled(featureBatch) && tx.isFieldPresent(ripple::sfBatchTxn)) @@ -1486,8 +1486,8 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) auto const toSkip = app_.getHashRouter().shouldRelay(e.transaction->getID()); - auto const txn = *(e.transaction->getSTransaction()); - if (toSkip && !txn.isFieldPresent(sfBatchTxn)) + if (auto const txn = *(e.transaction->getSTransaction()); + toSkip && !txn.isFieldPresent(sfBatchTxn)) { protocol::TMTransaction tx; Serializer s; @@ -2778,7 +2778,7 @@ NetworkOPsImp::pubProposedTransaction( std::shared_ptr const& transaction, TER result) { - // never publish batch txns + // never publish an inner txn inside a batch txn if (transaction->isFieldPresent(ripple::sfBatchTxn)) return; diff --git a/src/xrpld/app/tx/detail/ApplyContext.cpp b/src/xrpld/app/tx/detail/ApplyContext.cpp index c2188e7d529..e2d20368a99 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.cpp +++ b/src/xrpld/app/tx/detail/ApplyContext.cpp @@ -104,25 +104,25 @@ ApplyContext::updateAccountRootEntry() * will be added. */ void -ApplyContext::batchPrevious(ApplyViewImpl& avi) +ApplyContext::setBatchPrevAcctRootFields(ApplyViewImpl& avi) { AccountID const account = tx.getAccountID(sfAccount); - auto const sleBase = base_.read(keylet::account(account)); - auto const sle = view_->peek(keylet::account(account)); - if (sle && sleBase) + auto const sleBaseAcct = base_.read(keylet::account(account)); + auto const sleAcct = view_->peek(keylet::account(account)); + if (sleAcct && sleBaseAcct) { STObject prevFields{sfPreviousFields}; - for (auto const& obj : *sleBase) + for (auto const& obj : *sleBaseAcct) { if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) && - (!sle->hasMatchingEntry(obj) || obj.getFName() == sfSequence || + (!sleAcct->hasMatchingEntry(obj) || obj.getFName() == sfSequence || obj.getFName() == sfOwnerCount || obj.getFName() == sfTicketCount)) { prevFields.emplace_back(obj); } } - avi.addBatchPrevMetaData(std::move(prevFields)); + avi.setBatchPrevMetaData(std::move(prevFields)); } } diff --git a/src/xrpld/app/tx/detail/ApplyContext.h b/src/xrpld/app/tx/detail/ApplyContext.h index e983780879d..8c1ba205e06 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.h +++ b/src/xrpld/app/tx/detail/ApplyContext.h @@ -93,7 +93,7 @@ class ApplyContext /** Sets the batch prev fields in the metadata. */ void - batchPrevious(ApplyViewImpl& avi); + setBatchPrevAcctRootFields(ApplyViewImpl& avi); /** Get the number of unapplied changes. */ std::size_t diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 17c9a1d003e..dbb86aca35f 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -217,16 +217,9 @@ Batch::preflight(PreflightContext const& ctx) return tesSUCCESS; } -TER -Batch::preclaim(PreclaimContext const& ctx) -{ - return tesSUCCESS; -} - TER Batch::doApply() { - Sandbox sb(&ctx_.view()); bool changed = false; auto const flags = ctx_.tx.getFlags(); @@ -234,10 +227,10 @@ Batch::doApply() TER result = tesSUCCESS; ApplyViewImpl& avi = dynamic_cast(ctx_.view()); - OpenView subView(&sb); + OpenView subView(&ctx_.view()); auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions); - bool const not3rdParty = std::any_of( + bool const innerTxnSubmittedByOuterAcct = std::any_of( txns.begin(), txns.end(), [outerAccount](STObject const& txn) { return txn.getAccountID(sfAccount) == outerAccount; }); @@ -302,14 +295,14 @@ Batch::doApply() // Apply SubView & PreviousFields if (changed) { - // Only required when not 3rd Party - if (not3rdParty) - ctx_.batchPrevious(avi); + // Only required when the outer account also submitted at least one of + // the inner transactions + if (innerTxnSubmittedByOuterAcct) + ctx_.setBatchPrevAcctRootFields(avi); ctx_.applyOpenView(subView); } - sb.apply(ctx_.rawView()); return result; } diff --git a/src/xrpld/app/tx/detail/Batch.h b/src/xrpld/app/tx/detail/Batch.h index e232d2f9562..dcf3bc2875e 100644 --- a/src/xrpld/app/tx/detail/Batch.h +++ b/src/xrpld/app/tx/detail/Batch.h @@ -42,9 +42,6 @@ class Batch : public Transactor static NotTEC preflight(PreflightContext const& ctx); - static TER - preclaim(PreclaimContext const& ctx); - TER doApply() override; }; diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 47b17875998..ddab4eb4358 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -546,12 +546,14 @@ Transactor::checkBatchSign(PreclaimContext const& ctx) if (pkSigner.empty()) { STArray const& txSigners(signer.getFieldArray(sfSigners)); - ret = checkMultiSign(ctx.view, idAccount, txSigners, ctx.j); + if (ret = checkMultiSign(ctx.view, idAccount, txSigners, ctx.j); + !isTesSuccess(ret)) + return ret; } else { if (!publicKeyType(makeSlice(pkSigner))) - ret = tefBAD_AUTH; + return tefBAD_AUTH; auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner))); auto const sleAccount = ctx.view.read(keylet::account(idAccount)); @@ -561,8 +563,10 @@ Transactor::checkBatchSign(PreclaimContext const& ctx) if (!sleAccount) return tesSUCCESS; - ret = checkSingleSign( - idSigner, idAccount, sleAccount, ctx.view.rules(), ctx.j); + if (ret = checkSingleSign( + idSigner, idAccount, sleAccount, ctx.view.rules(), ctx.j); + !isTesSuccess(ret)) + return ret; } } return ret; @@ -1049,19 +1053,19 @@ Transactor::operator()() applied = isTecClaim(result); } - // Update the AccountRoot entry if the batch transaction was successfull + // Update the AccountRoot entry if the batch transaction was successful if (applied && ctx_.tx.getTxnType() == ttBATCH && result == tesSUCCESS) { auto const outerAccount = ctx_.tx.getAccountID(sfAccount); auto const& txns = ctx_.tx.getFieldArray(sfRawTransactions); - bool const not3rdParty = std::any_of( + bool const innerTxnSubmittedByOuterAcct = std::any_of( txns.begin(), txns.end(), [outerAccount](STObject const& txn) { return txn.getAccountID(sfAccount) == outerAccount; }); // Only update the account root entry if the batch transaction was // not a 3rd party transaction - if (not3rdParty) + if (innerTxnSubmittedByOuterAcct) ctx_.updateAccountRootEntry(); } diff --git a/src/xrpld/ledger/ApplyViewImpl.h b/src/xrpld/ledger/ApplyViewImpl.h index aeebd026c04..cc2d363e1be 100644 --- a/src/xrpld/ledger/ApplyViewImpl.h +++ b/src/xrpld/ledger/ApplyViewImpl.h @@ -78,9 +78,9 @@ class ApplyViewImpl final : public detail::ApplyViewBase * Takes ownership / use std::move */ void - addBatchPrevMetaData(STObject const& prevFields) + setBatchPrevMetaData(STObject const& prevFields) { - batchPrev_ = prevFields; + batchPrevAcctRootFields_ = prevFields; } void @@ -123,7 +123,7 @@ class ApplyViewImpl final : public detail::ApplyViewBase private: std::optional deliver_; std::vector batchExecution_; - std::optional batchPrev_; + std::optional batchPrevAcctRootFields_; }; } // namespace ripple diff --git a/src/xrpld/ledger/detail/ApplyViewImpl.cpp b/src/xrpld/ledger/detail/ApplyViewImpl.cpp index dd3b4c85d62..b0583b98f40 100644 --- a/src/xrpld/ledger/detail/ApplyViewImpl.cpp +++ b/src/xrpld/ledger/detail/ApplyViewImpl.cpp @@ -31,7 +31,8 @@ ApplyViewImpl::ApplyViewImpl(ReadView const* base, ApplyFlags flags) void ApplyViewImpl::apply(OpenView& to, STTx const& tx, TER ter, beast::Journal j) { - items_.apply(to, tx, ter, deliver_, batchExecution_, batchPrev_, j); + items_.apply( + to, tx, ter, deliver_, batchExecution_, batchPrevAcctRootFields_, j); } std::size_t From e4cb7841770c9c0e0f183166841cc3562edd5c86 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 7 Nov 2024 01:52:40 +0100 Subject: [PATCH 66/71] [fold] change sequence handling - Remove BatchTxn Object - Use Global Flag `tfInnerBatchTxn ` for inner batch txns - Remove Sequence & Ticket workaround - replace sfBatchTxn submit/relay validation with tfInnerBatchTxn - Remove Sequence 0 Requirement --- include/xrpl/protocol/TxFlags.h | 42 +- include/xrpl/protocol/detail/sfields.macro | 5 +- include/xrpl/protocol/jss.h | 1 - src/libxrpl/protocol/InnerObjectFormats.cpp | 7 - src/libxrpl/protocol/STTx.cpp | 17 - src/libxrpl/protocol/TxFormats.cpp | 1 - src/test/app/Batch_test.cpp | 457 +++++++------------- src/test/jtx/batch.h | 6 - src/test/jtx/impl/batch.cpp | 17 +- src/xrpld/app/ledger/detail/OpenLedger.cpp | 3 +- src/xrpld/app/misc/NetworkOPs.cpp | 9 +- src/xrpld/app/tx/detail/Transactor.cpp | 17 +- src/xrpld/app/tx/detail/apply.cpp | 3 +- src/xrpld/overlay/detail/PeerImp.cpp | 11 +- 14 files changed, 209 insertions(+), 387 deletions(-) diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index c9ac9d4ef72..fe17d12fab4 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -58,8 +58,10 @@ namespace ripple { // clang-format off // Universal Transaction flags: constexpr std::uint32_t tfFullyCanonicalSig = 0x80000000; +constexpr std::uint32_t tfInnerBatchTxn = 0x40000000; constexpr std::uint32_t tfUniversal = tfFullyCanonicalSig; -constexpr std::uint32_t tfUniversalMask = ~tfUniversal; +constexpr std::uint32_t tfUniversalV2 = tfFullyCanonicalSig | tfInnerBatchTxn; +constexpr std::uint32_t tfUniversalMask = ~(tfFullyCanonicalSig | tfInnerBatchTxn); // AccountSet flags: constexpr std::uint32_t tfRequireDestTag = 0x00010000; @@ -69,7 +71,7 @@ constexpr std::uint32_t tfOptionalAuth = 0x00080000; constexpr std::uint32_t tfDisallowXRP = 0x00100000; constexpr std::uint32_t tfAllowXRP = 0x00200000; constexpr std::uint32_t tfAccountSetMask = - ~(tfUniversal | tfRequireDestTag | tfOptionalDestTag | tfRequireAuth | + ~(tfUniversalV2 | tfRequireDestTag | tfOptionalDestTag | tfRequireAuth | tfOptionalAuth | tfDisallowXRP | tfAllowXRP); // AccountSet SetFlag/ClearFlag values @@ -98,15 +100,15 @@ constexpr std::uint32_t tfImmediateOrCancel = 0x00020000; constexpr std::uint32_t tfFillOrKill = 0x00040000; constexpr std::uint32_t tfSell = 0x00080000; constexpr std::uint32_t tfOfferCreateMask = - ~(tfUniversal | tfPassive | tfImmediateOrCancel | tfFillOrKill | tfSell); + ~(tfUniversalV2 | tfPassive | tfImmediateOrCancel | tfFillOrKill | tfSell); // Payment flags: constexpr std::uint32_t tfNoRippleDirect = 0x00010000; constexpr std::uint32_t tfPartialPayment = 0x00020000; constexpr std::uint32_t tfLimitQuality = 0x00040000; constexpr std::uint32_t tfPaymentMask = - ~(tfUniversal | tfPartialPayment | tfLimitQuality | tfNoRippleDirect); -constexpr std::uint32_t tfMPTPaymentMask = ~(tfUniversal | tfPartialPayment); + ~(tfUniversalV2 | tfPartialPayment | tfLimitQuality | tfNoRippleDirect); +constexpr std::uint32_t tfMPTPaymentMask = ~(tfUniversalV2 | tfPartialPayment); // TrustSet flags: constexpr std::uint32_t tfSetfAuth = 0x00010000; @@ -115,7 +117,7 @@ constexpr std::uint32_t tfClearNoRipple = 0x00040000; constexpr std::uint32_t tfSetFreeze = 0x00100000; constexpr std::uint32_t tfClearFreeze = 0x00200000; constexpr std::uint32_t tfTrustSetMask = - ~(tfUniversal | tfSetfAuth | tfSetNoRipple | tfClearNoRipple | tfSetFreeze | + ~(tfUniversalV2 | tfSetfAuth | tfSetNoRipple | tfClearNoRipple | tfSetFreeze | tfClearFreeze); // EnableAmendment flags: @@ -125,7 +127,7 @@ constexpr std::uint32_t tfLostMajority = 0x00020000; // PaymentChannelClaim flags: constexpr std::uint32_t tfRenew = 0x00010000; constexpr std::uint32_t tfClose = 0x00020000; -constexpr std::uint32_t tfPayChanClaimMask = ~(tfUniversal | tfRenew | tfClose); +constexpr std::uint32_t tfPayChanClaimMask = ~(tfUniversalV2 | tfRenew | tfClose); // NFTokenMint flags: constexpr std::uint32_t const tfBurnable = 0x00000001; @@ -142,16 +144,16 @@ constexpr std::uint32_t const tfMPTCanTrade = lsfMPTCanTrade; constexpr std::uint32_t const tfMPTCanTransfer = lsfMPTCanTransfer; constexpr std::uint32_t const tfMPTCanClawback = lsfMPTCanClawback; constexpr std::uint32_t const tfMPTokenIssuanceCreateMask = - ~(tfUniversal | tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback); + ~(tfUniversalV2 | tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback); // MPTokenAuthorize flags: constexpr std::uint32_t const tfMPTUnauthorize = 0x00000001; -constexpr std::uint32_t const tfMPTokenAuthorizeMask = ~(tfUniversal | tfMPTUnauthorize); +constexpr std::uint32_t const tfMPTokenAuthorizeMask = ~(tfUniversalV2 | tfMPTUnauthorize); // MPTokenIssuanceSet flags: constexpr std::uint32_t const tfMPTLock = 0x00000001; constexpr std::uint32_t const tfMPTUnlock = 0x00000002; -constexpr std::uint32_t const tfMPTokenIssuanceSetMask = ~(tfUniversal | tfMPTLock | tfMPTUnlock); +constexpr std::uint32_t const tfMPTokenIssuanceSetMask = ~(tfUniversalV2 | tfMPTLock | tfMPTUnlock); // MPTokenIssuanceDestroy flags: constexpr std::uint32_t const tfMPTokenIssuanceDestroyMask = ~tfUniversal; @@ -170,24 +172,24 @@ constexpr std::uint32_t const tfMPTokenIssuanceDestroyMask = ~tfUniversal; // tfTrustLine flag as a way to prevent the attack. But until the // amendment passes we still need to keep the old behavior available. constexpr std::uint32_t const tfNFTokenMintOldMask = - ~(tfUniversal | tfBurnable | tfOnlyXRP | tfTrustLine | tfTransferable); + ~(tfUniversalV2 | tfBurnable | tfOnlyXRP | tfTrustLine | tfTransferable); constexpr std::uint32_t const tfNFTokenMintMask = - ~(tfUniversal | tfBurnable | tfOnlyXRP | tfTransferable); + ~(tfUniversalV2 | tfBurnable | tfOnlyXRP | tfTransferable); // NFTokenCreateOffer flags: constexpr std::uint32_t const tfSellNFToken = 0x00000001; constexpr std::uint32_t const tfNFTokenCreateOfferMask = - ~(tfUniversal | tfSellNFToken); + ~(tfUniversalV2 | tfSellNFToken); // NFTokenCancelOffer flags: -constexpr std::uint32_t const tfNFTokenCancelOfferMask = ~(tfUniversal); +constexpr std::uint32_t const tfNFTokenCancelOfferMask = ~tfUniversalV2; // NFTokenAcceptOffer flags: -constexpr std::uint32_t const tfNFTokenAcceptOfferMask = ~tfUniversal; +constexpr std::uint32_t const tfNFTokenAcceptOfferMask = ~tfUniversalV2; // Clawback flags: -constexpr std::uint32_t const tfClawbackMask = ~tfUniversal; +constexpr std::uint32_t const tfClawbackMask = ~tfUniversalV2; // AMM Flags: constexpr std::uint32_t tfLPToken = 0x00010000; @@ -204,12 +206,12 @@ constexpr std::uint32_t tfWithdrawSubTx = constexpr std::uint32_t tfDepositSubTx = tfLPToken | tfSingleAsset | tfTwoAsset | tfOneAssetLPToken | tfLimitLPToken | tfTwoAssetIfEmpty; -constexpr std::uint32_t tfWithdrawMask = ~(tfUniversal | tfWithdrawSubTx); -constexpr std::uint32_t tfDepositMask = ~(tfUniversal | tfDepositSubTx); +constexpr std::uint32_t tfWithdrawMask = ~(tfUniversalV2 | tfWithdrawSubTx); +constexpr std::uint32_t tfDepositMask = ~(tfUniversalV2 | tfDepositSubTx); // BridgeModify flags: constexpr std::uint32_t tfClearAccountCreateAmount = 0x00010000; -constexpr std::uint32_t tfBridgeModifyMask = ~(tfUniversal | tfClearAccountCreateAmount); +constexpr std::uint32_t tfBridgeModifyMask = ~(tfUniversalV2 | tfClearAccountCreateAmount); // Batch Flags constexpr std::uint32_t tfAllOrNothing = 0x00010000; @@ -217,7 +219,7 @@ constexpr std::uint32_t tfOnlyOne = 0x00020000; constexpr std::uint32_t tfUntilFailure = 0x00040000; constexpr std::uint32_t tfIndependent = 0x00080000; constexpr std::uint32_t const tfBatchMask = - ~(tfUniversal | tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent); + ~(tfUniversalV2 | tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent); constexpr std::uint32_t const tfBatchSubTx = tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent; diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 89848b957b1..d7b4d03acfb 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -42,7 +42,6 @@ TYPED_SFIELD(sfTickSize, UINT8, 16) TYPED_SFIELD(sfUNLModifyDisabling, UINT8, 17) TYPED_SFIELD(sfHookResult, UINT8, 18) TYPED_SFIELD(sfWasLockingChainSend, UINT8, 19) -TYPED_SFIELD(sfBatchIndex, UINT8, 20) // 16-bit integers (common) TYPED_SFIELD(sfLedgerEntryType, UINT16, 1, SField::sMD_Never) @@ -282,7 +281,6 @@ TYPED_SFIELD(sfAttestationSignerAccount, ACCOUNT, 20) TYPED_SFIELD(sfAttestationRewardAccount, ACCOUNT, 21) TYPED_SFIELD(sfLockingChainDoor, ACCOUNT, 22) TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23) -TYPED_SFIELD(sfOuterAccount, ACCOUNT, 24) // vector of 256-bit TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::sMD_Never) @@ -343,8 +341,7 @@ UNTYPED_SFIELD(sfXChainCreateAccountAttestationCollectionElement, OBJECT, 31) UNTYPED_SFIELD(sfPriceData, OBJECT, 32) UNTYPED_SFIELD(sfRawTransaction, OBJECT, 33) UNTYPED_SFIELD(sfBatchExecution, OBJECT, 34) -UNTYPED_SFIELD(sfBatchTxn, OBJECT, 35) -UNTYPED_SFIELD(sfBatchSigner, OBJECT, 36) +UNTYPED_SFIELD(sfBatchSigner, OBJECT, 35) // array of objects (common) // ARRAY/1 is reserved for end of array diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index 0b87e92e6dc..1448941f661 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -90,7 +90,6 @@ JSS(Offer); // ledger type. JSS(OfferSequence); // field. JSS(Oracle); // ledger type. JSS(OracleDocumentID); // field -JSS(OuterAccount); // field JSS(Owner); // field JSS(Paths); // in/out: TransactionSign JSS(PayChannel); // ledger type. diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 54fd88feb41..eb2e2c6e6b0 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -154,13 +154,6 @@ InnerObjectFormats::InnerObjectFormats() {sfInnerResult, soeREQUIRED}, {sfTransactionHash, soeOPTIONAL}}); - add(sfBatchTxn.jsonName.c_str(), - sfBatchTxn.getCode(), - {{sfOuterAccount, soeREQUIRED}, - {sfSequence, soeOPTIONAL}, - {sfTicketSequence, soeOPTIONAL}, - {sfBatchIndex, soeREQUIRED}}); - add(sfBatchSigner.jsonName.c_str(), sfBatchSigner.getCode(), {{sfAccount, soeREQUIRED}, diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index fddd29d752b..3340ea18db2 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -189,23 +189,6 @@ STTx::getSeqProxy() const if (seq != 0) return SeqProxy::sequence(seq); - if (isFieldPresent(sfBatchTxn)) - { - STObject const& batchTxn = const_cast(*this) - .getField(sfBatchTxn) - .downcast(); - - std::uint32_t const batchIndex{batchTxn.getFieldU8(sfBatchIndex)}; - if (batchTxn.isFieldPresent(sfTicketSequence)) - { - std::uint32_t const ticketSeq{ - batchTxn.getFieldU32(sfTicketSequence)}; - return SeqProxy{SeqProxy::ticket, ticketSeq}; - } - std::uint32_t const startSequence{batchTxn.getFieldU32(sfSequence)}; - return SeqProxy::sequence(startSequence + batchIndex); - } - std::optional const ticketSeq{operator[](~sfTicketSequence)}; if (!ticketSeq) // No TicketSequence specified. Return the Sequence, whatever it is. diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index e1a99510609..76b1ae8ad4f 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -45,7 +45,6 @@ TxFormats::TxFormats() {sfTxnSignature, soeOPTIONAL}, {sfSigners, soeOPTIONAL}, // submit_multisigned {sfNetworkID, soeOPTIONAL}, - {sfBatchTxn, soeOPTIONAL}, }; #pragma push_macro("UNWRAP") diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 02001c5b82a..bb6c3c1da04 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -35,7 +35,6 @@ class Batch_test : public beast::unit_test::suite { std::string result; std::string txType; - std::string hash; }; struct TestSignData @@ -71,9 +70,6 @@ class Batch_test : public beast::unit_test::suite strHex(batchResults[index].result)); BEAST_EXPECT( b[sfTransactionType.jsonName] == batchResults[index].txType); - if (batchResults[index].hash != "") - BEAST_EXPECT( - b[sfTransactionHash.jsonName] == batchResults[index].hash); ++index; } } @@ -117,8 +113,6 @@ class Batch_test : public beast::unit_test::suite addBatchTx( Json::Value jv, Json::Value const& tx, - jtx::Account const& outerAccount, - std::uint8_t batchIndex, std::uint32_t sequence, std::optional ticket = std::nullopt) { @@ -130,20 +124,14 @@ class Batch_test : public beast::unit_test::suite batchTransaction[jss::RawTransaction] = tx; 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] = outerAccount.human(); - batchTxn[sfBatchIndex.jsonName] = batchIndex; - batchTxn[sfSequence.jsonName] = sequence; + batchTransaction[jss::RawTransaction][jss::Sequence] = sequence; // Optionally set ticket sequence if (ticket.has_value()) { - batchTxn[sfTicketSequence.jsonName] = *ticket; + batchTransaction[jss::RawTransaction][jss::Sequence] = 0; + batchTransaction[jss::RawTransaction][sfTicketSequence.jsonName] = + *ticket; } return jv; @@ -199,7 +187,7 @@ class Batch_test : public beast::unit_test::suite withBatch ? ter(tesSUCCESS) : ter(temDISABLED); env(batch::batch(alice, seq, batchFee, tfAllOrNothing), - batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), + batch::add(pay(alice, bob, XRP(1)), seq + 1), txResult); env.close(); } @@ -253,7 +241,7 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, XRP(10)); - jv = addBatchTx(jv, tx1, alice, 1, env.seq(alice)); + jv = addBatchTx(jv, tx1, env.seq(alice) + 1); 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())}; @@ -261,7 +249,7 @@ class Batch_test : public beast::unit_test::suite // Tx 2 Json::Value const tx2 = pay(bob, alice, XRP(5)); - jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob)); + jv = addBatchTx(jv, tx2, 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())}; @@ -288,15 +276,15 @@ class Batch_test : public beast::unit_test::suite auto const seq = env.seq(alice); 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)), seq + 1), + batch::add(pay(alice, bob, XRP(1)), seq + 2), + batch::add(pay(alice, bob, XRP(1)), seq + 3), + batch::add(pay(alice, bob, XRP(1)), seq + 4), + batch::add(pay(alice, bob, XRP(1)), seq + 5), + batch::add(pay(alice, bob, XRP(1)), seq + 6), + batch::add(pay(alice, bob, XRP(1)), seq + 7), + batch::add(pay(alice, bob, XRP(1)), seq + 8), + batch::add(pay(alice, bob, XRP(1)), seq + 9), ter(temARRAY_TOO_LARGE)); env.close(); } @@ -306,8 +294,8 @@ class Batch_test : public beast::unit_test::suite auto const seq = env.seq(alice); auto const batchFee = ((9 + 2) * feeDrops) + 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)), seq + 1), + batch::add(pay(alice, bob, XRP(1)), seq + 2), batch::sig( bob, carol, @@ -336,7 +324,7 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, XRP(10)); - jv = addBatchTx(jv, tx1, alice, 1, env.seq(alice)); + jv = addBatchTx(jv, tx1, env.seq(alice) + 1); 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())}; @@ -344,7 +332,7 @@ class Batch_test : public beast::unit_test::suite // Tx 2 Json::Value const tx2 = pay(bob, alice, XRP(5)); - jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob)); + jv = addBatchTx(jv, tx2, 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())}; @@ -385,14 +373,14 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, XRP(10)); - jv = addBatchTx(jv, tx1, alice, 1, env.seq(alice)); + jv = addBatchTx(jv, tx1, env.seq(alice) + 1); 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())}; // Tx 2 Json::Value const tx2 = pay(bob, alice, XRP(5)); - jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob)); + jv = addBatchTx(jv, tx2, env.seq(bob)); // Add a duplicate hash jv[sfTxIDs.jsonName].append(to_string(stx1.getTransactionID())); @@ -410,14 +398,14 @@ class Batch_test : public beast::unit_test::suite // Tx 1 Json::Value tx1 = pay(alice, bob, XRP(10)); - jv = addBatchTx(jv, tx1, alice, 1, env.seq(alice)); + jv = addBatchTx(jv, tx1, env.seq(alice) + 1); 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())}; // Tx 2 Json::Value const tx2 = pay(bob, alice, XRP(5)); - jv = addBatchTx(jv, tx2, alice, 0, env.seq(bob)); + jv = addBatchTx(jv, tx2, 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())}; @@ -439,11 +427,8 @@ class Batch_test : public beast::unit_test::suite 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), + batch::batch(alice, seq, batchFee, tfAllOrNothing), seq), + batch::add(pay(alice, bob, XRP(1)), seq + 2), ter(temINVALID_BATCH)); env.close(); } @@ -453,8 +438,8 @@ class Batch_test : public beast::unit_test::suite auto const seq = env.seq(alice); 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), + batch::add(acctdelete(alice, bob), seq + 1), + batch::add(pay(alice, bob, XRP(1)), seq + 2), ter(temINVALID_BATCH)); env.close(); } @@ -464,8 +449,8 @@ class Batch_test : public beast::unit_test::suite 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::add(pay(alice, bob, XRP(10)), seq + 0), + batch::add(pay(bob, alice, XRP(5)), env.seq(bob)), batch::sig(carol), ter(temBAD_SIGNER)); env.close(); @@ -476,8 +461,8 @@ class Batch_test : public beast::unit_test::suite 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::add(pay(alice, bob, XRP(10)), seq + 0), + batch::add(pay(bob, alice, XRP(5)), env.seq(bob)), batch::sig(bob, carol), ter(temBAD_SIGNER)); env.close(); @@ -519,8 +504,8 @@ class Batch_test : public beast::unit_test::suite 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::add(pay(alice, bob, XRP(10)), preAliceSeq + 1), + batch::add(pay(bob, alice, XRP(5)), preBobSeq + 10), batch::sig(bob), ter(tecBATCH_FAILURE)); @@ -568,8 +553,8 @@ class Batch_test : public beast::unit_test::suite 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::add(pay(alice, bob, XRP(10)), preAliceSeq + 1), + batch::add(pay(bob, alice, XRP(5)), preBobSeq), batch::sig(bob), ter(telINSUF_FEE_P)); env.close(); @@ -618,8 +603,8 @@ class Batch_test : public beast::unit_test::suite auto const batchFee = ((0 + 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, 0, preBobSeq), + batch::add(pay(alice, bob, XRP(10)), preAliceSeq + 1), + batch::add(pay(bob, alice, XRP(5)), preBobSeq), batch::sig(bob), ter(telINSUF_FEE_P)); env.close(); @@ -655,13 +640,10 @@ class Batch_test : public beast::unit_test::suite auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 3; auto const seq = env.seq(alice); env(batch::batch(alice, seq, batchFee, tfAllOrNothing), - batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), + batch::add(pay(alice, bob, XRP(1)), seq + 1), batch::add( - jtx::trust(alice, alice["USD"](1000), tfSetfAuth), - alice, - 2, - seq), - batch::add(pay(alice, bob, XRP(1)), alice, 3, seq), + jtx::trust(alice, alice["USD"](1000), tfSetfAuth), seq + 2), + batch::add(pay(alice, bob, XRP(1)), seq + 3), ter(tecBATCH_FAILURE)); env.close(); } @@ -671,13 +653,10 @@ class Batch_test : public beast::unit_test::suite auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 3; auto const seq = env.seq(alice); env(batch::batch(alice, seq, batchFee, tfUntilFailure), - batch::add(pay(alice, bob, XRP(1)), alice, 1, seq), + batch::add(pay(alice, bob, XRP(1)), seq + 1), batch::add( - jtx::trust(alice, alice["USD"](1000), tfSetfAuth), - alice, - 2, - seq), - batch::add(pay(alice, bob, XRP(1)), alice, 3, seq), + jtx::trust(alice, alice["USD"](1000), tfSetfAuth), seq + 2), + batch::add(pay(alice, bob, XRP(1)), seq + 3), ter(tecBATCH_FAILURE)); env.close(); } @@ -688,11 +667,8 @@ class Batch_test : public beast::unit_test::suite auto const seq = env.seq(alice); 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), + jtx::trust(alice, alice["USD"](1000), tfSetfAuth), seq + 1), + batch::add(pay(alice, bob, XRP(1)), seq + 2), ter(tecBATCH_FAILURE)); env.close(); } @@ -703,11 +679,8 @@ class Batch_test : public beast::unit_test::suite auto const seq = env.seq(alice); 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), + jtx::trust(alice, alice["USD"](1000), tfSetfAuth), seq + 1), + batch::add(pay(alice, bob, XRP(1)), seq + 2), ter(tecBATCH_FAILURE)); env.close(); } @@ -738,21 +711,13 @@ class Batch_test : public beast::unit_test::suite auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 2; auto const seq = env.seq(alice); 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)), seq + 1), + batch::add(pay(alice, bob, XRP(1)), seq + 2), ter(tesSUCCESS)); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "Payment", - "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5" - "BE5"}, - {"tesSUCCESS", - "Payment", - "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C1360" - "7DA"}, - }}; + std::vector testCases = { + {"tesSUCCESS", "Payment"}, {"tesSUCCESS", "Payment"}}; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -785,21 +750,15 @@ class Batch_test : public beast::unit_test::suite auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 2; auto const seq = env.seq(alice); 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), + batch::add(pay(alice, bob, XRP(1)), seq + 1), + batch::add(pay(alice, bob, XRP(999)), seq + 2), ter(tecBATCH_FAILURE)); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "Payment", - "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5" - "BE5"}, - {"tecUNFUNDED_PAYMENT", - "Payment", - "68803BEF141614DBBB34FA34BE0E485D79A43328891A9A8BDC461B6F22836" - "A5C"}, - }}; + std::vector testCases = { + {"tesSUCCESS", "Payment"}, + {"tecUNFUNDED_PAYMENT", "Payment"}, + }; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -839,22 +798,16 @@ class Batch_test : public beast::unit_test::suite auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 3; auto const seq = env.seq(alice); 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), + batch::add(pay(alice, bob, XRP(999)), seq + 1), + batch::add(pay(alice, bob, XRP(1)), seq + 2), + batch::add(pay(alice, bob, XRP(1)), seq + 3), ter(tesSUCCESS)); env.close(); - std::vector testCases = {{ - {"tecUNFUNDED_PAYMENT", - "Payment", - "093B51856BA4C111D626D933AC8D8EF8CCEB16B754EFE8A03819043E4927F50" - "3"}, - {"tesSUCCESS", - "Payment", - "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607D" - "A"}, - }}; + std::vector testCases = { + {"tecUNFUNDED_PAYMENT", "Payment"}, + {"tesSUCCESS", "Payment"}, + }; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -893,27 +846,18 @@ class Batch_test : public beast::unit_test::suite auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 4; auto const seq = env.seq(alice); 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), + batch::add(pay(alice, bob, XRP(1)), seq + 1), + batch::add(pay(alice, bob, XRP(1)), seq + 2), + batch::add(pay(alice, bob, XRP(999)), seq + 3), + batch::add(pay(alice, bob, XRP(1)), seq + 4), ter(tesSUCCESS)); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "Payment", - "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE" - "5"}, - {"tesSUCCESS", - "Payment", - "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607D" - "A"}, - {"tecUNFUNDED_PAYMENT", - "Payment", - "E6FC37AF2B22F398E7D32B89C73D2443DF8BE7A2F35CA8B0B6AF6E9A504A67F" - "4"}, - }}; + std::vector testCases = { + {"tesSUCCESS", "Payment"}, + {"tesSUCCESS", "Payment"}, + {"tecUNFUNDED_PAYMENT", "Payment"}, + }; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -952,31 +896,19 @@ class Batch_test : public beast::unit_test::suite auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 4; auto const seq = env.seq(alice); 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), + batch::add(pay(alice, bob, XRP(1)), seq + 1), + batch::add(pay(alice, bob, XRP(1)), seq + 2), + batch::add(pay(alice, bob, XRP(999)), seq + 3), + batch::add(pay(alice, bob, XRP(1)), seq + 4), ter(tesSUCCESS)); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "Payment", - "CF28B462454DC1651D1705E3C2BD49E0C4D91245C68D3A10D27CF56E5C9B5BE" - "5"}, - {"tesSUCCESS", - "Payment", - "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607D" - "A"}, - {"tecUNFUNDED_PAYMENT", - "Payment", - "E6FC37AF2B22F398E7D32B89C73D2443DF8BE7A2F35CA8B0B6AF6E9A504A67F" - "4"}, - {"tesSUCCESS", - "Payment", - "19E953305CF8D48C481ED35A577196432463AE420D52D68463BD5724492C7E9" - "6"}, - }}; + std::vector testCases = { + {"tesSUCCESS", "Payment"}, + {"tesSUCCESS", "Payment"}, + {"tecUNFUNDED_PAYMENT", "Payment"}, + {"tesSUCCESS", "Payment"}, + }; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -1018,21 +950,15 @@ class Batch_test : public beast::unit_test::suite 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::add(pay(alice, bob, XRP(10)), seq + 1), + batch::add(pay(bob, alice, XRP(5)), env.seq(bob)), batch::sig(bob)); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "Payment", - "F74D7914EB0CB080E7004AA11AAFCA7687558F1B43C0B34A0438BBC9AE70857" - "1"}, - {"tesSUCCESS", - "Payment", - "9464BBDA1E5893486507DDA75D702739B9FE3DA94D9D002A2DBD3840688AF76" - "6"}, - }}; + std::vector testCases = { + {"tesSUCCESS", "Payment"}, + {"tesSUCCESS", "Payment"}, + }; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -1075,21 +1001,15 @@ class Batch_test : public beast::unit_test::suite auto const seq = env.seq(alice); auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 6; 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)), seq + 1), + batch::add(pay(alice, bob, XRP(1)), seq + 2), msig(bob, carol)); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "Payment", - "21131DBC8CD39D1A514939F988B56235F33A38BD58762CE0CAF8EFA9489DB32" - "7"}, - {"tesSUCCESS", - "Payment", - "1C25CCB1FF8A57B53B39B9287BA48DCD62DF3F213D125FF22C8A891FAC955C3" - "2"}, - }}; + std::vector testCases = { + {"tesSUCCESS", "Payment"}, + {"tesSUCCESS", "Payment"}, + }; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -1133,21 +1053,15 @@ class Batch_test : public beast::unit_test::suite 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::add(pay(alice, bob, XRP(10)), seq + 1), + batch::add(pay(bob, alice, XRP(5)), env.seq(bob)), batch::msig(bob, {dave, carol})); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "Payment", - "F74D7914EB0CB080E7004AA11AAFCA7687558F1B43C0B34A0438BBC9AE70857" - "1"}, - {"tesSUCCESS", - "Payment", - "9464BBDA1E5893486507DDA75D702739B9FE3DA94D9D002A2DBD3840688AF76" - "6"}, - }}; + std::vector testCases = { + {"tesSUCCESS", "Payment"}, + {"tesSUCCESS", "Payment"}, + }; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -1187,12 +1101,10 @@ class Batch_test : public beast::unit_test::suite env(pay(gw, bob, USD(100))); env.close(); + // Invalid: txn has `tfInnerBatchTxn` flag and signature { auto jv = pay(alice, bob, USD(1)); - jv[sfBatchTxn.jsonName] = Json::Value{}; - jv[sfBatchTxn.jsonName][jss::OuterAccount] = alice.human(); - jv[sfBatchTxn.jsonName][sfSequence.jsonName] = 0; - jv[sfBatchTxn.jsonName][sfBatchIndex.jsonName] = 0; + jv[sfFlags.fieldName] = tfInnerBatchTxn; Serializer s; auto jt = env.jt(jv); @@ -1206,14 +1118,15 @@ class Batch_test : public beast::unit_test::suite env.close(); } + + // Invalid: txn has `tfInnerBatchTxn` flag and no signature { std::string txBlob = - "1200002280000000240000000561D4838D7EA4C68000000000000000000000" + "1200002240000000240000000561D4838D7EA4C68000000000000000000000" "0000005553440000000000A407AF5856CCF3C42619DAA925813FC955C72983" "68400000000000000A73210388935426E0D08083314842EDFBB2D517BD4769" "9F9A4527318A8E10468C97C0528114AE123A8556F3CF91154711376AFB0F89" - "4F832B3D8314F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90FE023240000" - "0000801814AE123A8556F3CF91154711376AFB0F894F832B3D00101400E1"; + "4F832B3D8314F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90F"; auto const jrr = env.rpc("submit", txBlob)[jss::result]; BEAST_EXPECT( jrr[jss::status] == "success" && @@ -1250,21 +1163,15 @@ class Batch_test : public beast::unit_test::suite 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(1000)), alice, 1, seq), - batch::add(tx1, alice, 0, ledSeq), + batch::add(pay(alice, bob, XRP(1000)), seq + 1), + batch::add(tx1, ledSeq), batch::sig(bob)); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "Payment", - "A2639F4AC0E57B007A8EEFAEDD00DBD608588A34ECB29A2A55139F0147AA7C9" - "9"}, - {"tesSUCCESS", - "AccountSet", - "16515DD535232F8D9B5993539CBFB6DC49C0274B8DD18E0C7199BFE30511F0C" - "1"}, - }}; + std::vector testCases = { + {"tesSUCCESS", "Payment"}, + {"tesSUCCESS", "AccountSet"}, + }; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -1309,20 +1216,14 @@ class Batch_test : public beast::unit_test::suite auto const seq = env.seq(alice); auto const batchFee = ((0 + 2) * feeDrops) + 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)); + batch::add(tx1, seq + 1), + batch::add(pay(alice, bob, XRP(1)), seq + 2)); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "AccountSet", - "6B6B225E26F2F4811A651D7FD1E4F675D3E9F677C0167F8AAE707E2CB9B508A" - "6"}, - {"tesSUCCESS", - "Payment", - "002C79A3D4BB339E09C358450D96B885C21B7F5701B0E908DAC3DFE6C13607D" - "A"}, - }}; + std::vector testCases = { + {"tesSUCCESS", "AccountSet"}, + {"tesSUCCESS", "Payment"}, + }; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -1382,22 +1283,15 @@ class Batch_test : public beast::unit_test::suite auto const batchFee = ((1 + 2) * feeDrops) + feeDrops * 2; uint256 const chkId{getCheckIndex(bob, env.seq(bob))}; 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::add(check::create(bob, alice, USD(10)), env.seq(bob)), + batch::add(check::cash(alice, chkId, USD(10)), seq + 1), batch::sig(bob)); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "CheckCreate", - "6443C49FA0E30F09AD6EF418EABC031E70AE854D62D0543F34214A3F1BADB5C" - "1"}, - {"tesSUCCESS", - "CheckCash", - "ABFC2892253C19A9312F5BEF9BDA7399498A9650F3F64777EE5A5C498B6BCFB" - "6"}, - }}; + std::vector testCases = { + {"tesSUCCESS", "CheckCreate"}, + {"tesSUCCESS", "CheckCash"}, + }; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -1453,22 +1347,15 @@ class Batch_test : public beast::unit_test::suite auto const batchFee = ((1 + 2) * feeDrops) + feeDrops * 2; uint256 const chkId{getCheckIndex(bob, bobTicketSeq)}; 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::add(check::create(bob, alice, USD(10)), 0, bobTicketSeq), + batch::add(check::cash(alice, chkId, USD(10)), seq + 1), batch::sig(bob)); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "CheckCreate", - "3D06827C2B17BAA07887C8E101DC6779EC7A3807E79E88D64022BA14DC0B252" - "C"}, - {"tesSUCCESS", - "CheckCash", - "B276687A136BD0FFE4B03F84ABB5C5F7A72C3D015968CEE83A27A7881E87127" - "F"}, - }}; + std::vector testCases = { + {"tesSUCCESS", "CheckCreate"}, + {"tesSUCCESS", "CheckCash"}, + }; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -1522,23 +1409,15 @@ class Batch_test : public beast::unit_test::suite auto const batchFee = ((2 + 2) * feeDrops) + feeDrops * 2; uint256 const chkId{getCheckIndex(bob, env.seq(bob))}; 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::add(check::create(bob, alice, USD(10)), env.seq(bob)), + batch::add(check::cash(alice, chkId, USD(10)), env.seq(alice)), batch::sig(alice, bob)); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "CheckCreate", - "74EE0A770F85E93055072F4BD8286D235AE6B333AF41C7AA44A45DD63643752" - "E"}, - {"tesSUCCESS", - "CheckCash", - "9CFCBABC4729B388C265A45F5B6C13ED2AF67942EC21FE6064FDBBF5F131609" - "3"}, - }}; + std::vector testCases = { + {"tesSUCCESS", "CheckCreate"}, + {"tesSUCCESS", "CheckCash"}, + }; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -1586,21 +1465,15 @@ class Batch_test : public beast::unit_test::suite auto const seq = env.seq(alice); auto const batchFee = ((0 + 2) * feeDrops) + 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), + batch::add(pay(alice, bob, XRP(1)), seq + 0), + batch::add(pay(alice, bob, XRP(1)), seq + 1), ticket::use(aliceTicketSeq++)); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "Payment", - "18629D496965A11CAB4454B86DB794BA07DABA3EE154DEFE9259977221E937E" - "5"}, - {"tesSUCCESS", - "Payment", - "7654B768E091768EB0D43C8EE33B7E72C82BC7A584D578F2646721F69AEEAB7" - "2"}, - }}; + std::vector testCases = { + {"tesSUCCESS", "Payment"}, + {"tesSUCCESS", "Payment"}, + }; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -1648,21 +1521,14 @@ class Batch_test : public beast::unit_test::suite auto const seq = env.seq(alice); auto const batchFee = ((0 + 2) * feeDrops) + 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 + 1)); + batch::add(pay(alice, bob, XRP(1)), 0, aliceTicketSeq), + batch::add(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1)); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "Payment", - "684C0FE631535577FE8BE663848AB3AFE71C6CD688101E4FEB43B9C13374DBB" - "2"}, - {"tesSUCCESS", - "Payment", - "AF3029D2951DEFB2A6A644A15DB3689FFA5FEA31C6923067DC43E9C31DFA915" - "B"}, - }}; + std::vector testCases = { + {"tesSUCCESS", "Payment"}, + {"tesSUCCESS", "Payment"}, + }; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -1710,22 +1576,15 @@ class Batch_test : public beast::unit_test::suite auto const seq = env.seq(alice); auto const batchFee = ((0 + 2) * feeDrops) + feeDrops * 2; env(batch::batch(alice, 0, batchFee, tfAllOrNothing), - batch::add( - pay(alice, bob, XRP(1)), alice, 1, 0, aliceTicketSeq + 1), - batch::add(pay(alice, bob, XRP(1)), alice, 0, seq), + batch::add(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1), + batch::add(pay(alice, bob, XRP(1)), seq + 0), ticket::use(aliceTicketSeq)); env.close(); - std::vector testCases = {{ - {"tesSUCCESS", - "Payment", - "AF3029D2951DEFB2A6A644A15DB3689FFA5FEA31C6923067DC43E9C31DFA915" - "B"}, - {"tesSUCCESS", - "Payment", - "18629D496965A11CAB4454B86DB794BA07DABA3EE154DEFE9259977221E937E" - "5"}, - }}; + std::vector testCases = { + {"tesSUCCESS", "Payment"}, + {"tesSUCCESS", "Payment"}, + }; Json::Value params; params[jss::ledger_index] = env.current()->seq() - 1; @@ -1778,6 +1637,10 @@ class Batch_test : public beast::unit_test::suite // TODO: tecINVARIANT_FAILED // You cannot check the invariants without applying the transactions but // you cannot revert the transactions after they have been applied. + + // TODO: test with `tfPartialPayment` + // TODO: add a test case to exercise the behavior where the last signer + // signature is correct, but previous ones are bad. } public: diff --git a/src/test/jtx/batch.h b/src/test/jtx/batch.h index 3703fdb2d21..d0d4b21a80a 100644 --- a/src/test/jtx/batch.h +++ b/src/test/jtx/batch.h @@ -49,20 +49,14 @@ 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) { diff --git a/src/test/jtx/impl/batch.cpp b/src/test/jtx/impl/batch.cpp index 85e8f3e80d7..5efb4341df6 100644 --- a/src/test/jtx/impl/batch.cpp +++ b/src/test/jtx/impl/batch.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -65,20 +66,16 @@ add::operator()(Env& env, JTx& jt) const 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_; + batchTransaction[jss::RawTransaction][jss::Sequence] = seq_; + batchTransaction[jss::RawTransaction][jss::Flags] = + batchTransaction[jss::RawTransaction][jss::Flags].asUInt() | + tfInnerBatchTxn; // Optionally set ticket sequence if (ticket_.has_value()) { - batchTxn[sfSequence.jsonName] = 0; - batchTxn[sfTicketSequence.jsonName] = *ticket_; + batchTransaction[jss::RawTransaction][jss::Sequence] = 0; + batchTransaction[jss::RawTransaction][sfTicketSequence.jsonName] = *ticket_; } // Set the hash of the transaction diff --git a/src/xrpld/app/ledger/detail/OpenLedger.cpp b/src/xrpld/app/ledger/detail/OpenLedger.cpp index 0af84ab5f61..1d201b68650 100644 --- a/src/xrpld/app/ledger/detail/OpenLedger.cpp +++ b/src/xrpld/app/ledger/detail/OpenLedger.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace ripple { @@ -123,7 +124,7 @@ OpenLedger::accept( auto const txId = tx->getTransactionID(); // skip batch txns - if (tx->isFieldPresent(sfBatchTxn)) + if (tx->isFlag(tfInnerBatchTxn)) continue; if (auto const toSkip = app.getHashRouter().shouldRelay(txId)) diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 3a0b54aabef..60b50f64ba6 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -1139,7 +1140,7 @@ NetworkOPsImp::submitTransaction(std::shared_ptr const& iTrans) // Enforce Network bar for batch txn if (auto const view = m_ledgerMaster.getCurrentLedger(); view->rules().enabled(featureBatch) && - iTrans->isFieldPresent(sfBatchTxn)) + iTrans->isFlag(tfInnerBatchTxn)) { JLOG(m_journal.error()) << "Submitted transaction invalid: BatchTxn present."; @@ -1220,7 +1221,7 @@ NetworkOPsImp::processTransaction( // txn from the network. auto const tx = *transaction->getSTransaction(); if (view->rules().enabled(featureBatch) && - tx.isFieldPresent(ripple::sfBatchTxn)) + tx.isFlag(tfInnerBatchTxn)) { transaction->setStatus(INVALID); transaction->setResult(temINVALID_BATCH); @@ -1487,7 +1488,7 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) app_.getHashRouter().shouldRelay(e.transaction->getID()); if (auto const txn = *(e.transaction->getSTransaction()); - toSkip && !txn.isFieldPresent(sfBatchTxn)) + toSkip && !txn.isFlag(tfInnerBatchTxn)) { protocol::TMTransaction tx; Serializer s; @@ -2779,7 +2780,7 @@ NetworkOPsImp::pubProposedTransaction( TER result) { // never publish an inner txn inside a batch txn - if (transaction->isFieldPresent(ripple::sfBatchTxn)) + if (transaction->isFlag(tfInnerBatchTxn)) return; MultiApiJson jvObj = diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index ddab4eb4358..474bc59f89a 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include namespace ripple { @@ -200,7 +201,7 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) return temBAD_FEE; auto const feePaid = ctx.tx[sfFee].xrp(); - if (ctx.tx.isFieldPresent(sfBatchTxn)) + if (ctx.tx.isFlag(tfInnerBatchTxn)) { if (feePaid == beast::zero) { @@ -296,16 +297,6 @@ Transactor::checkSeqProxy( SeqProxy const t_seqProx = tx.getSeqProxy(); SeqProxy a_seq = SeqProxy::sequence((*sle)[sfSequence]); - if (tx.isFieldPresent(sfBatchTxn)) - { - if (tx.getFieldU32(sfSequence) != 0) - { - JLOG(j.trace()) - << "applyTransaction: BatchTxn has a Sequence number"; - return temBAD_SEQUENCE; - } - } - if (t_seqProx.isSeq()) { if (tx.isFieldPresent(sfTicketSequence) && @@ -504,7 +495,7 @@ NotTEC Transactor::checkSign(PreclaimContext const& ctx) { // do not check signature of inner batch txn - if (ctx.tx.isFieldPresent(sfBatchTxn)) + if (ctx.tx.isFlag(tfInnerBatchTxn)) return tesSUCCESS; auto const idAccount = ctx.tx.getAccountID(sfAccount); @@ -964,7 +955,7 @@ Transactor::operator()() result = tecOVERSIZE; if ((isTecClaim(result) && (view().flags() & tapFAIL_HARD) && - !ctx_.tx.isFieldPresent(sfBatchTxn))) + !ctx_.tx.isFlag(tfInnerBatchTxn))) { // If the tapFAIL_HARD flag is set, a tec result // must not do anything diff --git a/src/xrpld/app/tx/detail/apply.cpp b/src/xrpld/app/tx/detail/apply.cpp index e7ef819e283..5d4c693d864 100644 --- a/src/xrpld/app/tx/detail/apply.cpp +++ b/src/xrpld/app/tx/detail/apply.cpp @@ -22,6 +22,7 @@ #include #include #include +#include namespace ripple { @@ -45,7 +46,7 @@ checkValidity( auto const flags = router.getFlags(id); // Validate Inner BatchTxn - if (rules.enabled(featureBatch) && tx.isFieldPresent(sfBatchTxn)) + if (rules.enabled(featureBatch) && tx.isFlag(tfInnerBatchTxn)) { // batched transactions do not contain signatures if (tx.isFieldPresent(sfTxnSignature)) diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index e73c48ce8a0..c7d06f0eb21 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -39,6 +39,7 @@ #include #include // #include +#include #include #include @@ -1238,11 +1239,11 @@ PeerImp::handleTransaction( auto stx = std::make_shared(sit); uint256 txID = stx->getTransactionID(); - // Charge strongly for attempting to relay a txn with sfBatchTxn - if (stx->isFieldPresent(sfBatchTxn)) + // Charge strongly for attempting to relay a txn with tfInnerBatchTxn + if (stx->isFlag(tfInnerBatchTxn)) { JLOG(p_journal_.warn()) << "Ignoring Network relayed Tx containing " - "sfBatchTxn (handleTransaction)."; + "tfInnerBatchTxn (handleTransaction)."; fee_ = Resource::feeHighBurdenPeer; return; } @@ -2739,10 +2740,10 @@ PeerImp::checkTransaction( try { // charge strongly for relaying batch txns - if (stx->isFieldPresent(sfBatchTxn)) + if (stx->isFlag(tfInnerBatchTxn)) { JLOG(p_journal_.warn()) << "Ignoring Network relayed Tx containing " - "sfBatchTxn (checkSignature)."; + "tfInnerBatchTxn (checkSignature)."; charge(Resource::feeHighBurdenPeer); return; } From 47120ac1780c634e53437b1177379375595ea003 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 7 Nov 2024 01:53:33 +0100 Subject: [PATCH 67/71] [fixup] rename `prevFields` -> `batchPrevAcctRootFields` --- src/xrpld/ledger/ApplyViewImpl.h | 4 ++-- src/xrpld/ledger/detail/ApplyStateTable.cpp | 6 +++--- src/xrpld/ledger/detail/ApplyStateTable.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/xrpld/ledger/ApplyViewImpl.h b/src/xrpld/ledger/ApplyViewImpl.h index cc2d363e1be..79c94ccc7d8 100644 --- a/src/xrpld/ledger/ApplyViewImpl.h +++ b/src/xrpld/ledger/ApplyViewImpl.h @@ -78,9 +78,9 @@ class ApplyViewImpl final : public detail::ApplyViewBase * Takes ownership / use std::move */ void - setBatchPrevMetaData(STObject const& prevFields) + setBatchPrevMetaData(STObject const& batchPrevAcctRootFields) { - batchPrevAcctRootFields_ = prevFields; + batchPrevAcctRootFields_ = batchPrevAcctRootFields; } void diff --git a/src/xrpld/ledger/detail/ApplyStateTable.cpp b/src/xrpld/ledger/detail/ApplyStateTable.cpp index 12529051cd7..809f20ded75 100644 --- a/src/xrpld/ledger/detail/ApplyStateTable.cpp +++ b/src/xrpld/ledger/detail/ApplyStateTable.cpp @@ -116,7 +116,7 @@ ApplyStateTable::apply( TER ter, std::optional const& deliver, std::vector const& batchExecution, - std::optional const& batchPrev, + std::optional const& batchPrevAcctRootFields, beast::Journal j) { bool const isBatch = tx.getTxnType() == ttBATCH; @@ -216,10 +216,10 @@ ApplyStateTable::apply( prevs.emplace_back(obj); } - if (isBatch && nodeType == ltACCOUNT_ROOT && batchPrev) + if (isBatch && nodeType == ltACCOUNT_ROOT && batchPrevAcctRootFields) { // TODO: This could fail if the fields already exist - for (auto const& obj : *batchPrev) + for (auto const& obj : *batchPrevAcctRootFields) prevs.emplace_back(obj); } diff --git a/src/xrpld/ledger/detail/ApplyStateTable.h b/src/xrpld/ledger/detail/ApplyStateTable.h index d4cd78522a8..7c2e078a537 100644 --- a/src/xrpld/ledger/detail/ApplyStateTable.h +++ b/src/xrpld/ledger/detail/ApplyStateTable.h @@ -71,7 +71,7 @@ class ApplyStateTable TER ter, std::optional const& deliver, std::vector const& batchExecution, - std::optional const& batchPrev, + std::optional const& batchPrevAcctRootFields, beast::Journal j); bool From 6ed4ac81db774fc1f96023f7aad2206d530a96f4 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 7 Nov 2024 01:56:54 +0100 Subject: [PATCH 68/71] [fold] revert applyFlags change --- src/xrpld/app/tx/apply.h | 3 +-- src/xrpld/app/tx/detail/Transactor.cpp | 3 +-- src/xrpld/app/tx/detail/apply.cpp | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/xrpld/app/tx/apply.h b/src/xrpld/app/tx/apply.h index eaf79e21278..6a0401e2b55 100644 --- a/src/xrpld/app/tx/apply.h +++ b/src/xrpld/app/tx/apply.h @@ -64,8 +64,7 @@ checkValidity( HashRouter& router, STTx const& tx, Rules const& rules, - Config const& config, - ApplyFlags const& applyFlags = tapNONE); + Config const& config); /** Sets the validity of a given transaction in the cache. diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 474bc59f89a..dccbc2bdca7 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -137,8 +137,7 @@ preflight2(PreflightContext const& ctx) ctx.app.getHashRouter(), ctx.tx, ctx.rules, - ctx.app.config(), - ctx.flags); + ctx.app.config()); if (sigValid.first == Validity::SigBad) { JLOG(ctx.j.debug()) << "preflight2: bad signature. " << sigValid.second; diff --git a/src/xrpld/app/tx/detail/apply.cpp b/src/xrpld/app/tx/detail/apply.cpp index 5d4c693d864..b7aee5998c3 100644 --- a/src/xrpld/app/tx/detail/apply.cpp +++ b/src/xrpld/app/tx/detail/apply.cpp @@ -39,8 +39,7 @@ checkValidity( HashRouter& router, STTx const& tx, Rules const& rules, - Config const& config, - ApplyFlags const& applyFlags) + Config const& config) { auto const id = tx.getTransactionID(); auto const flags = router.getFlags(id); From 52f1ba912eab0ddf8301407b1b6eb05e15a077eb Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 7 Nov 2024 02:04:05 +0100 Subject: [PATCH 69/71] [fold] use `unordered_set` --- src/test/app/Batch_test.cpp | 1 + src/xrpld/app/tx/detail/Batch.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index bb6c3c1da04..9814097563e 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -1641,6 +1641,7 @@ class Batch_test : public beast::unit_test::suite // TODO: test with `tfPartialPayment` // TODO: add a test case to exercise the behavior where the last signer // signature is correct, but previous ones are bad. + // TODO: tfUntilFailure verify it's possible that none of the inner txns succeeds and yet the batch txn is "successful"? } public: diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index dbb86aca35f..5aec5b3cf12 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -141,11 +141,11 @@ Batch::preflight(PreflightContext const& ctx) } } - std::set uniqueSigners; - std::set uniqueHashes; + std::unordered_set uniqueSigners; + std::unordered_set> uniqueHashes; for (int i = 0; i < txns.size(); ++i) { - if (!uniqueHashes.insert(hashes[i]).second) + if (!uniqueHashes.emplace(hashes[i]).second) { JLOG(ctx.j.trace()) << "Batch: duplicate TxID found."; return temMALFORMED; @@ -193,7 +193,7 @@ Batch::preflight(PreflightContext const& ctx) continue; // Add the inner account to the unique signers set. - uniqueSigners.insert(innerAccount); + uniqueSigners.emplace(innerAccount); // Validate that the account for this (inner) txn has a signature in the // batch signers array. From 6ffe349efc6168a184a9e8dbb1aa9e7d9b22dbec Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 7 Nov 2024 02:09:38 +0100 Subject: [PATCH 70/71] [fold] clang-format --- src/libxrpl/protocol/InnerObjectFormats.cpp | 1 - src/test/app/Batch_test.cpp | 3 ++- src/test/jtx/batch.h | 4 +--- src/test/jtx/impl/batch.cpp | 5 +++-- src/xrpld/app/misc/NetworkOPs.cpp | 6 ++---- src/xrpld/app/tx/detail/ApplyContext.cpp | 3 ++- src/xrpld/app/tx/detail/Transactor.cpp | 5 +---- src/xrpld/ledger/detail/ApplyStateTable.cpp | 3 ++- 8 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index b925ee2a79e..33dc822eb44 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -167,7 +167,6 @@ InnerObjectFormats::InnerObjectFormats() {sfSigningPubKey, soeOPTIONAL}, {sfTxnSignature, soeOPTIONAL}, {sfSigners, soeOPTIONAL}}); - } InnerObjectFormats const& diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 9814097563e..e3354314f10 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -1641,7 +1641,8 @@ class Batch_test : public beast::unit_test::suite // TODO: test with `tfPartialPayment` // TODO: add a test case to exercise the behavior where the last signer // signature is correct, but previous ones are bad. - // TODO: tfUntilFailure verify it's possible that none of the inner txns succeeds and yet the batch txn is "successful"? + // TODO: tfUntilFailure verify it's possible that none of the inner txns + // succeeds and yet the batch txn is "successful"? } public: diff --git a/src/test/jtx/batch.h b/src/test/jtx/batch.h index d0d4b21a80a..797b5bfcf6b 100644 --- a/src/test/jtx/batch.h +++ b/src/test/jtx/batch.h @@ -56,9 +56,7 @@ class add add(Json::Value const& txn, std::uint32_t const& sequence, std::optional const& ticket = std::nullopt) - : txn_(txn) - , seq_(sequence) - , ticket_(ticket) + : txn_(txn), seq_(sequence), ticket_(ticket) { } diff --git a/src/test/jtx/impl/batch.cpp b/src/test/jtx/impl/batch.cpp index 5efb4341df6..4d39b6fa096 100644 --- a/src/test/jtx/impl/batch.cpp +++ b/src/test/jtx/impl/batch.cpp @@ -74,8 +74,9 @@ add::operator()(Env& env, JTx& jt) const // Optionally set ticket sequence if (ticket_.has_value()) { - batchTransaction[jss::RawTransaction][jss::Sequence] = 0; - batchTransaction[jss::RawTransaction][sfTicketSequence.jsonName] = *ticket_; + batchTransaction[jss::RawTransaction][jss::Sequence] = 0; + batchTransaction[jss::RawTransaction][sfTicketSequence.jsonName] = + *ticket_; } // Set the hash of the transaction diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index baabc6745a3..7bec276d56c 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -1140,8 +1140,7 @@ NetworkOPsImp::submitTransaction(std::shared_ptr const& iTrans) // Enforce Network bar for batch txn if (auto const view = m_ledgerMaster.getCurrentLedger(); - view->rules().enabled(featureBatch) && - iTrans->isFlag(tfInnerBatchTxn)) + view->rules().enabled(featureBatch) && iTrans->isFlag(tfInnerBatchTxn)) { JLOG(m_journal.error()) << "Submitted transaction invalid: BatchTxn present."; @@ -1221,8 +1220,7 @@ NetworkOPsImp::processTransaction( // under no circumstances will we ever accept an inner txn within a batch // txn from the network. auto const tx = *transaction->getSTransaction(); - if (view->rules().enabled(featureBatch) && - tx.isFlag(tfInnerBatchTxn)) + if (view->rules().enabled(featureBatch) && tx.isFlag(tfInnerBatchTxn)) { transaction->setStatus(INVALID); transaction->setResult(temINVALID_BATCH); diff --git a/src/xrpld/app/tx/detail/ApplyContext.cpp b/src/xrpld/app/tx/detail/ApplyContext.cpp index e2d20368a99..7c3d5969f27 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.cpp +++ b/src/xrpld/app/tx/detail/ApplyContext.cpp @@ -115,7 +115,8 @@ ApplyContext::setBatchPrevAcctRootFields(ApplyViewImpl& avi) for (auto const& obj : *sleBaseAcct) { if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) && - (!sleAcct->hasMatchingEntry(obj) || obj.getFName() == sfSequence || + (!sleAcct->hasMatchingEntry(obj) || + obj.getFName() == sfSequence || obj.getFName() == sfOwnerCount || obj.getFName() == sfTicketCount)) { diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 2882ea47c30..94aa37f5a05 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -135,10 +135,7 @@ NotTEC preflight2(PreflightContext const& ctx) { auto const sigValid = checkValidity( - ctx.app.getHashRouter(), - ctx.tx, - ctx.rules, - ctx.app.config()); + ctx.app.getHashRouter(), ctx.tx, ctx.rules, ctx.app.config()); if (sigValid.first == Validity::SigBad) { JLOG(ctx.j.debug()) << "preflight2: bad signature. " << sigValid.second; diff --git a/src/xrpld/ledger/detail/ApplyStateTable.cpp b/src/xrpld/ledger/detail/ApplyStateTable.cpp index 809f20ded75..2766ea1d282 100644 --- a/src/xrpld/ledger/detail/ApplyStateTable.cpp +++ b/src/xrpld/ledger/detail/ApplyStateTable.cpp @@ -216,7 +216,8 @@ ApplyStateTable::apply( prevs.emplace_back(obj); } - if (isBatch && nodeType == ltACCOUNT_ROOT && batchPrevAcctRootFields) + if (isBatch && nodeType == ltACCOUNT_ROOT && + batchPrevAcctRootFields) { // TODO: This could fail if the fields already exist for (auto const& obj : *batchPrevAcctRootFields) From 04519e627fdacd5005b6eb4b902cdacae63e4528 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 7 Nov 2024 02:15:46 +0100 Subject: [PATCH 71/71] [fold] fix bad merge --- include/xrpl/protocol/detail/transactions.macro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 6c92d912174..37bb2aab400 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -448,7 +448,7 @@ TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, ({ })) /** This transaction type wraps inner transactions for batch. */ -TRANSACTION(ttBATCH, 58, Batch, ({ +TRANSACTION(ttBATCH, 61, Batch, ({ {sfRawTransactions, soeREQUIRED}, {sfTxIDs, soeREQUIRED}, {sfBatchSigners, soeOPTIONAL},