From 11b8f3ec1a5fbf68e43f55e653eaa9b022b9e07f Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Thu, 19 Feb 2026 20:03:45 +0900 Subject: [PATCH] fix amendment to add `PreviousTxnID`/`PreviousTxnLgrSequence` (#4751) This amendment, `fixPreviousTxnID`, adds `PreviousTxnID` and `PreviousTxnLgrSequence` as fields to all ledger objects that did not already have them included (`DirectoryNode`, `Amendments`, `FeeSettings`, `NegativeUNL`, and `AMM`). This makes it much easier to go through the history of these ledger objects. --- src/ripple/ledger/impl/ApplyStateTable.cpp | 8 +- src/ripple/protocol/Feature.h | 3 +- src/ripple/protocol/STLedgerEntry.h | 3 +- src/ripple/protocol/impl/Feature.cpp | 1 + src/ripple/protocol/impl/LedgerFormats.cpp | 34 +++++--- src/ripple/protocol/impl/STLedgerEntry.cpp | 20 ++++- src/ripple/protocol/jss.h | 1 + src/ripple/rpc/impl/RPCHelpers.cpp | 19 ++--- src/test/app/TxQ_test.cpp | 18 ++--- src/test/ledger/Directory_test.cpp | 91 ++++++++++++++++++++++ src/test/rpc/AccountTx_test.cpp | 4 +- src/test/rpc/Subscribe_test.cpp | 4 +- src/test/rpc/TransactionEntry_test.cpp | 10 +-- src/test/rpc/Transaction_test.cpp | 8 +- 14 files changed, 172 insertions(+), 52 deletions(-) diff --git a/src/ripple/ledger/impl/ApplyStateTable.cpp b/src/ripple/ledger/impl/ApplyStateTable.cpp index 6a742d95de..d065a2fc09 100644 --- a/src/ripple/ledger/impl/ApplyStateTable.cpp +++ b/src/ripple/ledger/impl/ApplyStateTable.cpp @@ -195,8 +195,8 @@ ApplyStateTable::generateTxMeta( { assert(curNode && origNode); - if (curNode->isThreadedType()) // thread transaction to node - // item modified + if (curNode->isThreadedType(to.rules())) // thread transaction to + // node item modified threadItem(meta, curNode, to.rules()); STObject prevs(sfPreviousFields); @@ -229,7 +229,7 @@ ApplyStateTable::generateTxMeta( assert(curNode && !origNode); threadOwners(to, meta, curNode, newMod, j); - if (curNode->isThreadedType()) // always thread to self + if (curNode->isThreadedType(to.rules())) // always thread to self threadItem(meta, curNode, to.rules()); STObject news(sfNewFields); @@ -718,6 +718,8 @@ ApplyStateTable::threadTx( JLOG(j.warn()) << "Threading to non-existent account: " << toBase58(to); return; } + // threadItem only applied to AccountRoot + assert(sle->isThreadedType(base.rules())); threadItem(meta, sle, base.rules()); } diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index 1a8a01bc25..0f6822a252 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -74,7 +74,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 = 104; +static constexpr std::size_t numFeatures = 105; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -392,6 +392,7 @@ extern uint256 const fixAMMOverflowOffer; extern uint256 const featurePriceOracle; extern uint256 const fixEmptyDID; extern uint256 const fixXChainRewardRounding; +extern uint256 const fixPreviousTxnID; } // namespace ripple diff --git a/src/ripple/protocol/STLedgerEntry.h b/src/ripple/protocol/STLedgerEntry.h index 14732ff5c2..6fd50aa154 100644 --- a/src/ripple/protocol/STLedgerEntry.h +++ b/src/ripple/protocol/STLedgerEntry.h @@ -25,6 +25,7 @@ namespace ripple { +class Rules; class Invariants_test; class STLedgerEntry final : public STObject, public CountedObject @@ -67,7 +68,7 @@ public: // is this a ledger entry that can be threaded bool - isThreadedType() const; + isThreadedType(Rules const& rules) const; bool thread( diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index b3517fabdb..553a974e42 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -498,6 +498,7 @@ REGISTER_FIX (fixAMMOverflowOffer, Supported::yes, VoteBehavior::De REGISTER_FEATURE(PriceOracle, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FIX (fixEmptyDID, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FIX (fixXChainRewardRounding, Supported::yes, VoteBehavior::DefaultNo); +REGISTER_FIX (fixPreviousTxnID, Supported::yes, VoteBehavior::DefaultNo); // The following amendments are obsolete, but must remain supported // because they could potentially get enabled. diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index 4e0523928a..0b6c5718d1 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -89,6 +89,8 @@ LedgerFormats::LedgerFormats() {sfIndexNext, soeOPTIONAL}, {sfIndexPrevious, soeOPTIONAL}, {sfNFTokenID, soeOPTIONAL}, + {sfPreviousTxnID, soeOPTIONAL}, + {sfPreviousTxnLgrSeq, soeOPTIONAL}, }, commonFields); @@ -160,6 +162,8 @@ LedgerFormats::LedgerFormats() { {sfAmendments, soeOPTIONAL}, // Enabled {sfMajorities, soeOPTIONAL}, + {sfPreviousTxnID, soeOPTIONAL}, + {sfPreviousTxnLgrSeq, soeOPTIONAL}, }, commonFields); @@ -175,10 +179,10 @@ LedgerFormats::LedgerFormats() ltFEE_SETTINGS, { // Old version uses raw numbers - {sfBaseFee, soeOPTIONAL}, - {sfReferenceFeeUnits, soeOPTIONAL}, - {sfReserveBase, soeOPTIONAL}, - {sfReserveIncrement, soeOPTIONAL}, + {sfBaseFee, soeOPTIONAL}, + {sfReferenceFeeUnits, soeOPTIONAL}, + {sfReserveBase, soeOPTIONAL}, + {sfReserveIncrement, soeOPTIONAL}, // New version uses Amounts {sfBaseFeeDrops, soeOPTIONAL}, {sfReserveBaseDrops, soeOPTIONAL}, @@ -186,6 +190,8 @@ LedgerFormats::LedgerFormats() {sfXahauActivationLgrSeq, soeOPTIONAL}, {sfAccountCount, soeOPTIONAL}, {sfNetworkID, soeOPTIONAL}, + {sfPreviousTxnID, soeOPTIONAL}, + {sfPreviousTxnLgrSeq, soeOPTIONAL}, }, commonFields); @@ -309,6 +315,8 @@ LedgerFormats::LedgerFormats() {sfDisabledValidators, soeOPTIONAL}, {sfValidatorToDisable, soeOPTIONAL}, {sfValidatorToReEnable, soeOPTIONAL}, + {sfPreviousTxnID, soeOPTIONAL}, + {sfPreviousTxnLgrSeq, soeOPTIONAL}, }, commonFields); @@ -387,14 +395,16 @@ LedgerFormats::LedgerFormats() add(jss::AMM, ltAMM, { - {sfAccount, soeREQUIRED}, - {sfTradingFee, soeDEFAULT}, - {sfVoteSlots, soeOPTIONAL}, - {sfAuctionSlot, soeOPTIONAL}, - {sfLPTokenBalance, soeREQUIRED}, - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, - {sfOwnerNode, soeREQUIRED}, + {sfAccount, soeREQUIRED}, + {sfTradingFee, soeDEFAULT}, + {sfVoteSlots, soeOPTIONAL}, + {sfAuctionSlot, soeOPTIONAL}, + {sfLPTokenBalance, soeREQUIRED}, + {sfAsset, soeREQUIRED}, + {sfAsset2, soeREQUIRED}, + {sfOwnerNode, soeREQUIRED}, + {sfPreviousTxnID, soeOPTIONAL}, + {sfPreviousTxnLgrSeq, soeOPTIONAL}, }, commonFields); diff --git a/src/ripple/protocol/impl/STLedgerEntry.cpp b/src/ripple/protocol/impl/STLedgerEntry.cpp index 8ad5e1fb5b..10ec5627aa 100644 --- a/src/ripple/protocol/impl/STLedgerEntry.cpp +++ b/src/ripple/protocol/impl/STLedgerEntry.cpp @@ -17,14 +17,19 @@ */ //============================================================================== +#include + #include #include #include #include +#include #include -#include +#include #include #include +#include +#include #include namespace ripple { @@ -124,9 +129,18 @@ STLedgerEntry::getJson(JsonOptions options) const } bool -STLedgerEntry::isThreadedType() const +STLedgerEntry::isThreadedType(Rules const& rules) const { - return getFieldIndex(sfPreviousTxnID) != -1; + static constexpr std::array newPreviousTxnIDTypes = { + ltDIR_NODE, ltAMENDMENTS, ltFEE_SETTINGS, ltNEGATIVE_UNL, ltAMM}; + // Exclude PrevTxnID/PrevTxnLgrSeq if the fixPreviousTxnID amendment is not + // enabled and the ledger object type is in the above set + bool const excludePrevTxnID = !rules.enabled(fixPreviousTxnID) && + std::count( + newPreviousTxnIDTypes.cbegin(), + newPreviousTxnIDTypes.cend(), + type_); + return !excludePrevTxnID && getFieldIndex(sfPreviousTxnID) != -1; } bool diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index f26ba6bc88..201450ec0a 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -617,6 +617,7 @@ JSS(node_writes_duration_us); // out: GetCounts JSS(node_write_retries); // out: GetCounts JSS(node_writes_delayed); // out::GetCounts JSS(nth); // out: RPC server_definitions +JSS(nunl); // in: AccountObjects JSS(obligations); // out: GatewayBalances JSS(offer); // in: LedgerEntry JSS(offers); // out: NetworkOPs, AccountOffers, Subscribe diff --git a/src/ripple/rpc/impl/RPCHelpers.cpp b/src/ripple/rpc/impl/RPCHelpers.cpp index a0f92612f5..a44e8a7519 100644 --- a/src/ripple/rpc/impl/RPCHelpers.cpp +++ b/src/ripple/rpc/impl/RPCHelpers.cpp @@ -1059,12 +1059,16 @@ chooseLedgerEntryType(Json::Value const& params) std::pair result{RPC::Status::OK, ltANY}; if (params.isMember(jss::type)) { - static constexpr std::array, 29> + static constexpr std::array, 30> types{ {{jss::account, ltACCOUNT_ROOT}, {jss::amendments, ltAMENDMENTS}, + {jss::amm, ltAMM}, + {jss::bridge, ltBRIDGE}, {jss::check, ltCHECK}, + {jss::cron, ltCRON}, {jss::deposit_preauth, ltDEPOSIT_PREAUTH}, + {jss::did, ltDID}, {jss::directory, ltDIR_NODE}, {jss::escrow, ltESCROW}, {jss::emitted_txn, ltEMITTED_TXN}, @@ -1074,23 +1078,20 @@ chooseLedgerEntryType(Json::Value const& params) {jss::fee, ltFEE_SETTINGS}, {jss::hashes, ltLEDGER_HASHES}, {jss::import_vlseq, ltIMPORT_VLSEQ}, + {jss::nunl, ltNEGATIVE_UNL}, + {jss::oracle, ltORACLE}, + {jss::nft_offer, ltNFTOKEN_OFFER}, + {jss::nft_page, ltNFTOKEN_PAGE}, {jss::offer, ltOFFER}, {jss::payment_channel, ltPAYCHAN}, {jss::uri_token, ltURI_TOKEN}, {jss::signer_list, ltSIGNER_LIST}, {jss::state, ltRIPPLE_STATE}, {jss::ticket, ltTICKET}, - {jss::nft_offer, ltNFTOKEN_OFFER}, - {jss::nft_page, ltNFTOKEN_PAGE}, {jss::unl_report, ltUNL_REPORT}, - {jss::cron, ltCRON}, - {jss::amm, ltAMM}, - {jss::bridge, ltBRIDGE}, {jss::xchain_owned_claim_id, ltXCHAIN_OWNED_CLAIM_ID}, {jss::xchain_owned_create_account_claim_id, - ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}, - {jss::did, ltDID}, - {jss::oracle, ltORACLE}}}; + ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}}}; auto const& p = params[jss::type]; if (!p.isString()) diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index ff7230ff3a..e5fdbb5b96 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -2897,6 +2897,12 @@ public: { // This test focuses on which gaps in queued transactions are // allowed to be filled even when the account's queue is full. + + // NOTE: This test is fragile and dependent on ordering of + // transactions, which is affected by the closed/validated + // ledger hash. This test may need to be edited if changes + // are made that impact the ledger hash. + // TODO: future-proof this test. using namespace jtx; testcase("full queue gap handling"); @@ -3038,17 +3044,9 @@ public: // may not reduce to 8. env.close(); checkMetrics(__LINE__, env, 9, 50, 6, 5, 256); + BEAST_EXPECT(env.seq(alice) == aliceSeq + 14); - // env.seq(alice): 17, aliceSeq + 15: 18 - /*std::cout - << "env.seq(alice): " - << env.seq(alice) - << ", aliceSeq + 15: " - << (aliceSeq + 15) << "\n"; - */ - BEAST_EXPECT(env.seq(alice) == aliceSeq + 15); - - // Close ledger 7. That should remove 7 more of alice's transactions. + // Close ledger 7. That should remove 4 more of alice's transactions. env.close(); checkMetrics(__LINE__, env, 2, 60, 7, 6, 256); BEAST_EXPECT(env.seq(alice) == aliceSeq + 19); diff --git a/src/test/ledger/Directory_test.cpp b/src/test/ledger/Directory_test.cpp index 8274cecd23..42dcdab9bd 100644 --- a/src/test/ledger/Directory_test.cpp +++ b/src/test/ledger/Directory_test.cpp @@ -396,6 +396,96 @@ struct Directory_test : public beast::unit_test::suite } } + void + testPreviousTxnID() + { + testcase("fixPreviousTxnID"); + using namespace jtx; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const USD = gw["USD"]; + + auto ledger_data = [this](Env& env) { + Json::Value params; + params[jss::type] = jss::directory; + params[jss::ledger_index] = "validated"; + auto const result = + env.rpc("json", "ledger_data", to_string(params))[jss::result]; + BEAST_EXPECT(!result.isMember(jss::marker)); + return result; + }; + + // fixPreviousTxnID is disabled. + Env env(*this, supported_amendments() - fixPreviousTxnID); + env.fund(XRP(10000), alice, gw); + env.close(); + env.trust(USD(1000), alice); + env(pay(gw, alice, USD(1000))); + env.close(); + + { + auto const jrr = ledger_data(env); + auto const& jstate = jrr[jss::state]; + BEAST_EXPECTS(checkArraySize(jstate, 2), jrr.toStyledString()); + for (auto const& directory : jstate) + { + BEAST_EXPECT( + directory["LedgerEntryType"] == + jss::DirectoryNode); // sanity check + // The PreviousTxnID and PreviousTxnLgrSeq fields should not be + // on the DirectoryNode object when the amendment is disabled + BEAST_EXPECT(!directory.isMember("PreviousTxnID")); + BEAST_EXPECT(!directory.isMember("PreviousTxnLgrSeq")); + } + } + + // Now enable the amendment so the directory node is updated. + env.enableFeature(fixPreviousTxnID); + env.close(); + + // Make sure the `PreviousTxnID` and `PreviousTxnLgrSeq` fields now + // exist + env(offer(alice, XRP(1), USD(1))); + auto const txID = to_string(env.tx()->getTransactionID()); + auto const ledgerSeq = env.current()->info().seq; + env.close(); + // Make sure the fields only exist if the object is touched + env(noop(gw)); + env.close(); + + { + auto const jrr = ledger_data(env); + auto const& jstate = jrr[jss::state]; + BEAST_EXPECTS(checkArraySize(jstate, 3), jrr.toStyledString()); + for (auto const& directory : jstate) + { + BEAST_EXPECT( + directory["LedgerEntryType"] == + jss::DirectoryNode); // sanity check + if (directory[jss::Owner] == gw.human()) + { + // gw's directory did not get touched, so it + // should not have those fields populated + BEAST_EXPECT(!directory.isMember("PreviousTxnID")); + BEAST_EXPECT(!directory.isMember("PreviousTxnLgrSeq")); + } + else + { + // All of the other directories, including the order + // book, did get touched, so they should have those + // fields + BEAST_EXPECT( + directory.isMember("PreviousTxnID") && + directory["PreviousTxnID"].asString() == txID); + BEAST_EXPECT( + directory.isMember("PreviousTxnLgrSeq") && + directory["PreviousTxnLgrSeq"].asUInt() == ledgerSeq); + } + } + } + } + void run() override { @@ -403,6 +493,7 @@ struct Directory_test : public beast::unit_test::suite testDirIsEmpty(); testRipd1353(); testEmptyChain(); + testPreviousTxnID(); } }; diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index 6fac0a9024..107b06d8af 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -163,8 +163,8 @@ class AccountTx_test : public beast::unit_test::suite (payment[jss::validated] == true) && (payment[jss::ledger_index] == 3) && (payment[jss::ledger_hash] == - "6A55EEBF003EB79C1BF0B8FF75CBF448161F60E3366808820" - "06903B7CC077EF2") && + "AE2BCAFE1CCE0C42F4EE1C0DED6E2735B2F0E26B0EF63917D" + "BAA85E7DB6FDA3F") && (payment[jss::close_time_iso] == "2000-01-01T00:00:10Z"); } diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index 09866b2b9b..2c5c40c8ec 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -349,8 +349,8 @@ public: == "2000-01-01T00:00:10Z" && jv[jss::validated] == true && // jv[jss::ledger_hash] == - "6E25C92F6F3CB01DC53E61A1F34DEFE16E7E1B70BFE8FDA5A48265D708" - "7F3F12" && // + "F9E3B2399D4C6C792E26ADB6DC2E0E816475021518BE00CE35E561A48F" + "56CA76" && // !jv[jss::inLedger] && jv[jss::ledger_index] == 3 && // jv[jss::tx_json][jss::TransactionType] // diff --git a/src/test/rpc/TransactionEntry_test.cpp b/src/test/rpc/TransactionEntry_test.cpp index f99aa19000..6dbc814e31 100644 --- a/src/test/rpc/TransactionEntry_test.cpp +++ b/src/test/rpc/TransactionEntry_test.cpp @@ -279,7 +279,7 @@ class TransactionEntry_test : public beast::unit_test::suite "TransactionType" : "AccountSet", "TxnSignature" : "3044022007B35E3B99460534FF6BC3A66FBBA03591C355CC38E38588968E87CCD01BE229022071A443026DE45041B55ABB1CC76812A87EA701E475BBB7E165513B4B242D3474", })", - "9E73E138359D4EB17B7F51F5F910E27F7920E9C50713E6D805A8AA8A87F9A43B", + "90880BD8C896B4F12B864D1459B9D1FBBC130F56CDCDFE61059F0E3C9DF7472A", "2000-01-01T00:00:10Z"); check_tx( env.closed()->seq(), @@ -293,7 +293,7 @@ class TransactionEntry_test : public beast::unit_test::suite "TransactionType" : "AccountSet", "TxnSignature" : "3045022100C8857FC0759A2AC0D2F320684691A66EAD252EAED9EF88C79791BC58BFCC9D860220421722286487DD0ED6BBA626CE6FCBDD14289F7F4726870C3465A4054C2702D7", })", - "9E73E138359D4EB17B7F51F5F910E27F7920E9C50713E6D805A8AA8A87F9A43B", + "90880BD8C896B4F12B864D1459B9D1FBBC130F56CDCDFE61059F0E3C9DF7472A", "2000-01-01T00:00:10Z"); env.trust(A2["USD"](1000), A1); @@ -328,7 +328,7 @@ class TransactionEntry_test : public beast::unit_test::suite "TransactionType" : "Payment", "TxnSignature" : "3044022033D9EBF7F02950AF2F6B13C07AEE641C8FEBDD540A338FCB9027A965A4AED35B02206E4E227DCC226A3456C0FEF953449D21645A24EB63CA0BB7C5B62470147FD1D1", })", - "670A61F42C747402BB7AED07FF08127E542553513D924144241C0EA844605AE8", + "9CD96249E8A003F29F0DEF971F40C7E43559EBBB634A06B53AE27E281BD76285", "2000-01-01T00:00:20Z"); check_tx( @@ -350,7 +350,7 @@ class TransactionEntry_test : public beast::unit_test::suite "TransactionType" : "Payment", "TxnSignature" : "30450221008A722B7F16EDB2348886E88ED4EC682AE9973CC1EE0FF37C93BB2CEC821D3EDF022059E464472031BA5E0D88A93E944B6A8B8DB3E1D5E5D1399A805F615789DB0BED", })", - "670A61F42C747402BB7AED07FF08127E542553513D924144241C0EA844605AE8", + "9CD96249E8A003F29F0DEF971F40C7E43559EBBB634A06B53AE27E281BD76285", "2000-01-01T00:00:20Z"); env(offer(A2, XRP(100), A2["USD"](1))); @@ -379,7 +379,7 @@ class TransactionEntry_test : public beast::unit_test::suite "TransactionType" : "OfferCreate", "TxnSignature" : "304502210093FC93ACB77B4E3DE3315441BD010096734859080C1797AB735EB47EBD541BD102205020BB1A7C3B4141279EE4C287C13671E2450EA78914EFD0C6DB2A18344CD4F2", })", - "3D1139D4A7659F3D01293BE0A1E2683FF4D11DF34AA4E30307EA6E1C2ACA5A63", + "E1F6FC9A5015672642D6D3C00C52B801020F39C534E6D244977D1D4E4809E75C", "2000-01-01T00:00:30Z"); } diff --git a/src/test/rpc/Transaction_test.cpp b/src/test/rpc/Transaction_test.cpp index 14f6b4b854..4125202225 100644 --- a/src/test/rpc/Transaction_test.cpp +++ b/src/test/rpc/Transaction_test.cpp @@ -779,8 +779,8 @@ class Transaction_test : public beast::unit_test::suite BEAST_EXPECT(result[jss::result][jss::ledger_index] == 4); BEAST_EXPECT( result[jss::result][jss::ledger_hash] == - "4F979CA2B0C506BC0244B115CA5104E81BD8BB0C4F6496AC8796A3E009B258" - "05"); + "BFA42423AA6DB8327D6B75672CF3D811B42AA3175A6AB1EE37ED4BD7E6954D" + "7A"); } for (auto memberIt = expected.begin(); memberIt != expected.end(); @@ -855,8 +855,8 @@ class Transaction_test : public beast::unit_test::suite result[jss::result][jss::meta_blob] == expected_meta_blob); BEAST_EXPECT( result[jss::result][jss::ledger_hash] == - "BC720494ECE70EF8EF078043E71845D9BAC4D1C079181127AF309CC72B" - "ABB1EA"); + "C8DC083CF39BA9B03760263440EF236ACE90BD60311E819EF7F3B337F2" + "6FB1B0"); BEAST_EXPECT( result[jss::result][jss::close_time_iso] == "2000-01-01T00:00:10Z");