From fdbb24d8987bb917feef79476501b78a1b9d2cb1 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Wed, 4 Dec 2024 15:33:50 -0500 Subject: [PATCH] refactor: clean up `LedgerEntry.cpp` (#5199) Refactors LedgerEntry to make it easier to read and understand. --- API-CHANGELOG.md | 14 +- src/libxrpl/protocol/TxMeta.cpp | 4 +- src/test/rpc/LedgerRPC_test.cpp | 338 +-- src/xrpld/app/hook/applyHook.h | 4 +- src/xrpld/app/ledger/Ledger.cpp | 4 +- src/xrpld/app/ledger/detail/BuildLedger.cpp | 4 +- src/xrpld/app/ledger/detail/InboundLedger.cpp | 12 +- src/xrpld/app/main/Application.cpp | 16 +- src/xrpld/app/rdb/backend/RWDBDatabase.h | 10 +- src/xrpld/app/tx/detail/CreateOffer.cpp | 5 +- src/xrpld/app/tx/detail/PayChan.cpp | 8 +- src/xrpld/app/tx/detail/SetHook.cpp | 3 +- src/xrpld/app/tx/detail/SetSignerList.cpp | 8 +- src/xrpld/app/tx/detail/Transactor.cpp | 27 +- src/xrpld/ledger/detail/ApplyStateTable.cpp | 19 +- src/xrpld/ledger/detail/ApplyViewBase.cpp | 5 +- src/xrpld/ledger/detail/View.cpp | 11 +- src/xrpld/net/detail/RPCCall.cpp | 18 +- src/xrpld/overlay/detail/PeerImp.cpp | 8 +- src/xrpld/rpc/detail/RPCHelpers.cpp | 12 +- src/xrpld/rpc/handlers/LedgerEntry.cpp | 1968 +++++++++-------- 21 files changed, 1363 insertions(+), 1135 deletions(-) diff --git a/API-CHANGELOG.md b/API-CHANGELOG.md index 1c0595b00..b3691d5dd 100644 --- a/API-CHANGELOG.md +++ b/API-CHANGELOG.md @@ -83,11 +83,13 @@ The [commandline](https://xrpl.org/docs/references/http-websocket-apis/api-conve The `network_id` field was added in the `server_info` response in version 1.5.0 (2019), but it is not returned in [reporting mode](https://xrpl.org/rippled-server-modes.html#reporting-mode). However, use of reporting mode is now discouraged, in favor of using [Clio](https://github.com/XRPLF/clio) instead. -## XRP Ledger server version 2.2.0 +## XRP Ledger server version 2.4.0 -The following is a non-breaking addition to the API. +### Addition in 2.4 -- The `feature` method now has a non-admin mode for users. (It was previously only available to admin connections.) The method returns an updated list of amendments, including their names and other information. ([#4781](https://github.com/XRPLF/rippled/pull/4781)) +- `ledger_entry`: `state` is added an alias for `ripple_state`. + +## XRP Ledger server version 2.3.0 ### Breaking change in 2.3 @@ -105,6 +107,12 @@ The following additions are non-breaking (because they are purely additive). - In `Payment` transactions, `DeliverMax` has been added. This is a replacement for the `Amount` field, which should not be used. Typically, the `delivered_amount` (in transaction metadata) should be used. To ease the transition, `DeliverMax` is present regardless of API version, since adding a field is non-breaking. - API version 2 has been moved from beta to supported, meaning that it is generally available (regardless of the `beta_rpc_api` setting). +## XRP Ledger server version 2.2.0 + +The following is a non-breaking addition to the API. + +- The `feature` method now has a non-admin mode for users. (It was previously only available to admin connections.) The method returns an updated list of amendments, including their names and other information. ([#4781](https://github.com/XRPLF/rippled/pull/4781)) + ## XRP Ledger server version 1.12.0 [Version 1.12.0](https://github.com/XRPLF/rippled/releases/tag/1.12.0) was released on Sep 6, 2023. The following additions are non-breaking (because they are purely additive). diff --git a/src/libxrpl/protocol/TxMeta.cpp b/src/libxrpl/protocol/TxMeta.cpp index 22576c4e0..77e5dafec 100644 --- a/src/libxrpl/protocol/TxMeta.cpp +++ b/src/libxrpl/protocol/TxMeta.cpp @@ -254,8 +254,8 @@ TxMeta::addRaw(Serializer& s, TER result, std::uint32_t index) mResult = TERtoInt(result); mIndex = index; ASSERT( - (mResult == 0 || mResult == 1) || - ((mResult > 100) && (mResult <= 255)),"ripple::TxMeta::addRaw : valid TER input"); + (mResult == 0 || mResult == 1) || ((mResult > 100) && (mResult <= 255)), + "ripple::TxMeta::addRaw : valid TER input"); mNodes.sort([](STObject const& o1, STObject const& o2) { return o1.getFieldH256(sfLedgerIndex) < o2.getFieldH256(sfLedgerIndex); diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index 27c12d650..3be0bc863 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -2323,160 +2323,164 @@ public: env(pay(gw, alice, USD(97))); env.close(); - std::string const ledgerHash{to_string(env.closed()->info().hash)}; + // check both aliases + for (auto const& fieldName : {jss::ripple_state, jss::state}) { - // Request the trust line using the accounts and currency. - Json::Value jvParams; - jvParams[jss::ripple_state] = Json::objectValue; - jvParams[jss::ripple_state][jss::accounts] = Json::arrayValue; - jvParams[jss::ripple_state][jss::accounts][0u] = alice.human(); - jvParams[jss::ripple_state][jss::accounts][1u] = gw.human(); - jvParams[jss::ripple_state][jss::currency] = "USD"; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT( - jrr[jss::node][sfBalance.jsonName][jss::value] == "-97"); - BEAST_EXPECT( - jrr[jss::node][sfHighLimit.jsonName][jss::value] == "999"); - } - { - // ripple_state is not an object. - Json::Value jvParams; - jvParams[jss::ripple_state] = "ripple_state"; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // ripple_state.currency is missing. - Json::Value jvParams; - jvParams[jss::ripple_state] = Json::objectValue; - jvParams[jss::ripple_state][jss::accounts] = Json::arrayValue; - jvParams[jss::ripple_state][jss::accounts][0u] = alice.human(); - jvParams[jss::ripple_state][jss::accounts][1u] = gw.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // ripple_state accounts is not an array. - Json::Value jvParams; - jvParams[jss::ripple_state] = Json::objectValue; - jvParams[jss::ripple_state][jss::accounts] = 2; - jvParams[jss::ripple_state][jss::currency] = "USD"; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // ripple_state one of the accounts is missing. - Json::Value jvParams; - jvParams[jss::ripple_state] = Json::objectValue; - jvParams[jss::ripple_state][jss::accounts] = Json::arrayValue; - jvParams[jss::ripple_state][jss::accounts][0u] = alice.human(); - jvParams[jss::ripple_state][jss::currency] = "USD"; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // ripple_state more than 2 accounts. - Json::Value jvParams; - jvParams[jss::ripple_state] = Json::objectValue; - jvParams[jss::ripple_state][jss::accounts] = Json::arrayValue; - jvParams[jss::ripple_state][jss::accounts][0u] = alice.human(); - jvParams[jss::ripple_state][jss::accounts][1u] = gw.human(); - jvParams[jss::ripple_state][jss::accounts][2u] = alice.human(); - jvParams[jss::ripple_state][jss::currency] = "USD"; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // ripple_state account[0] is not a string. - Json::Value jvParams; - jvParams[jss::ripple_state] = Json::objectValue; - jvParams[jss::ripple_state][jss::accounts] = Json::arrayValue; - jvParams[jss::ripple_state][jss::accounts][0u] = 44; - jvParams[jss::ripple_state][jss::accounts][1u] = gw.human(); - jvParams[jss::ripple_state][jss::currency] = "USD"; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // ripple_state account[1] is not a string. - Json::Value jvParams; - jvParams[jss::ripple_state] = Json::objectValue; - jvParams[jss::ripple_state][jss::accounts] = Json::arrayValue; - jvParams[jss::ripple_state][jss::accounts][0u] = alice.human(); - jvParams[jss::ripple_state][jss::accounts][1u] = 21; - jvParams[jss::ripple_state][jss::currency] = "USD"; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // ripple_state account[0] == account[1]. - Json::Value jvParams; - jvParams[jss::ripple_state] = Json::objectValue; - jvParams[jss::ripple_state][jss::accounts] = Json::arrayValue; - jvParams[jss::ripple_state][jss::accounts][0u] = alice.human(); - jvParams[jss::ripple_state][jss::accounts][1u] = alice.human(); - jvParams[jss::ripple_state][jss::currency] = "USD"; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // ripple_state malformed account[0]. - Json::Value jvParams; - jvParams[jss::ripple_state] = Json::objectValue; - jvParams[jss::ripple_state][jss::accounts] = Json::arrayValue; - jvParams[jss::ripple_state][jss::accounts][0u] = - makeBadAddress(alice.human()); - jvParams[jss::ripple_state][jss::accounts][1u] = gw.human(); - jvParams[jss::ripple_state][jss::currency] = "USD"; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedAddress", ""); - } - { - // ripple_state malformed account[1]. - Json::Value jvParams; - jvParams[jss::ripple_state] = Json::objectValue; - jvParams[jss::ripple_state][jss::accounts] = Json::arrayValue; - jvParams[jss::ripple_state][jss::accounts][0u] = alice.human(); - jvParams[jss::ripple_state][jss::accounts][1u] = - makeBadAddress(gw.human()); - jvParams[jss::ripple_state][jss::currency] = "USD"; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedAddress", ""); - } - { - // ripple_state malformed currency. - Json::Value jvParams; - jvParams[jss::ripple_state] = Json::objectValue; - jvParams[jss::ripple_state][jss::accounts] = Json::arrayValue; - jvParams[jss::ripple_state][jss::accounts][0u] = alice.human(); - jvParams[jss::ripple_state][jss::accounts][1u] = gw.human(); - jvParams[jss::ripple_state][jss::currency] = "USDollars"; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedCurrency", ""); + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + { + // Request the trust line using the accounts and currency. + Json::Value jvParams; + jvParams[fieldName] = Json::objectValue; + jvParams[fieldName][jss::accounts] = Json::arrayValue; + jvParams[fieldName][jss::accounts][0u] = alice.human(); + jvParams[fieldName][jss::accounts][1u] = gw.human(); + jvParams[fieldName][jss::currency] = "USD"; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfBalance.jsonName][jss::value] == "-97"); + BEAST_EXPECT( + jrr[jss::node][sfHighLimit.jsonName][jss::value] == "999"); + } + { + // ripple_state is not an object. + Json::Value jvParams; + jvParams[fieldName] = "ripple_state"; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // ripple_state.currency is missing. + Json::Value jvParams; + jvParams[fieldName] = Json::objectValue; + jvParams[fieldName][jss::accounts] = Json::arrayValue; + jvParams[fieldName][jss::accounts][0u] = alice.human(); + jvParams[fieldName][jss::accounts][1u] = gw.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // ripple_state accounts is not an array. + Json::Value jvParams; + jvParams[fieldName] = Json::objectValue; + jvParams[fieldName][jss::accounts] = 2; + jvParams[fieldName][jss::currency] = "USD"; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // ripple_state one of the accounts is missing. + Json::Value jvParams; + jvParams[fieldName] = Json::objectValue; + jvParams[fieldName][jss::accounts] = Json::arrayValue; + jvParams[fieldName][jss::accounts][0u] = alice.human(); + jvParams[fieldName][jss::currency] = "USD"; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // ripple_state more than 2 accounts. + Json::Value jvParams; + jvParams[fieldName] = Json::objectValue; + jvParams[fieldName][jss::accounts] = Json::arrayValue; + jvParams[fieldName][jss::accounts][0u] = alice.human(); + jvParams[fieldName][jss::accounts][1u] = gw.human(); + jvParams[fieldName][jss::accounts][2u] = alice.human(); + jvParams[fieldName][jss::currency] = "USD"; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // ripple_state account[0] is not a string. + Json::Value jvParams; + jvParams[fieldName] = Json::objectValue; + jvParams[fieldName][jss::accounts] = Json::arrayValue; + jvParams[fieldName][jss::accounts][0u] = 44; + jvParams[fieldName][jss::accounts][1u] = gw.human(); + jvParams[fieldName][jss::currency] = "USD"; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // ripple_state account[1] is not a string. + Json::Value jvParams; + jvParams[fieldName] = Json::objectValue; + jvParams[fieldName][jss::accounts] = Json::arrayValue; + jvParams[fieldName][jss::accounts][0u] = alice.human(); + jvParams[fieldName][jss::accounts][1u] = 21; + jvParams[fieldName][jss::currency] = "USD"; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // ripple_state account[0] == account[1]. + Json::Value jvParams; + jvParams[fieldName] = Json::objectValue; + jvParams[fieldName][jss::accounts] = Json::arrayValue; + jvParams[fieldName][jss::accounts][0u] = alice.human(); + jvParams[fieldName][jss::accounts][1u] = alice.human(); + jvParams[fieldName][jss::currency] = "USD"; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // ripple_state malformed account[0]. + Json::Value jvParams; + jvParams[fieldName] = Json::objectValue; + jvParams[fieldName][jss::accounts] = Json::arrayValue; + jvParams[fieldName][jss::accounts][0u] = + makeBadAddress(alice.human()); + jvParams[fieldName][jss::accounts][1u] = gw.human(); + jvParams[fieldName][jss::currency] = "USD"; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedAddress", ""); + } + { + // ripple_state malformed account[1]. + Json::Value jvParams; + jvParams[fieldName] = Json::objectValue; + jvParams[fieldName][jss::accounts] = Json::arrayValue; + jvParams[fieldName][jss::accounts][0u] = alice.human(); + jvParams[fieldName][jss::accounts][1u] = + makeBadAddress(gw.human()); + jvParams[fieldName][jss::currency] = "USD"; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedAddress", ""); + } + { + // ripple_state malformed currency. + Json::Value jvParams; + jvParams[fieldName] = Json::objectValue; + jvParams[fieldName][jss::accounts] = Json::arrayValue; + jvParams[fieldName][jss::accounts][0u] = alice.human(); + jvParams[fieldName][jss::accounts][1u] = gw.human(); + jvParams[fieldName][jss::currency] = "USDollars"; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedCurrency", ""); + } } } @@ -3694,6 +3698,33 @@ public: } } + void + testLedgerEntryCLI() + { + testcase("ledger_entry command-line"); + using namespace test::jtx; + + Env env{*this}; + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + auto const checkId = keylet::check(env.master, env.seq(env.master)); + + env(check::create(env.master, alice, XRP(100))); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + { + // Request a check. + Json::Value const jrr = + env.rpc("ledger_entry", to_string(checkId.key))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check); + BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000"); + } + } + public: void run() override @@ -3730,6 +3761,7 @@ public: testInvalidOracleLedgerEntry(); testOracleLedgerEntry(); testLedgerEntryMPT(); + testLedgerEntryCLI(); forAllApiVersions(std::bind_front( &LedgerRPC_test::testLedgerEntryInvalidParams, this)); diff --git a/src/xrpld/app/hook/applyHook.h b/src/xrpld/app/hook/applyHook.h index e3dc9982c..b7145811c 100644 --- a/src/xrpld/app/hook/applyHook.h +++ b/src/xrpld/app/hook/applyHook.h @@ -732,7 +732,9 @@ public: beast::Journal const& j) { // HookExecutor can only execute once - ASSERT(!spent, "HookExecutor::executeWasm : HookExecutor can only execute once"); + ASSERT( + !spent, + "HookExecutor::executeWasm : HookExecutor can only execute once"); spent = true; diff --git a/src/xrpld/app/ledger/Ledger.cpp b/src/xrpld/app/ledger/Ledger.cpp index fc7cd5ea5..d4cd73e05 100644 --- a/src/xrpld/app/ledger/Ledger.cpp +++ b/src/xrpld/app/ledger/Ledger.cpp @@ -1136,7 +1136,9 @@ finishLoadByIndexOrHash( if (!ledger) return; - ASSERT(ledger->read(keylet::fees()), "ripple::finishLoadByIndexOrHash : valid ledger fees"); + ASSERT( + ledger->read(keylet::fees()), + "ripple::finishLoadByIndexOrHash : valid ledger fees"); ledger->setImmutable(); JLOG(j.trace()) << "Loaded ledger: " << to_string(ledger->info().hash); diff --git a/src/xrpld/app/ledger/detail/BuildLedger.cpp b/src/xrpld/app/ledger/detail/BuildLedger.cpp index d43cdc38b..bf4743b45 100644 --- a/src/xrpld/app/ledger/detail/BuildLedger.cpp +++ b/src/xrpld/app/ledger/detail/BuildLedger.cpp @@ -75,7 +75,9 @@ buildLedgerImpl( built->unshare(); // Accept ledger - ASSERT(built->read(keylet::fees()), "ripple::buildLedgerImpl : valid ledger fees"); + ASSERT( + built->read(keylet::fees()), + "ripple::buildLedgerImpl : valid ledger fees"); built->setAccepted(closeTime, closeResolution, closeTimeCorrect); return built; diff --git a/src/xrpld/app/ledger/detail/InboundLedger.cpp b/src/xrpld/app/ledger/detail/InboundLedger.cpp index de35f9045..424829dc2 100644 --- a/src/xrpld/app/ledger/detail/InboundLedger.cpp +++ b/src/xrpld/app/ledger/detail/InboundLedger.cpp @@ -120,7 +120,9 @@ InboundLedger::init(ScopedLockType& collectionLock) JLOG(journal_.debug()) << "Acquiring ledger we already have in " << " local store. " << hash_; - ASSERT(mLedger->read(keylet::fees()), "ripple::InboundLedger::init : valid ledger fees"); + ASSERT( + mLedger->read(keylet::fees()), + "ripple::InboundLedger::init : valid ledger fees"); mLedger->setImmutable(); if (mReason == Reason::HISTORY) @@ -349,7 +351,9 @@ InboundLedger::tryDB(NodeStore::Database& srcDB) { JLOG(journal_.debug()) << "Had everything locally"; complete_ = true; - ASSERT(mLedger->read(keylet::fees()), "ripple::InboundLedger::tryDB : valid ledger fees"); + ASSERT( + mLedger->read(keylet::fees()), + "ripple::InboundLedger::tryDB : valid ledger fees"); mLedger->setImmutable(); } } @@ -449,7 +453,9 @@ InboundLedger::done() if (complete_ && !failed_ && mLedger) { - ASSERT(mLedger->read(keylet::fees()), "ripple::InboundLedger::done : valid ledger fees"); + ASSERT( + mLedger->read(keylet::fees()), + "ripple::InboundLedger::done : valid ledger fees"); mLedger->setImmutable(); switch (mReason) { diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index 1a3eabb3e..620074cd5 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -1733,7 +1733,9 @@ ApplicationImp::startGenesisLedger() auto const next = std::make_shared(*genesis, timeKeeper().closeTime()); next->updateSkipList(); - ASSERT(next->read(keylet::fees()), "ripple::ApplicationImp::startGenesisLedger : valid ledger fees"); + ASSERT( + next->read(keylet::fees()), + "ripple::ApplicationImp::startGenesisLedger : valid ledger fees"); next->setImmutable(); openLedger_.emplace(next, cachedSLEs_, logs_->journal("OpenLedger")); m_ledgerMaster->storeLedger(next); @@ -1781,7 +1783,9 @@ ApplicationImp::getLastFullLedger() if (!ledger) return ledger; - ASSERT(ledger->read(keylet::fees()), "ripple::ApplicationImp::getLastFullLedger : valid ledger fees"); + ASSERT( + ledger->read(keylet::fees()), + "ripple::ApplicationImp::getLastFullLedger : valid ledger fees"); ledger->setImmutable(); if (getLedgerMaster().haveLedger(seq)) @@ -1933,7 +1937,9 @@ ApplicationImp::loadLedgerFromFile(std::string const& name) loadLedger->stateMap().flushDirty(hotACCOUNT_NODE); - ASSERT(loadLedger->read(keylet::fees()), "ripple::ApplicationImp::loadLedgerFromFile : valid ledger fees"); + ASSERT( + loadLedger->read(keylet::fees()), + "ripple::ApplicationImp::loadLedgerFromFile : valid ledger fees"); loadLedger->setAccepted( closeTime, closeTimeResolution, !closeTimeEstimated); @@ -2061,7 +2067,9 @@ ApplicationImp::loadLedgerFromJson(std::string const& jsonValue) loadLedger->stateMap().flushDirty(hotACCOUNT_NODE); - ASSERT(loadLedger->read(keylet::fees()), "ripple::ApplicationImp::loadLedgerFromFile : valid ledger fees"); + ASSERT( + loadLedger->read(keylet::fees()), + "ripple::ApplicationImp::loadLedgerFromFile : valid ledger fees"); loadLedger->setAccepted( closeTime, closeTimeResolution, !closeTimeEstimated); diff --git a/src/xrpld/app/rdb/backend/RWDBDatabase.h b/src/xrpld/app/rdb/backend/RWDBDatabase.h index 592a611df..bba9d9698 100644 --- a/src/xrpld/app/rdb/backend/RWDBDatabase.h +++ b/src/xrpld/app/rdb/backend/RWDBDatabase.h @@ -225,7 +225,8 @@ public: if (!ledger->info().accountHash.isNonZero()) { JLOG(j.fatal()) << "AH is zero: " << getJson({*ledger, {}}); - UNREACHABLE("RWDBDatabase::saveValidatedLedger : account hash is zero"); + UNREACHABLE( + "RWDBDatabase::saveValidatedLedger : account hash is zero"); } if (ledger->info().accountHash != @@ -235,10 +236,13 @@ public: << " != " << ledger->stateMap().getHash(); JLOG(j.fatal()) << "saveAcceptedLedger: seq=" << seq << ", current=" << current; - UNREACHABLE("RWDBDatabase::saveValidatedLedger : account hash mismatch"); + UNREACHABLE( + "RWDBDatabase::saveValidatedLedger : account hash mismatch"); } - ASSERT(ledger->info().txHash == ledger->txMap().getHash().as_uint256(), "RWDBDatabase::saveValidatedLedger : tx hash mismatch"); + ASSERT( + ledger->info().txHash == ledger->txMap().getHash().as_uint256(), + "RWDBDatabase::saveValidatedLedger : tx hash mismatch"); // Save the ledger header in the hashed object store { diff --git a/src/xrpld/app/tx/detail/CreateOffer.cpp b/src/xrpld/app/tx/detail/CreateOffer.cpp index ccbac3bf2..a8d103f28 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.cpp +++ b/src/xrpld/app/tx/detail/CreateOffer.cpp @@ -1087,7 +1087,10 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) // We expect the implementation of cross to succeed // or give a tec. - ASSERT(isTesSuccess(result) || isTecClaim(result), "ripple::CreateOffer::applyGuts : result is tesSUCCESS or tecCLAIM"); + ASSERT( + isTesSuccess(result) || isTecClaim(result), + "ripple::CreateOffer::applyGuts : result is tesSUCCESS or " + "tecCLAIM"); if (auto stream = j_.trace()) { diff --git a/src/xrpld/app/tx/detail/PayChan.cpp b/src/xrpld/app/tx/detail/PayChan.cpp index 256540a91..30c5a75ea 100644 --- a/src/xrpld/app/tx/detail/PayChan.cpp +++ b/src/xrpld/app/tx/detail/PayChan.cpp @@ -173,7 +173,9 @@ closeChannel( if (!sle) return tefINTERNAL; - ASSERT((*slep)[sfAmount] >= (*slep)[sfBalance], "ripple::closeChannel : minimum channel amount"); + ASSERT( + (*slep)[sfAmount] >= (*slep)[sfBalance], + "ripple::closeChannel : minimum channel amount"); if (isXRP(amount)) (*sle)[sfBalance] = (*sle)[sfBalance] + amount; @@ -773,7 +775,9 @@ PayChanClaim::doApply() (*slep)[sfBalance] = ctx_.tx[sfBalance]; STAmount const reqDelta = reqBalance - chanBalance; - ASSERT(reqDelta >= beast::zero, "ripple::PayChanClaim::doApply : minimum balance delta"); + ASSERT( + reqDelta >= beast::zero, + "ripple::PayChanClaim::doApply : minimum balance delta"); if (isXRP(reqDelta)) (*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta; else diff --git a/src/xrpld/app/tx/detail/SetHook.cpp b/src/xrpld/app/tx/detail/SetHook.cpp index 38d1f9570..81a90bc16 100644 --- a/src/xrpld/app/tx/detail/SetHook.cpp +++ b/src/xrpld/app/tx/detail/SetHook.cpp @@ -1604,7 +1604,8 @@ SetHook::setHook() return tecINTERNAL; } else - UNREACHABLE("SetHook::hsoCREATE : should never happen"); + UNREACHABLE( + "SetHook::hsoCREATE : should never happen"); } // otherwise assign instruction counts diff --git a/src/xrpld/app/tx/detail/SetSignerList.cpp b/src/xrpld/app/tx/detail/SetSignerList.cpp index af16d0cab..88986c2b9 100644 --- a/src/xrpld/app/tx/detail/SetSignerList.cpp +++ b/src/xrpld/app/tx/detail/SetSignerList.cpp @@ -137,8 +137,12 @@ SetSignerList::preCompute() { // Get the quorum and operation info. auto result = determineOperation(ctx_.tx, view().flags(), j_); - ASSERT(isTesSuccess(std::get<0>(result)), "ripple::SetSignerList::preCompute : result is tesSUCCESS"); - ASSERT(std::get<3>(result) != unknown, "ripple::SetSignerList::preCompute : result is known operation"); + ASSERT( + isTesSuccess(std::get<0>(result)), + "ripple::SetSignerList::preCompute : result is tesSUCCESS"); + ASSERT( + std::get<3>(result) != unknown, + "ripple::SetSignerList::preCompute : result is known operation"); quorum_ = std::get<1>(result); signers_ = std::get<2>(result); diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 87c364f35..13c1b4871 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -330,7 +330,9 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) hookExecutionFee += toAdd; } - ASSERT(emitDetails.isFieldPresent(sfEmitBurden), "Transactor::calculateBaseFee : emit burden not present"); + ASSERT( + emitDetails.isFieldPresent(sfEmitBurden), + "Transactor::calculateBaseFee : emit burden not present"); burden = emitDetails.getFieldU64(sfEmitBurden); } @@ -692,7 +694,8 @@ Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx) TER Transactor::consumeSeqProxy(SLE::pointer const& sleAccount) { - ASSERT(sleAccount, "ripple::Transactor::consumeSeqProxy : non-null account"); + ASSERT( + sleAccount, "ripple::Transactor::consumeSeqProxy : non-null account"); // do not update sequence of sfAccountTxnID for emitted tx if (ctx_.isEmittedTxn()) @@ -787,9 +790,10 @@ Transactor::apply() // that allow zero account. (and ttIMPORT) ASSERT( sle != nullptr || account_ == beast::zero || - view().rules().enabled(featureImport) && - ctx_.tx.getTxnType() == ttIMPORT && - !ctx_.tx.isFieldPresent(sfIssuer), "ripple::Transactor::apply : non-null SLE or zero account"); + view().rules().enabled(featureImport) && + ctx_.tx.getTxnType() == ttIMPORT && + !ctx_.tx.isFieldPresent(sfIssuer), + "ripple::Transactor::apply : non-null SLE or zero account"); if (sle) { @@ -1616,7 +1620,9 @@ Transactor::doTSH( if (tshFeeDrops == 0) continue; - ASSERT(tshFeeDrops >= beast::zero, "Transactor::doTSH : tsh fee drops is negative"); + ASSERT( + tshFeeDrops >= beast::zero, + "Transactor::doTSH : tsh fee drops is negative"); STAmount priorBalance = tshAcc->getFieldAmount(sfBalance); @@ -1657,8 +1663,13 @@ Transactor::doTSH( if (tshFeeDrops > beast::zero) { STAmount finalBalance = priorBalance - tshFeeDrops; - ASSERT(finalBalance >= beast::zero, "Transactor::doTSH : final balance is negative"); - ASSERT(finalBalance < priorBalance, "Transactor::doTSH : final balance is greater than prior balance"); + ASSERT( + finalBalance >= beast::zero, + "Transactor::doTSH : final balance is negative"); + ASSERT( + finalBalance < priorBalance, + "Transactor::doTSH : final balance is greater than prior " + "balance"); tshAcc->setFieldAmount(sfBalance, finalBalance); view.update(tshAcc); diff --git a/src/xrpld/ledger/detail/ApplyStateTable.cpp b/src/xrpld/ledger/detail/ApplyStateTable.cpp index 224ac048e..009c994b4 100644 --- a/src/xrpld/ledger/detail/ApplyStateTable.cpp +++ b/src/xrpld/ledger/detail/ApplyStateTable.cpp @@ -161,7 +161,10 @@ ApplyStateTable::generateTxMeta( meta.setAffectedNode(item.first, *type, nodeType); if (type == &sfDeletedNode) { - ASSERT(origNode && curNode, "ripple::detail::ApplyStateTable::apply : valid nodes for deletion"); + ASSERT( + origNode && curNode, + "ripple::detail::ApplyStateTable::apply : valid nodes for " + "deletion"); threadOwners(to, meta, origNode, newMod, j); STObject prevs(sfPreviousFields); @@ -192,7 +195,10 @@ ApplyStateTable::generateTxMeta( } else if (type == &sfModifiedNode) { - ASSERT(curNode && origNode, "ripple::detail::ApplyStateTable::apply : valid nodes for modification"); + ASSERT( + curNode && origNode, + "ripple::detail::ApplyStateTable::apply : valid nodes for " + "modification"); if (curNode->isThreadedType(to.rules())) // thread transaction to // node item modified @@ -225,7 +231,10 @@ ApplyStateTable::generateTxMeta( } else if (type == &sfCreatedNode) // if created, thread to owner(s) { - ASSERT(curNode && !origNode, "ripple::detail::ApplyStateTable::apply : valid nodes for creation"); + ASSERT( + curNode && !origNode, + "ripple::detail::ApplyStateTable::apply : valid nodes for " + "creation"); threadOwners(to, meta, curNode, newMod, j); if (curNode->isThreadedType(to.rules())) // always thread to self @@ -250,7 +259,9 @@ ApplyStateTable::generateTxMeta( } else { - UNREACHABLE("ripple::detail::ApplyStateTable::apply : unsupported operation type"); + UNREACHABLE( + "ripple::detail::ApplyStateTable::apply : unsupported " + "operation type"); } } diff --git a/src/xrpld/ledger/detail/ApplyViewBase.cpp b/src/xrpld/ledger/detail/ApplyViewBase.cpp index f4c6566c7..d96bd5be8 100644 --- a/src/xrpld/ledger/detail/ApplyViewBase.cpp +++ b/src/xrpld/ledger/detail/ApplyViewBase.cpp @@ -254,7 +254,10 @@ ApplyViewBase::balanceChanges(ReadView const& view) const { // modify auto const at = after->getType(); - ASSERT(at == before->getType(), "ripple::PaymentSandbox::balanceChanges : after and before types matching"); + ASSERT( + at == before->getType(), + "ripple::PaymentSandbox::balanceChanges : after and before " + "types matching"); switch (at) { case ltACCOUNT_ROOT: diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index 3d5101ff2..ad5b9e7c5 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -1033,7 +1033,8 @@ isTrustDefault( std::shared_ptr const& acc, std::shared_ptr const& line) { - ASSERT(acc && line, "ripple::isTrustDefault : account and line are not null"); + ASSERT( + acc && line, "ripple::isTrustDefault : account and line are not null"); uint32_t const tlFlags = line->getFieldU32(sfFlags); @@ -1043,7 +1044,9 @@ isTrustDefault( AccountID const accID = acc->getAccountID(sfAccount); - ASSERT(accID == highAccID || accID == lowAccID, "ripple::isTrustDefault : account ID mismatch"); + ASSERT( + accID == highAccID || accID == lowAccID, + "ripple::isTrustDefault : account ID mismatch"); bool const high = accID == highAccID; @@ -1311,7 +1314,9 @@ rippleSendIOU( // Calculate the amount to transfer accounting // for any transfer fees: - ASSERT(waiveFee == WaiveTransferFee::No || senderPaysXferFees == true, "ripple::rippleSendIOU : transfer fees are waived or sender pays"); + ASSERT( + waiveFee == WaiveTransferFee::No || senderPaysXferFees == true, + "ripple::rippleSendIOU : transfer fees are waived or sender pays"); STAmount senderPays = saAmount; STAmount destReceives = saAmount; diff --git a/src/xrpld/net/detail/RPCCall.cpp b/src/xrpld/net/detail/RPCCall.cpp index ded0a9805..97bd02de6 100644 --- a/src/xrpld/net/detail/RPCCall.cpp +++ b/src/xrpld/net/detail/RPCCall.cpp @@ -716,6 +716,21 @@ private: return jvRequest; } + // ledger_entry [id] [] + Json::Value + parseLedgerEntry(Json::Value const& jvParams) + { + Json::Value jvRequest{Json::objectValue}; + + jvRequest[jss::index] = jvParams[0u].asString(); + + if (jvParams.size() == 2 && + !jvParseLedger(jvRequest, jvParams[1u].asString())) + return rpcError(rpcLGR_IDX_MALFORMED); + + return jvRequest; + } + // log_level: Get log levels // log_level : Set master log level to the // specified severity log_level : Set specified @@ -1404,8 +1419,7 @@ public: {"ledger_accept", &RPCParser::parseAsIs, 0, 0}, {"ledger_closed", &RPCParser::parseAsIs, 0, 0}, {"ledger_current", &RPCParser::parseAsIs, 0, 0}, - // { "ledger_entry", &RPCParser::parseLedgerEntry, - // -1, -1 }, + {"ledger_entry", &RPCParser::parseLedgerEntry, 1, 2}, {"ledger_header", &RPCParser::parseLedgerId, 1, 1}, {"ledger_request", &RPCParser::parseLedgerId, 1, 1}, {"log_level", &RPCParser::parseLogLevel, 0, 2}, diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 919483d7e..25ef87d88 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -1236,7 +1236,8 @@ PeerImp::handleTransaction( bool batch) { ASSERT( - eraseTxQueue != batch, "ripple::PeerImp::handleTransaction correct function params"); + eraseTxQueue != batch, + "ripple::PeerImp::handleTransaction correct function params"); if (tracking_.load() == Tracking::diverged) return; @@ -2818,7 +2819,10 @@ PeerImp::checkTransaction( // TransactionMaster cache std::string reason; auto tx = std::make_shared(stx, reason, app_); - ASSERT(tx->getStatus() == NEW, "ripple::PeerImp::checkTransaction Transaction created correctly"); + ASSERT( + tx->getStatus() == NEW, + "ripple::PeerImp::checkTransaction Transaction created " + "correctly"); if (tx->getStatus() == NEW) { JLOG(p_journal_.debug()) diff --git a/src/xrpld/rpc/detail/RPCHelpers.cpp b/src/xrpld/rpc/detail/RPCHelpers.cpp index c6d13fb53..e4a244df7 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCHelpers.cpp @@ -1076,22 +1076,22 @@ 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::mpt_issuance, ltMPTOKEN_ISSUANCE}, + {jss::mptoken, ltMPTOKEN}, {jss::nft_offer, ltNFTOKEN_OFFER}, {jss::nft_page, ltNFTOKEN_PAGE}, + {jss::nunl, ltNEGATIVE_UNL}, {jss::offer, ltOFFER}, + {jss::oracle, ltORACLE}, {jss::payment_channel, ltPAYCHAN}, - {jss::uri_token, ltURI_TOKEN}, {jss::signer_list, ltSIGNER_LIST}, {jss::state, ltRIPPLE_STATE}, {jss::ticket, ltTICKET}, + {jss::uri_token, ltURI_TOKEN}, {jss::unl_report, ltUNL_REPORT}, {jss::xchain_owned_claim_id, ltXCHAIN_OWNED_CLAIM_ID}, {jss::xchain_owned_create_account_claim_id, - ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}, - {jss::mpt_issuance, ltMPTOKEN_ISSUANCE}, - {jss::mptoken, ltMPTOKEN}}}; + ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}}}; auto const& p = params[jss::type]; if (!p.isString()) diff --git a/src/xrpld/rpc/handlers/LedgerEntry.cpp b/src/xrpld/rpc/handlers/LedgerEntry.cpp index 46a96d99e..dd7cc864f 100644 --- a/src/xrpld/rpc/handlers/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/LedgerEntry.cpp @@ -33,6 +33,7 @@ #include #include #include +#include namespace ripple { @@ -67,6 +68,941 @@ parseAuthorizeCredentials(Json::Value const& jv) return arr; } +std::optional +parseIndex(Json::Value const& params, Json::Value& jvResult) +{ + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(params.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + return uNodeIndex; +} + +std::optional +parseAccountRoot(Json::Value const& params, Json::Value& jvResult) +{ + auto const account = parseBase58(params.asString()); + if (!account || account->isZero()) + { + jvResult[jss::error] = "malformedAddress"; + return std::nullopt; + } + + return keylet::account(*account).key; +} + +std::optional +parseCheck(Json::Value const& params, Json::Value& jvResult) +{ + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(params.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + return uNodeIndex; +} + +std::optional +parseDepositPreauth(Json::Value const& dp, Json::Value& jvResult) +{ + if (!dp.isObject()) + { + uint256 uNodeIndex; + if (!dp.isString() || !uNodeIndex.parseHex(dp.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return uNodeIndex; + } + + // clang-format off + if ( + (!dp.isMember(jss::owner) || !dp[jss::owner].isString()) || + (dp.isMember(jss::authorized) == dp.isMember(jss::authorized_credentials)) || + (dp.isMember(jss::authorized) && !dp[jss::authorized].isString()) || + (dp.isMember(jss::authorized_credentials) && !dp[jss::authorized_credentials].isArray()) + ) + // clang-format on + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + auto const owner = parseBase58(dp[jss::owner].asString()); + if (!owner) + { + jvResult[jss::error] = "malformedOwner"; + return std::nullopt; + } + + if (dp.isMember(jss::authorized)) + { + auto const authorized = + parseBase58(dp[jss::authorized].asString()); + if (!authorized) + { + jvResult[jss::error] = "malformedAuthorized"; + return std::nullopt; + } + return keylet::depositPreauth(*owner, *authorized).key; + } + + auto const& ac(dp[jss::authorized_credentials]); + STArray const arr = parseAuthorizeCredentials(ac); + + if (arr.empty() || (arr.size() > maxCredentialsArraySize)) + { + jvResult[jss::error] = "malformedAuthorizedCredentials"; + return std::nullopt; + } + + auto const& sorted = credentials::makeSorted(arr); + if (sorted.empty()) + { + jvResult[jss::error] = "malformedAuthorizedCredentials"; + return std::nullopt; + } + + return keylet::depositPreauth(*owner, sorted).key; +} + +std::optional +parseDirectory(Json::Value const& params, Json::Value& jvResult) +{ + if (params.isNull()) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + if (!params.isObject()) + { + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(params.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return uNodeIndex; + } + + if (params.isMember(jss::sub_index) && !params[jss::sub_index].isIntegral()) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + std::uint64_t uSubIndex = + params.isMember(jss::sub_index) ? params[jss::sub_index].asUInt() : 0; + + if (params.isMember(jss::dir_root)) + { + uint256 uDirRoot; + + if (params.isMember(jss::owner)) + { + // May not specify both dir_root and owner. + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + if (!uDirRoot.parseHex(params[jss::dir_root].asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return keylet::page(uDirRoot, uSubIndex).key; + } + + if (params.isMember(jss::owner)) + { + auto const ownerID = + parseBase58(params[jss::owner].asString()); + + if (!ownerID) + { + jvResult[jss::error] = "malformedAddress"; + return std::nullopt; + } + + return keylet::page(keylet::ownerDir(*ownerID), uSubIndex).key; + } + + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; +} + +std::optional +parseEscrow(Json::Value const& params, Json::Value& jvResult) +{ + if (!params.isObject()) + { + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(params.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + return uNodeIndex; + } + + if (!params.isMember(jss::owner) || !params.isMember(jss::seq) || + !params[jss::seq].isIntegral()) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + auto const id = parseBase58(params[jss::owner].asString()); + + if (!id) + { + jvResult[jss::error] = "malformedOwner"; + return std::nullopt; + } + + return keylet::escrow(*id, params[jss::seq].asUInt()).key; +} + +std::optional +parseOffer(Json::Value const& params, Json::Value& jvResult) +{ + if (!params.isObject()) + { + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(params.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return uNodeIndex; + } + + if (!params.isMember(jss::account) || !params.isMember(jss::seq) || + !params[jss::seq].isIntegral()) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + auto const id = parseBase58(params[jss::account].asString()); + if (!id) + { + jvResult[jss::error] = "malformedAddress"; + return std::nullopt; + } + + return keylet::offer(*id, params[jss::seq].asUInt()).key; +} + +std::optional +parsePaymentChannel(Json::Value const& params, Json::Value& jvResult) +{ + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(params.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + return uNodeIndex; +} + +std::optional +parseRippleState(Json::Value const& jvRippleState, Json::Value& jvResult) +{ + Currency uCurrency; + + if (!jvRippleState.isObject() || !jvRippleState.isMember(jss::currency) || + !jvRippleState.isMember(jss::accounts) || + !jvRippleState[jss::accounts].isArray() || + 2 != jvRippleState[jss::accounts].size() || + !jvRippleState[jss::accounts][0u].isString() || + !jvRippleState[jss::accounts][1u].isString() || + (jvRippleState[jss::accounts][0u].asString() == + jvRippleState[jss::accounts][1u].asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + auto const id1 = + parseBase58(jvRippleState[jss::accounts][0u].asString()); + auto const id2 = + parseBase58(jvRippleState[jss::accounts][1u].asString()); + if (!id1 || !id2) + { + jvResult[jss::error] = "malformedAddress"; + return std::nullopt; + } + + if (!to_currency(uCurrency, jvRippleState[jss::currency].asString())) + { + jvResult[jss::error] = "malformedCurrency"; + return std::nullopt; + } + + return keylet::line(*id1, *id2, uCurrency).key; +} + +std::optional +parseTicket(Json::Value const& params, Json::Value& jvResult) +{ + if (!params.isObject()) + { + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(params.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return uNodeIndex; + } + + if (!params.isMember(jss::account) || !params.isMember(jss::ticket_seq) || + !params[jss::ticket_seq].isIntegral()) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + auto const id = parseBase58(params[jss::account].asString()); + if (!id) + { + jvResult[jss::error] = "malformedAddress"; + return std::nullopt; + } + + return getTicketIndex(*id, params[jss::ticket_seq].asUInt()); +} + +std::optional +parseNFTokenPage(Json::Value const& params, Json::Value& jvResult) +{ + if (params.isString()) + { + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(params.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return uNodeIndex; + } + + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; +} + +std::optional +parseAMM(Json::Value const& params, Json::Value& jvResult) +{ + if (!params.isObject()) + { + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(params.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return uNodeIndex; + } + + if (!params.isMember(jss::asset) || !params.isMember(jss::asset2)) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + try + { + auto const issue = issueFromJson(params[jss::asset]); + auto const issue2 = issueFromJson(params[jss::asset2]); + return keylet::amm(issue, issue2).key; + } + catch (std::runtime_error const&) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } +} + +std::optional +parseBridge(Json::Value const& params, Json::Value& jvResult) +{ + // return the keylet for the specified bridge or nullopt if the + // request is malformed + auto const maybeKeylet = [&]() -> std::optional { + try + { + if (!params.isMember(jss::bridge_account)) + return std::nullopt; + + auto const& jsBridgeAccount = params[jss::bridge_account]; + if (!jsBridgeAccount.isString()) + { + return std::nullopt; + } + + auto const account = + parseBase58(jsBridgeAccount.asString()); + if (!account || account->isZero()) + { + return std::nullopt; + } + + // This may throw and is the reason for the `try` block. The + // try block has a larger scope so the `bridge` variable + // doesn't need to be an optional. + STXChainBridge const bridge(params[jss::bridge]); + STXChainBridge::ChainType const chainType = + STXChainBridge::srcChain(account == bridge.lockingChainDoor()); + + if (account != bridge.door(chainType)) + return std::nullopt; + + return keylet::bridge(bridge, chainType); + } + catch (...) + { + return std::nullopt; + } + }(); + + if (maybeKeylet) + { + return maybeKeylet->key; + } + + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; +} + +std::optional +parseXChainOwnedClaimID(Json::Value const& claim_id, Json::Value& jvResult) +{ + if (claim_id.isString()) + { + uint256 uNodeIndex; + // we accept a node id as specifier of a xchain claim id + if (!uNodeIndex.parseHex(claim_id.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return uNodeIndex; + } + + if (!claim_id.isObject() || + !(claim_id.isMember(sfIssuingChainDoor.getJsonName()) && + claim_id[sfIssuingChainDoor.getJsonName()].isString()) || + !(claim_id.isMember(sfLockingChainDoor.getJsonName()) && + claim_id[sfLockingChainDoor.getJsonName()].isString()) || + !claim_id.isMember(sfIssuingChainIssue.getJsonName()) || + !claim_id.isMember(sfLockingChainIssue.getJsonName()) || + !claim_id.isMember(jss::xchain_owned_claim_id)) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + // if not specified with a node id, a claim_id is specified by + // four strings defining the bridge (locking_chain_door, + // locking_chain_issue, issuing_chain_door, issuing_chain_issue) + // and the claim id sequence number. + auto const lockingChainDoor = parseBase58( + claim_id[sfLockingChainDoor.getJsonName()].asString()); + auto const issuingChainDoor = parseBase58( + claim_id[sfIssuingChainDoor.getJsonName()].asString()); + Issue lockingChainIssue, issuingChainIssue; + bool valid = lockingChainDoor && issuingChainDoor; + + if (valid) + { + try + { + lockingChainIssue = + issueFromJson(claim_id[sfLockingChainIssue.getJsonName()]); + issuingChainIssue = + issueFromJson(claim_id[sfIssuingChainIssue.getJsonName()]); + } + catch (std::runtime_error const& ex) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + } + + if (valid && claim_id[jss::xchain_owned_claim_id].isIntegral()) + { + auto const seq = claim_id[jss::xchain_owned_claim_id].asUInt(); + + STXChainBridge bridge_spec( + *lockingChainDoor, + lockingChainIssue, + *issuingChainDoor, + issuingChainIssue); + Keylet keylet = keylet::xChainClaimID(bridge_spec, seq); + return keylet.key; + } + + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; +} + +std::optional +parseXChainOwnedCreateAccountClaimID( + Json::Value const& claim_id, + Json::Value& jvResult) +{ + if (claim_id.isString()) + { + uint256 uNodeIndex; + // we accept a node id as specifier of a xchain create account + // claim_id + if (!uNodeIndex.parseHex(claim_id.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return uNodeIndex; + } + + if (!claim_id.isObject() || + !(claim_id.isMember(sfIssuingChainDoor.getJsonName()) && + claim_id[sfIssuingChainDoor.getJsonName()].isString()) || + !(claim_id.isMember(sfLockingChainDoor.getJsonName()) && + claim_id[sfLockingChainDoor.getJsonName()].isString()) || + !claim_id.isMember(sfIssuingChainIssue.getJsonName()) || + !claim_id.isMember(sfLockingChainIssue.getJsonName()) || + !claim_id.isMember(jss::xchain_owned_create_account_claim_id)) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + // if not specified with a node id, a create account claim_id is + // specified by four strings defining the bridge + // (locking_chain_door, locking_chain_issue, issuing_chain_door, + // issuing_chain_issue) and the create account claim id sequence + // number. + auto const lockingChainDoor = parseBase58( + claim_id[sfLockingChainDoor.getJsonName()].asString()); + auto const issuingChainDoor = parseBase58( + claim_id[sfIssuingChainDoor.getJsonName()].asString()); + Issue lockingChainIssue, issuingChainIssue; + bool valid = lockingChainDoor && issuingChainDoor; + if (valid) + { + try + { + lockingChainIssue = + issueFromJson(claim_id[sfLockingChainIssue.getJsonName()]); + issuingChainIssue = + issueFromJson(claim_id[sfIssuingChainIssue.getJsonName()]); + } + catch (std::runtime_error const& ex) + { + valid = false; + jvResult[jss::error] = "malformedRequest"; + } + } + + if (valid && + claim_id[jss::xchain_owned_create_account_claim_id].isIntegral()) + { + auto const seq = + claim_id[jss::xchain_owned_create_account_claim_id].asUInt(); + + STXChainBridge bridge_spec( + *lockingChainDoor, + lockingChainIssue, + *issuingChainDoor, + issuingChainIssue); + Keylet keylet = keylet::xChainCreateAccountClaimID(bridge_spec, seq); + return keylet.key; + } + + return std::nullopt; +} + +std::optional +parseDID(Json::Value const& params, Json::Value& jvResult) +{ + auto const account = parseBase58(params.asString()); + if (!account || account->isZero()) + { + jvResult[jss::error] = "malformedAddress"; + return std::nullopt; + } + + return keylet::did(*account).key; +} + +std::optional +parseOracle(Json::Value const& params, Json::Value& jvResult) +{ + if (!params.isObject()) + { + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(params.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return uNodeIndex; + } + + if (!params.isMember(jss::oracle_document_id) || + !params.isMember(jss::account)) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + auto const& oracle = params; + auto const documentID = [&]() -> std::optional { + auto const id = oracle[jss::oracle_document_id]; + if (id.isUInt() || (id.isInt() && id.asInt() >= 0)) + return std::make_optional(id.asUInt()); + + if (id.isString()) + { + std::uint32_t v; + if (beast::lexicalCastChecked(v, id.asString())) + return std::make_optional(v); + } + + return std::nullopt; + }(); + + auto const account = + parseBase58(oracle[jss::account].asString()); + if (!account || account->isZero()) + { + jvResult[jss::error] = "malformedAddress"; + return std::nullopt; + } + + if (!documentID) + { + jvResult[jss::error] = "malformedDocumentID"; + return std::nullopt; + } + + return keylet::oracle(*account, *documentID).key; +} + +std::optional +parseCredential(Json::Value const& cred, Json::Value& jvResult) +{ + if (cred.isString()) + { + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(cred.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return uNodeIndex; + } + + if ((!cred.isMember(jss::subject) || !cred[jss::subject].isString()) || + (!cred.isMember(jss::issuer) || !cred[jss::issuer].isString()) || + (!cred.isMember(jss::credential_type) || + !cred[jss::credential_type].isString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + auto const subject = parseBase58(cred[jss::subject].asString()); + auto const issuer = parseBase58(cred[jss::issuer].asString()); + auto const credType = strUnHex(cred[jss::credential_type].asString()); + + if (!subject || subject->isZero() || !issuer || issuer->isZero() || + !credType || credType->empty()) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + return keylet::credential( + *subject, *issuer, Slice(credType->data(), credType->size())) + .key; +} + +std::optional +parseMPTokenIssuance( + Json::Value const& unparsedMPTIssuanceID, + Json::Value& jvResult) +{ + if (unparsedMPTIssuanceID.isString()) + { + uint192 mptIssuanceID; + if (!mptIssuanceID.parseHex(unparsedMPTIssuanceID.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + return keylet::mptIssuance(mptIssuanceID).key; + } + + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; +} + +std::optional +parseMPToken(Json::Value const& mptJson, Json::Value& jvResult) +{ + if (!mptJson.isObject()) + { + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(mptJson.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return uNodeIndex; + } + + if (!mptJson.isMember(jss::mpt_issuance_id) || + !mptJson.isMember(jss::account)) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + try + { + auto const mptIssuanceIdStr = mptJson[jss::mpt_issuance_id].asString(); + + uint192 mptIssuanceID; + if (!mptIssuanceID.parseHex(mptIssuanceIdStr)) + Throw("Cannot parse mpt_issuance_id"); + + auto const account = + parseBase58(mptJson[jss::account].asString()); + + if (!account || account->isZero()) + { + jvResult[jss::error] = "malformedAddress"; + return std::nullopt; + } + + return keylet::mptoken(mptIssuanceID, *account).key; + } + catch (std::runtime_error const&) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } +} + +std::optional +parseEmittedTxn(Json::Value const& emittedTxnJson, Json::Value& jvResult) +{ + if (!emittedTxnJson.isObject()) + { + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(emittedTxnJson.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return keylet::emittedTxn(uNodeIndex).key; + } + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; +} + +std::optional +parseHook(Json::Value const& hookJson, Json::Value& jvResult) +{ + if (!hookJson.isObject()) + { + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(hookJson.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return uNodeIndex; + } + else if (!hookJson.isMember(jss::account)) + { + jvResult[jss::error] = "malformedRequest"; + } + else + { + auto const id = + parseBase58(hookJson[jss::account].asString()); + if (!id) + { + jvResult[jss::error] = "malformedAddress"; + return std::nullopt; + } + else + return keylet::hook(*id).key; + } + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; +} + +std::optional +parseHookDefinition( + Json::Value const& hookDefinitionJson, + Json::Value& jvResult) +{ + uint256 uNodeIndex; + if (hookDefinitionJson.isObject() || + (!uNodeIndex.parseHex(hookDefinitionJson.asString()))) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + else + { + return keylet::hookDefinition(uNodeIndex).key; + } + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; +} + +std::optional +parseHookState(Json::Value const& hookStateJson, Json::Value& jvResult) +{ + uint256 uNodeKey; + uint256 uNameSpace; + + if (!hookStateJson.isObject() || !hookStateJson.isMember(jss::account) || + !hookStateJson.isMember(jss::key) || + !hookStateJson.isMember(jss::namespace_id) || + !hookStateJson[jss::account].isString() || + !hookStateJson[jss::key].isString() || + !hookStateJson[jss::namespace_id].isString()) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + else + { + auto const account = + parseBase58(hookStateJson[jss::account].asString()); + if (!account) + { + jvResult[jss::error] = "malformedAddress"; + return std::nullopt; + } + else if (!uNodeKey.parseHex(hookStateJson[jss::key].asString())) + { + jvResult[jss::error] = "malformedRequest"; + } + else if (!uNameSpace.parseHex( + hookStateJson[jss::namespace_id].asString())) + { + jvResult[jss::error] = "malformedRequest"; + } + else + { + return keylet::hookState(*account, uNodeKey, uNameSpace).key; + } + } + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; +} + +std::optional +parseImportVLseq(Json::Value const& importVLseqJson, Json::Value& jvResult) +{ + if (!importVLseqJson.isObject()) + { + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(importVLseqJson.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return uNodeIndex; + } + else if ( + !importVLseqJson.isMember(jss::public_key) || + !importVLseqJson[jss::public_key].isString()) + { + jvResult[jss::error] = "malformedRequest"; + } + else + { + auto const pkHex = + strUnHex(importVLseqJson[jss::public_key].asString()); + auto const pkSlice = makeSlice(*pkHex); + if (!publicKeyType(pkSlice)) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + else + { + auto const pk = PublicKey(pkSlice); + return keylet::import_vlseq(pk).key; + } + } + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; +} + +std::optional +parseUriToken(Json::Value const& uriTokenJson, Json::Value& jvResult) +{ + if (!uriTokenJson.isObject()) + { + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(uriTokenJson.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return uNodeIndex; + } + else if ( + !uriTokenJson.isMember(jss::account) || + !uriTokenJson.isMember(jss::uri)) + { + jvResult[jss::error] = "malformedRequest"; + } + else + { + auto const id = + parseBase58(uriTokenJson[jss::account].asString()); + auto const strUri = uriTokenJson[jss::uri].asString(); + Blob raw = Blob(strUri.begin(), strUri.end()); + if (!id) + jvResult[jss::error] = "malformedAddress"; + else + return keylet::uritoken(*id, raw).key; + } + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; +} + +using FunctionType = + std::optional (*)(Json::Value const&, Json::Value&); + +struct LedgerEntry +{ + Json::StaticString fieldName; + FunctionType parseFunction; + LedgerEntryType expectedType; +}; + // { // ledger_hash : // ledger_index : @@ -81,965 +1017,133 @@ doLedgerEntry(RPC::JsonContext& context) if (!lpLedger) return jvResult; + static auto ledgerEntryParsers = std::to_array({ + {jss::index, parseIndex, ltANY}, + {jss::account_root, parseAccountRoot, ltACCOUNT_ROOT}, + // TODO: add amendments + {jss::amm, parseAMM, ltAMM}, + {jss::bridge, parseBridge, ltBRIDGE}, + {jss::check, parseCheck, ltCHECK}, + {jss::credential, parseCredential, ltCREDENTIAL}, + {jss::deposit_preauth, parseDepositPreauth, ltDEPOSIT_PREAUTH}, + {jss::did, parseDID, ltDID}, + {jss::directory, parseDirectory, ltDIR_NODE}, + {jss::escrow, parseEscrow, ltESCROW}, + {jss::emitted_txn, parseEmittedTxn, ltEMITTED_TXN}, + {jss::hook, parseHook, ltHOOK}, + {jss::hook_definition, parseHookDefinition, ltHOOK_DEFINITION}, + {jss::hook_state, parseHookState, ltHOOK_STATE}, + {jss::import_vlseq, parseImportVLseq, ltIMPORT_VLSEQ}, + // TODO: add fee, hashes + {jss::mpt_issuance, parseMPTokenIssuance, ltMPTOKEN_ISSUANCE}, + {jss::mptoken, parseMPToken, ltMPTOKEN}, + // TODO: add NFT Offers + {jss::nft_page, parseNFTokenPage, ltNFTOKEN_PAGE}, + // TODO: add NegativeUNL + {jss::offer, parseOffer, ltOFFER}, + {jss::oracle, parseOracle, ltORACLE}, + {jss::payment_channel, parsePaymentChannel, ltPAYCHAN}, + {jss::ripple_state, parseRippleState, ltRIPPLE_STATE}, + // This is an alias, since the `ledger_data` filter uses jss::state + {jss::state, parseRippleState, ltRIPPLE_STATE}, + {jss::ticket, parseTicket, ltTICKET}, + {jss::uri_token, parseUriToken, ltURI_TOKEN}, + {jss::xchain_owned_claim_id, + parseXChainOwnedClaimID, + ltXCHAIN_OWNED_CLAIM_ID}, + {jss::xchain_owned_create_account_claim_id, + parseXChainOwnedCreateAccountClaimID, + ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}, + }); + uint256 uNodeIndex; - bool bNodeBinary = false; LedgerEntryType expectedType = ltANY; try { - if (context.params.isMember(jss::index)) + bool found = false; + for (const auto& ledgerEntry : ledgerEntryParsers) { - if (!uNodeIndex.parseHex(context.params[jss::index].asString())) + if (context.params.isMember(ledgerEntry.fieldName)) { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; + expectedType = ledgerEntry.expectedType; + // `Bridge` is the only type that involves two fields at the + // `ledger_entry` param level. + // So that parser needs to have the whole `params` field. + // All other parsers only need the one field name's info. + Json::Value const& params = ledgerEntry.fieldName == jss::bridge + ? context.params + : context.params[ledgerEntry.fieldName]; + uNodeIndex = ledgerEntry.parseFunction(params, jvResult) + .value_or(beast::zero); + if (jvResult.isMember(jss::error)) + { + return jvResult; + } + found = true; + break; } } - else if (context.params.isMember(jss::account_root)) + if (!found) { - expectedType = ltACCOUNT_ROOT; - auto const account = parseBase58( - context.params[jss::account_root].asString()); - if (!account || account->isZero()) - jvResult[jss::error] = "malformedAddress"; + if (context.apiVersion < 2u) + jvResult[jss::error] = "unknownOption"; else - uNodeIndex = keylet::account(*account).key; - } - else if (context.params.isMember(jss::check)) - { - expectedType = ltCHECK; - - if (!uNodeIndex.parseHex(context.params[jss::check].asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if (context.params.isMember(jss::deposit_preauth)) - { - expectedType = ltDEPOSIT_PREAUTH; - auto const& dp = context.params[jss::deposit_preauth]; - - if (!dp.isObject()) - { - if (!dp.isString() || !uNodeIndex.parseHex(dp.asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - // clang-format off - else if ( - (!dp.isMember(jss::owner) || !dp[jss::owner].isString()) || - (dp.isMember(jss::authorized) == dp.isMember(jss::authorized_credentials)) || - (dp.isMember(jss::authorized) && !dp[jss::authorized].isString()) || - (dp.isMember(jss::authorized_credentials) && !dp[jss::authorized_credentials].isArray()) - ) - // clang-format on - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - auto const owner = - parseBase58(dp[jss::owner].asString()); - if (!owner) - { - jvResult[jss::error] = "malformedOwner"; - } - else if (dp.isMember(jss::authorized)) - { - auto const authorized = - parseBase58(dp[jss::authorized].asString()); - if (!authorized) - jvResult[jss::error] = "malformedAuthorized"; - else - uNodeIndex = - keylet::depositPreauth(*owner, *authorized).key; - } - else - { - auto const& ac(dp[jss::authorized_credentials]); - STArray const arr = parseAuthorizeCredentials(ac); - - if (arr.empty() || (arr.size() > maxCredentialsArraySize)) - jvResult[jss::error] = "malformedAuthorizedCredentials"; - else - { - auto sorted = credentials::makeSorted(arr); - if (sorted.empty()) - jvResult[jss::error] = - "malformedAuthorizedCredentials"; - else - uNodeIndex = - keylet::depositPreauth(*owner, sorted).key; - } - } - } - } - else if (context.params.isMember(jss::directory)) - { - expectedType = ltDIR_NODE; - if (context.params[jss::directory].isNull()) - { - jvResult[jss::error] = "malformedRequest"; - } - else if (!context.params[jss::directory].isObject()) - { - if (!uNodeIndex.parseHex( - context.params[jss::directory].asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if ( - context.params[jss::directory].isMember(jss::sub_index) && - !context.params[jss::directory][jss::sub_index].isIntegral()) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - std::uint64_t uSubIndex = - context.params[jss::directory].isMember(jss::sub_index) - ? context.params[jss::directory][jss::sub_index].asUInt() - : 0; - - if (context.params[jss::directory].isMember(jss::dir_root)) - { - uint256 uDirRoot; - - if (context.params[jss::directory].isMember(jss::owner)) - { - // May not specify both dir_root and owner. - jvResult[jss::error] = "malformedRequest"; - } - else if (!uDirRoot.parseHex( - context.params[jss::directory][jss::dir_root] - .asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - else - { - uNodeIndex = keylet::page(uDirRoot, uSubIndex).key; - } - } - else if (context.params[jss::directory].isMember(jss::owner)) - { - auto const ownerID = parseBase58( - context.params[jss::directory][jss::owner].asString()); - - if (!ownerID) - { - jvResult[jss::error] = "malformedAddress"; - } - else - { - uNodeIndex = - keylet::page(keylet::ownerDir(*ownerID), uSubIndex) - .key; - } - } - else - { - jvResult[jss::error] = "malformedRequest"; - } - } - } - else if (context.params.isMember(jss::escrow)) - { - expectedType = ltESCROW; - if (!context.params[jss::escrow].isObject()) - { - if (!uNodeIndex.parseHex( - context.params[jss::escrow].asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if ( - !context.params[jss::escrow].isMember(jss::owner) || - !context.params[jss::escrow].isMember(jss::seq) || - !context.params[jss::escrow][jss::seq].isIntegral()) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - auto const id = parseBase58( - context.params[jss::escrow][jss::owner].asString()); - if (!id) - jvResult[jss::error] = "malformedOwner"; - else - uNodeIndex = - keylet::escrow( - *id, context.params[jss::escrow][jss::seq].asUInt()) - .key; - } - } - else if (context.params.isMember(jss::emitted_txn)) - { - expectedType = ltEMITTED_TXN; - if (!context.params[jss::emitted_txn].isObject()) - { - if (!uNodeIndex.parseHex( - context.params[jss::emitted_txn].asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - uNodeIndex = keylet::emittedTxn(uNodeIndex).key; - } - } - else if (context.params.isMember(jss::import_vlseq)) - { - expectedType = ltIMPORT_VLSEQ; - if (!context.params[jss::import_vlseq].isObject()) - { - if (!uNodeIndex.parseHex( - context.params[jss::import_vlseq].asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if ( - !context.params[jss::import_vlseq].isMember(jss::public_key) || - !context.params[jss::import_vlseq][jss::public_key].isString()) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - auto const pkHex = - strUnHex(context.params[jss::import_vlseq][jss::public_key] - .asString()); - auto const pkSlice = makeSlice(*pkHex); - if (!publicKeyType(pkSlice)) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - else - { - auto const pk = PublicKey(pkSlice); - uNodeIndex = keylet::import_vlseq(pk).key; - } - } - } - else if (context.params.isMember(jss::offer)) - { - expectedType = ltOFFER; - if (!context.params[jss::offer].isObject()) - { - if (!uNodeIndex.parseHex(context.params[jss::offer].asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if ( - !context.params[jss::offer].isMember(jss::account) || - !context.params[jss::offer].isMember(jss::seq) || - !context.params[jss::offer][jss::seq].isIntegral()) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - auto const id = parseBase58( - context.params[jss::offer][jss::account].asString()); - if (!id) - jvResult[jss::error] = "malformedAddress"; - else - uNodeIndex = - keylet::offer( - *id, context.params[jss::offer][jss::seq].asUInt()) - .key; - } - } - else if (context.params.isMember(jss::payment_channel)) - { - expectedType = ltPAYCHAN; - - if (!uNodeIndex.parseHex( - context.params[jss::payment_channel].asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if (context.params.isMember(jss::uri_token)) - { - expectedType = ltURI_TOKEN; - - if (!context.params[jss::uri_token].isObject()) - { - if (!uNodeIndex.parseHex( - context.params[jss::uri_token].asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if ( - !context.params[jss::uri_token].isMember(jss::account) || - !context.params[jss::uri_token].isMember(jss::uri)) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - auto const id = parseBase58( - context.params[jss::uri_token][jss::account].asString()); - auto const strUri = - context.params[jss::uri_token][jss::uri].asString(); - Blob raw = Blob(strUri.begin(), strUri.end()); - if (!id) - jvResult[jss::error] = "malformedAddress"; - else - uNodeIndex = keylet::uritoken(*id, raw).key; - } - } - else if (context.params.isMember(jss::ripple_state)) - { - expectedType = ltRIPPLE_STATE; - Currency uCurrency; - Json::Value jvRippleState = context.params[jss::ripple_state]; - - if (!jvRippleState.isObject() || - !jvRippleState.isMember(jss::currency) || - !jvRippleState.isMember(jss::accounts) || - !jvRippleState[jss::accounts].isArray() || - 2 != jvRippleState[jss::accounts].size() || - !jvRippleState[jss::accounts][0u].isString() || - !jvRippleState[jss::accounts][1u].isString() || - (jvRippleState[jss::accounts][0u].asString() == - jvRippleState[jss::accounts][1u].asString())) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - auto const id1 = parseBase58( - jvRippleState[jss::accounts][0u].asString()); - auto const id2 = parseBase58( - jvRippleState[jss::accounts][1u].asString()); - if (!id1 || !id2) - { - jvResult[jss::error] = "malformedAddress"; - } - else if (!to_currency( - uCurrency, - jvRippleState[jss::currency].asString())) - { - jvResult[jss::error] = "malformedCurrency"; - } - else - { - uNodeIndex = keylet::line(*id1, *id2, uCurrency).key; - } - } - } - else if (context.params.isMember(jss::ticket)) - { - expectedType = ltTICKET; - if (!context.params[jss::ticket].isObject()) - { - if (!uNodeIndex.parseHex( - context.params[jss::ticket].asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if ( - !context.params[jss::ticket].isMember(jss::account) || - !context.params[jss::ticket].isMember(jss::ticket_seq) || - !context.params[jss::ticket][jss::ticket_seq].isIntegral()) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - auto const id = parseBase58( - context.params[jss::ticket][jss::account].asString()); - if (!id) - jvResult[jss::error] = "malformedAddress"; - else - uNodeIndex = getTicketIndex( - *id, - context.params[jss::ticket][jss::ticket_seq].asUInt()); - } - } - else if (context.params.isMember(jss::hook)) - { - expectedType = ltHOOK; - if (!context.params[jss::hook].isObject()) - { - if (!uNodeIndex.parseHex(context.params[jss::hook].asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if (!context.params[jss::hook].isMember(jss::account)) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - auto const id = parseBase58( - context.params[jss::hook][jss::account].asString()); - if (!id) - jvResult[jss::error] = "malformedAddress"; - else - uNodeIndex = keylet::hook(*id).key; - } - } - else if (context.params.isMember(jss::hook_definition)) - { - expectedType = ltHOOK_DEFINITION; - if (context.params[jss::hook_definition].isObject() || - (!uNodeIndex.parseHex( - context.params[jss::hook_definition].asString()))) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - else - { - uNodeIndex = keylet::hookDefinition(uNodeIndex).key; - } - } - else if (context.params.isMember(jss::hook_state)) - { - expectedType = ltHOOK_STATE; - uint256 uNodeKey; - uint256 uNameSpace; - Json::Value jvHookState = context.params[jss::hook_state]; - - if (!jvHookState.isObject() || - !jvHookState.isMember(jss::account) || - !jvHookState.isMember(jss::key) || - !jvHookState.isMember(jss::namespace_id) || - !jvHookState[jss::account].isString() || - !jvHookState[jss::key].isString() || - !jvHookState[jss::namespace_id].isString()) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - else - { - auto const account = parseBase58( - jvHookState[jss::account].asString()); - if (!account) - { - jvResult[jss::error] = "malformedAddress"; - } - else if (!uNodeKey.parseHex(jvHookState[jss::key].asString())) - { - jvResult[jss::error] = "malformedRequest"; - } - else if (!uNameSpace.parseHex( - jvHookState[jss::namespace_id].asString())) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - uNodeIndex = - keylet::hookState(*account, uNodeKey, uNameSpace).key; - } - } - } - else if (context.params.isMember(jss::nft_page)) - { - expectedType = ltNFTOKEN_PAGE; - - if (context.params[jss::nft_page].isString()) - { - if (!uNodeIndex.parseHex( - context.params[jss::nft_page].asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else - { - jvResult[jss::error] = "malformedRequest"; - } - } - else if (context.params.isMember(jss::amm)) - { - expectedType = ltAMM; - if (!context.params[jss::amm].isObject()) - { - if (!uNodeIndex.parseHex(context.params[jss::amm].asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if ( - !context.params[jss::amm].isMember(jss::asset) || - !context.params[jss::amm].isMember(jss::asset2)) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - try - { - auto const issue = - issueFromJson(context.params[jss::amm][jss::asset]); - auto const issue2 = - issueFromJson(context.params[jss::amm][jss::asset2]); - uNodeIndex = keylet::amm(issue, issue2).key; - } - catch (std::runtime_error const&) - { - jvResult[jss::error] = "malformedRequest"; - } - } - } - else if (context.params.isMember(jss::bridge)) - { - expectedType = ltBRIDGE; - - // return the keylet for the specified bridge or nullopt if the - // request is malformed - auto const maybeKeylet = [&]() -> std::optional { - try - { - if (!context.params.isMember(jss::bridge_account)) - return std::nullopt; - - auto const& jsBridgeAccount = - context.params[jss::bridge_account]; - if (!jsBridgeAccount.isString()) - { - return std::nullopt; - } - auto const account = - parseBase58(jsBridgeAccount.asString()); - if (!account || account->isZero()) - { - return std::nullopt; - } - - // This may throw and is the reason for the `try` block. The - // try block has a larger scope so the `bridge` variable - // doesn't need to be an optional. - STXChainBridge const bridge(context.params[jss::bridge]); - STXChainBridge::ChainType const chainType = - STXChainBridge::srcChain( - account == bridge.lockingChainDoor()); - if (account != bridge.door(chainType)) - return std::nullopt; - - return keylet::bridge(bridge, chainType); - } - catch (...) - { - return std::nullopt; - } - }(); - - if (maybeKeylet) - { - uNodeIndex = maybeKeylet->key; - } - else - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if (context.params.isMember(jss::xchain_owned_claim_id)) - { - expectedType = ltXCHAIN_OWNED_CLAIM_ID; - auto& claim_id = context.params[jss::xchain_owned_claim_id]; - if (claim_id.isString()) - { - // we accept a node id as specifier of a xchain claim id - if (!uNodeIndex.parseHex(claim_id.asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if ( - !claim_id.isObject() || - !(claim_id.isMember(sfIssuingChainDoor.getJsonName()) && - claim_id[sfIssuingChainDoor.getJsonName()].isString()) || - !(claim_id.isMember(sfLockingChainDoor.getJsonName()) && - claim_id[sfLockingChainDoor.getJsonName()].isString()) || - !claim_id.isMember(sfIssuingChainIssue.getJsonName()) || - !claim_id.isMember(sfLockingChainIssue.getJsonName()) || - !claim_id.isMember(jss::xchain_owned_claim_id)) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - // if not specified with a node id, a claim_id is specified by - // four strings defining the bridge (locking_chain_door, - // locking_chain_issue, issuing_chain_door, issuing_chain_issue) - // and the claim id sequence number. - auto lockingChainDoor = parseBase58( - claim_id[sfLockingChainDoor.getJsonName()].asString()); - auto issuingChainDoor = parseBase58( - claim_id[sfIssuingChainDoor.getJsonName()].asString()); - Issue lockingChainIssue, issuingChainIssue; - bool valid = lockingChainDoor && issuingChainDoor; - if (valid) - { - try - { - lockingChainIssue = issueFromJson( - claim_id[sfLockingChainIssue.getJsonName()]); - issuingChainIssue = issueFromJson( - claim_id[sfIssuingChainIssue.getJsonName()]); - } - catch (std::runtime_error const& ex) - { - valid = false; - jvResult[jss::error] = "malformedRequest"; - } - } - - if (valid && claim_id[jss::xchain_owned_claim_id].isIntegral()) - { - auto seq = claim_id[jss::xchain_owned_claim_id].asUInt(); - - STXChainBridge bridge_spec( - *lockingChainDoor, - lockingChainIssue, - *issuingChainDoor, - issuingChainIssue); - Keylet keylet = keylet::xChainClaimID(bridge_spec, seq); - uNodeIndex = keylet.key; - } - } - } - else if (context.params.isMember( - jss::xchain_owned_create_account_claim_id)) - { - // see object definition in LedgerFormats.cpp - expectedType = ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID; - auto& claim_id = - context.params[jss::xchain_owned_create_account_claim_id]; - if (claim_id.isString()) - { - // we accept a node id as specifier of a xchain create account - // claim_id - if (!uNodeIndex.parseHex(claim_id.asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if ( - !claim_id.isObject() || - !(claim_id.isMember(sfIssuingChainDoor.getJsonName()) && - claim_id[sfIssuingChainDoor.getJsonName()].isString()) || - !(claim_id.isMember(sfLockingChainDoor.getJsonName()) && - claim_id[sfLockingChainDoor.getJsonName()].isString()) || - !claim_id.isMember(sfIssuingChainIssue.getJsonName()) || - !claim_id.isMember(sfLockingChainIssue.getJsonName()) || - !claim_id.isMember(jss::xchain_owned_create_account_claim_id)) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - // if not specified with a node id, a create account claim_id is - // specified by four strings defining the bridge - // (locking_chain_door, locking_chain_issue, issuing_chain_door, - // issuing_chain_issue) and the create account claim id sequence - // number. - auto lockingChainDoor = parseBase58( - claim_id[sfLockingChainDoor.getJsonName()].asString()); - auto issuingChainDoor = parseBase58( - claim_id[sfIssuingChainDoor.getJsonName()].asString()); - Issue lockingChainIssue, issuingChainIssue; - bool valid = lockingChainDoor && issuingChainDoor; - if (valid) - { - try - { - lockingChainIssue = issueFromJson( - claim_id[sfLockingChainIssue.getJsonName()]); - issuingChainIssue = issueFromJson( - claim_id[sfIssuingChainIssue.getJsonName()]); - } - catch (std::runtime_error const& ex) - { - valid = false; - jvResult[jss::error] = "malformedRequest"; - } - } - - if (valid && - claim_id[jss::xchain_owned_create_account_claim_id] - .isIntegral()) - { - auto seq = - claim_id[jss::xchain_owned_create_account_claim_id] - .asUInt(); - - STXChainBridge bridge_spec( - *lockingChainDoor, - lockingChainIssue, - *issuingChainDoor, - issuingChainIssue); - Keylet keylet = - keylet::xChainCreateAccountClaimID(bridge_spec, seq); - uNodeIndex = keylet.key; - } - } - } - else if (context.params.isMember(jss::did)) - { - expectedType = ltDID; - auto const account = - parseBase58(context.params[jss::did].asString()); - if (!account || account->isZero()) - jvResult[jss::error] = "malformedAddress"; - else - uNodeIndex = keylet::did(*account).key; - } - else if (context.params.isMember(jss::oracle)) - { - expectedType = ltORACLE; - if (!context.params[jss::oracle].isObject()) - { - if (!uNodeIndex.parseHex( - context.params[jss::oracle].asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if ( - !context.params[jss::oracle].isMember( - jss::oracle_document_id) || - !context.params[jss::oracle].isMember(jss::account)) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - uNodeIndex = beast::zero; - auto const& oracle = context.params[jss::oracle]; - auto const documentID = [&]() -> std::optional { - auto const& id = oracle[jss::oracle_document_id]; - if (id.isUInt() || (id.isInt() && id.asInt() >= 0)) - return std::make_optional(id.asUInt()); - else if (id.isString()) - { - std::uint32_t v; - if (beast::lexicalCastChecked(v, id.asString())) - return std::make_optional(v); - } - return std::nullopt; - }(); - auto const account = - parseBase58(oracle[jss::account].asString()); - if (!account || account->isZero()) - jvResult[jss::error] = "malformedAddress"; - else if (!documentID) - jvResult[jss::error] = "malformedDocumentID"; - else - uNodeIndex = keylet::oracle(*account, *documentID).key; - } - } - else if (context.params.isMember(jss::credential)) - { - expectedType = ltCREDENTIAL; - auto const& cred = context.params[jss::credential]; - - if (cred.isString()) - { - if (!uNodeIndex.parseHex(cred.asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if ( - (!cred.isMember(jss::subject) || - !cred[jss::subject].isString()) || - (!cred.isMember(jss::issuer) || - !cred[jss::issuer].isString()) || - (!cred.isMember(jss::credential_type) || - !cred[jss::credential_type].isString())) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - auto const subject = - parseBase58(cred[jss::subject].asString()); - auto const issuer = - parseBase58(cred[jss::issuer].asString()); - auto const credType = - strUnHex(cred[jss::credential_type].asString()); - if (!subject || subject->isZero() || !issuer || - issuer->isZero() || !credType || credType->empty()) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - uNodeIndex = keylet::credential( - *subject, - *issuer, - Slice(credType->data(), credType->size())) - .key; - } - } - } - else if (context.params.isMember(jss::mpt_issuance)) - { - expectedType = ltMPTOKEN_ISSUANCE; - auto const unparsedMPTIssuanceID = - context.params[jss::mpt_issuance]; - if (unparsedMPTIssuanceID.isString()) - { - uint192 mptIssuanceID; - if (!mptIssuanceID.parseHex(unparsedMPTIssuanceID.asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - else - uNodeIndex = keylet::mptIssuance(mptIssuanceID).key; - } - else - { - jvResult[jss::error] = "malformedRequest"; - } - } - else if (context.params.isMember(jss::mptoken)) - { - expectedType = ltMPTOKEN; - auto const& mptJson = context.params[jss::mptoken]; - if (!mptJson.isObject()) - { - if (!uNodeIndex.parseHex(mptJson.asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else if ( - !mptJson.isMember(jss::mpt_issuance_id) || - !mptJson.isMember(jss::account)) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - try - { - auto const mptIssuanceIdStr = - mptJson[jss::mpt_issuance_id].asString(); - - uint192 mptIssuanceID; - if (!mptIssuanceID.parseHex(mptIssuanceIdStr)) - Throw( - "Cannot parse mpt_issuance_id"); - - auto const account = parseBase58( - mptJson[jss::account].asString()); - - if (!account || account->isZero()) - jvResult[jss::error] = "malformedAddress"; - else - uNodeIndex = - keylet::mptoken(mptIssuanceID, *account).key; - } - catch (std::runtime_error const&) - { - jvResult[jss::error] = "malformedRequest"; - } - } - } - else - { - if (context.params.isMember("params") && - context.params["params"].isArray() && - context.params["params"].size() == 1 && - context.params["params"][0u].isString()) - { - if (!uNodeIndex.parseHex( - context.params["params"][0u].asString())) - { - uNodeIndex = beast::zero; - jvResult[jss::error] = "malformedRequest"; - } - } - else - { - if (context.apiVersion < 2u) - jvResult[jss::error] = "unknownOption"; - else - jvResult[jss::error] = "invalidParams"; - } + jvResult[jss::error] = "invalidParams"; + return jvResult; } } catch (Json::error& e) { if (context.apiVersion > 1u) { - // For apiVersion 2 onwards, any parsing failures that throw - // this + // For apiVersion 2 onwards, any parsing failures that throw this // exception return an invalidParam error. - uNodeIndex = beast::zero; jvResult[jss::error] = "invalidParams"; + return jvResult; } else throw; } - if (uNodeIndex.isNonZero()) + if (uNodeIndex.isZero()) { - auto const sleNode = lpLedger->read(keylet::unchecked(uNodeIndex)); - if (context.params.isMember(jss::binary)) - bNodeBinary = context.params[jss::binary].asBool(); + jvResult[jss::error] = "entryNotFound"; + return jvResult; + } - if (!sleNode) - { - // Not found. - jvResult[jss::error] = "entryNotFound"; - } - else if ( - (expectedType != ltANY) && (expectedType != sleNode->getType())) - { - jvResult[jss::error] = "unexpectedLedgerType"; - } - else if (bNodeBinary) - { - Serializer s; + auto const sleNode = lpLedger->read(keylet::unchecked(uNodeIndex)); - sleNode->add(s); + bool bNodeBinary = false; + if (context.params.isMember(jss::binary)) + bNodeBinary = context.params[jss::binary].asBool(); - jvResult[jss::node_binary] = strHex(s.peekData()); - jvResult[jss::index] = to_string(uNodeIndex); - } - else - { - jvResult[jss::node] = sleNode->getJson(JsonOptions::none); - jvResult[jss::index] = to_string(uNodeIndex); - } + if (!sleNode) + { + // Not found. + jvResult[jss::error] = "entryNotFound"; + return jvResult; + } + + if ((expectedType != ltANY) && (expectedType != sleNode->getType())) + { + jvResult[jss::error] = "unexpectedLedgerType"; + return jvResult; + } + + if (bNodeBinary) + { + Serializer s; + + sleNode->add(s); + + jvResult[jss::node_binary] = strHex(s.peekData()); + jvResult[jss::index] = to_string(uNodeIndex); + } + else + { + jvResult[jss::node] = sleNode->getJson(JsonOptions::none); + jvResult[jss::index] = to_string(uNodeIndex); } return jvResult; @@ -1070,7 +1174,7 @@ doLedgerEntryGrpc( return {response, errorStatus}; } - auto key = uint256::fromVoidChecked(request.key()); + auto const key = uint256::fromVoidChecked(request.key()); if (!key) { grpc::Status errorStatus{