diff --git a/src/test/rpc/LedgerEntry_test.cpp b/src/test/rpc/LedgerEntry_test.cpp new file mode 100644 index 000000000..1a5734266 --- /dev/null +++ b/src/test/rpc/LedgerEntry_test.cpp @@ -0,0 +1,3152 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "test/jtx/Env.h" + +namespace ripple { + +namespace test { + +class LedgerEntry_test : public beast::unit_test::suite +{ +#define HSFEE fee(100'000'000) + void + checkErrorValue( + Json::Value const& jv, + std::string const& err, + std::string const& msg) + { + if (BEAST_EXPECT(jv.isMember(jss::status))) + BEAST_EXPECT(jv[jss::status] == "error"); + if (BEAST_EXPECT(jv.isMember(jss::error))) + BEAST_EXPECT(jv[jss::error] == err); + if (msg.empty()) + { + BEAST_EXPECT( + jv[jss::error_message] == Json::nullValue || + jv[jss::error_message] == ""); + } + else if (BEAST_EXPECT(jv.isMember(jss::error_message))) + BEAST_EXPECT(jv[jss::error_message] == msg); + } + + // Corrupt a valid address by replacing the 10th character with '!'. + // '!' is not part of the ripple alphabet. + std::string + makeBadAddress(std::string good) + { + std::string ret = std::move(good); + ret.replace(10, 1, 1, '!'); + return ret; + } + + void + testLedgerEntryInvalid() + { + testcase("Invalid requests"); + using namespace test::jtx; + Env env{*this}; + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + { + // Missing ledger_entry ledger_hash + Json::Value jvParams; + jvParams[jss::account_root] = alice.human(); + jvParams[jss::ledger_hash] = + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AA"; + auto const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound"); + } + + { + // ask for an zero index + Json::Value jvParams; + jvParams[jss::ledger_index] = "validated"; + jvParams[jss::index] = + "00000000000000000000000000000000000000000000000000000000000000" + "0000"; + auto const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + } + + void + testLedgerEntryAccountRoot() + { + testcase("ledger_entry Request AccountRoot"); + using namespace test::jtx; + + auto cfg = envconfig(); + cfg->FEES.reference_fee = 10; + Env env{ + *this, + std::move(cfg), + supported_amendments() - featureXahauGenesis - fixHookAPI20251128}; + + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + { + // Exercise ledger_closed along the way. + Json::Value const jrr = env.rpc("ledger_closed")[jss::result]; + BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash); + BEAST_EXPECT(jrr[jss::ledger_index] == 3); + } + + std::string accountRootIndex; + { + // Request alice's account root. + Json::Value jvParams; + jvParams[jss::account_root] = alice.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr.isMember(jss::node)); + BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000"); + accountRootIndex = jrr[jss::index].asString(); + } + { + constexpr char alicesAcctRootBinary[]{ + "1100612200800000240000000425000000032D00000000559CE54C3B934E4" + "73A995B477E92EC229F99CED5B62BF4D2ACE4DC42719103AE2F6240000002" + "540BE4008114AE123A8556F3CF91154711376AFB0F894F832B3D"}; + + // Request alice's account root, but with binary == true; + Json::Value jvParams; + jvParams[jss::account_root] = alice.human(); + jvParams[jss::binary] = 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr.isMember(jss::node_binary)); + BEAST_EXPECT(jrr[jss::node_binary] == alicesAcctRootBinary); + } + { + // Request alice's account root using the index. + Json::Value jvParams; + jvParams[jss::index] = accountRootIndex; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(!jrr.isMember(jss::node_binary)); + BEAST_EXPECT(jrr.isMember(jss::node)); + BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000"); + } + { + // Request alice's account root by index, but with binary == false. + Json::Value jvParams; + jvParams[jss::index] = accountRootIndex; + jvParams[jss::binary] = 0; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr.isMember(jss::node)); + BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000"); + } + { + // Request using a corrupted AccountID. + Json::Value jvParams; + jvParams[jss::account_root] = makeBadAddress(alice.human()); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedAddress", ""); + } + { + // Request an account that is not in the ledger. + Json::Value jvParams; + jvParams[jss::account_root] = Account("bob").human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + + void + testLedgerEntryCheck() + { + testcase("ledger_entry Request Check"); + 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 jvParams; + jvParams[jss::check] = to_string(checkId.key); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check); + BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000"); + } + { + // Request an index that is not a check. We'll use alice's + // account root index. + std::string accountRootIndex; + { + Json::Value jvParams; + jvParams[jss::account_root] = alice.human(); + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + accountRootIndex = jrr[jss::index].asString(); + } + Json::Value jvParams; + jvParams[jss::check] = accountRootIndex; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "unexpectedLedgerType", ""); + } + } + + void + testLedgerEntryCredentials() + { + testcase("ledger_entry credentials"); + + using namespace test::jtx; + + Env env(*this, supported_amendments() | featureCredentials); + Account const issuer{"issuer"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + const char credType[] = "abcde"; + + env.fund(XRP(5000), issuer, alice, bob); + env.close(); + + // Setup credentials with DepositAuth object for Alice and Bob + env(credentials::create(alice, issuer, credType)); + env.close(); + + { + // Succeed + auto jv = credentials::ledgerEntry(env, alice, issuer, credType); + BEAST_EXPECT( + jv.isObject() && jv.isMember(jss::result) && + !jv[jss::result].isMember(jss::error) && + jv[jss::result].isMember(jss::node) && + jv[jss::result][jss::node].isMember( + sfLedgerEntryType.jsonName) && + jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == + jss::Credential); + + std::string const credIdx = jv[jss::result][jss::index].asString(); + + jv = credentials::ledgerEntry(env, credIdx); + BEAST_EXPECT( + jv.isObject() && jv.isMember(jss::result) && + !jv[jss::result].isMember(jss::error) && + jv[jss::result].isMember(jss::node) && + jv[jss::result][jss::node].isMember( + sfLedgerEntryType.jsonName) && + jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == + jss::Credential); + } + + { + // Fail, index not a hash + auto const jv = credentials::ledgerEntry(env, ""); + checkErrorValue(jv[jss::result], "malformedRequest", ""); + } + + { + // Fail, credential doesn't exist + auto const jv = credentials::ledgerEntry( + env, + "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" + "E4"); + checkErrorValue(jv[jss::result], "entryNotFound", ""); + } + + { + // Fail, invalid subject + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = 42; + jv[jss::credential][jss::issuer] = issuer.human(); + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, invalid issuer + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::issuer] = 42; + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, invalid credentials type + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::issuer] = issuer.human(); + jv[jss::credential][jss::credential_type] = 42; + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, empty subject + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = ""; + jv[jss::credential][jss::issuer] = issuer.human(); + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, empty issuer + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::issuer] = ""; + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, empty credentials type + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::issuer] = issuer.human(); + jv[jss::credential][jss::credential_type] = ""; + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, no subject + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::issuer] = issuer.human(); + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, no issuer + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, no credentials type + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::issuer] = issuer.human(); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, not AccountID subject + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = "wehsdbvasbdfvj"; + jv[jss::credential][jss::issuer] = issuer.human(); + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, not AccountID issuer + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::issuer] = "c4p93ugndfbsiu"; + jv[jss::credential][jss::credential_type] = + strHex(std::string_view(credType)); + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, credentials type isn't hex encoded + Json::Value jv; + jv[jss::ledger_index] = jss::validated; + jv[jss::credential][jss::subject] = alice.human(); + jv[jss::credential][jss::issuer] = issuer.human(); + jv[jss::credential][jss::credential_type] = "12KK"; + auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + } + + void + testLedgerEntryCron() + { + testcase("ledger_entry Request Cron"); + using namespace test::jtx; + + Env env{*this}; + + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + auto const startTime = + env.current()->parentCloseTime().time_since_epoch().count() + 100; + env(cron::set(alice), + cron::startTime(startTime), + cron::delay(100), + cron::repeat(200), + fee(XRP(1)), + ter(tesSUCCESS)); + + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + + uint256 const cronIndex{keylet::cron(startTime, alice).key}; + { + // Request the cron using its index. + Json::Value jvParams; + jvParams[jss::cron] = to_string(cronIndex); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfStartTime.jsonName] == startTime); + BEAST_EXPECT(jrr[jss::node][sfDelaySeconds.jsonName] == 100); + BEAST_EXPECT(jrr[jss::node][sfRepeatCount.jsonName] == 200); + } + { + // Request the cron using its owner and time. + Json::Value jvParams; + jvParams[jss::cron] = Json::objectValue; + jvParams[jss::cron][jss::owner] = alice.human(); + jvParams[jss::cron][jss::time] = startTime; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfStartTime.jsonName] == startTime); + BEAST_EXPECT(jrr[jss::node][sfDelaySeconds.jsonName] == 100); + BEAST_EXPECT(jrr[jss::node][sfRepeatCount.jsonName] == 200); + } + { + // Malformed uritoken object. Missing owner member. + Json::Value jvParams; + jvParams[jss::cron] = Json::objectValue; + jvParams[jss::cron][jss::time] = startTime; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed uritoken object. Missing time member. + Json::Value jvParams; + jvParams[jss::cron] = Json::objectValue; + jvParams[jss::cron][jss::owner] = alice.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Request an index that is not a uritoken. + Json::Value jvParams; + jvParams[jss::cron] = ledgerHash; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + + void + testLedgerEntryDepositPreauth() + { + testcase("ledger_entry Deposit Preauth"); + + using namespace test::jtx; + + Env env{*this}; + Account const alice{"alice"}; + Account const becky{"becky"}; + + env.fund(XRP(10000), alice, becky); + env.close(); + + env(deposit::auth(alice, becky)); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + std::string depositPreauthIndex; + { + // Request a depositPreauth by owner and authorized. + Json::Value jvParams; + jvParams[jss::deposit_preauth][jss::owner] = alice.human(); + jvParams[jss::deposit_preauth][jss::authorized] = becky.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == + jss::DepositPreauth); + BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human()); + depositPreauthIndex = jrr[jss::node][jss::index].asString(); + } + { + // Request a depositPreauth by index. + Json::Value jvParams; + jvParams[jss::deposit_preauth] = depositPreauthIndex; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == + jss::DepositPreauth); + BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human()); + } + { + // Malformed request: deposit_preauth neither object nor string. + Json::Value jvParams; + jvParams[jss::deposit_preauth] = -5; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed request: deposit_preauth not hex string. + Json::Value jvParams; + jvParams[jss::deposit_preauth] = "0123456789ABCDEFG"; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed request: missing [jss::deposit_preauth][jss::owner] + Json::Value jvParams; + jvParams[jss::deposit_preauth][jss::authorized] = becky.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed request: [jss::deposit_preauth][jss::owner] not string. + Json::Value jvParams; + jvParams[jss::deposit_preauth][jss::owner] = 7; + jvParams[jss::deposit_preauth][jss::authorized] = becky.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed: missing [jss::deposit_preauth][jss::authorized] + Json::Value jvParams; + jvParams[jss::deposit_preauth][jss::owner] = alice.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed: [jss::deposit_preauth][jss::authorized] not string. + Json::Value jvParams; + jvParams[jss::deposit_preauth][jss::owner] = alice.human(); + jvParams[jss::deposit_preauth][jss::authorized] = 47; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed: [jss::deposit_preauth][jss::owner] is malformed. + Json::Value jvParams; + jvParams[jss::deposit_preauth][jss::owner] = + "rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas"; + + jvParams[jss::deposit_preauth][jss::authorized] = becky.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedOwner", ""); + } + { + // Malformed: [jss::deposit_preauth][jss::authorized] is malformed. + Json::Value jvParams; + jvParams[jss::deposit_preauth][jss::owner] = alice.human(); + jvParams[jss::deposit_preauth][jss::authorized] = + "rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas"; + + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedAuthorized", ""); + } + } + + void + testLedgerEntryDepositPreauthCred() + { + testcase("ledger_entry Deposit Preauth with credentials"); + + using namespace test::jtx; + + Env env(*this, supported_amendments() | featureCredentials); + Account const issuer{"issuer"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + const char credType[] = "abcde"; + + env.fund(XRP(5000), issuer, alice, bob); + env.close(); + + { + // Setup Bob with DepositAuth + env(fset(bob, asfDepositAuth)); + env.close(); + env(deposit::authCredentials(bob, {{issuer, credType}})); + env.close(); + } + + { + // Succeed + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(std::move(jo)); + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + + BEAST_EXPECT( + jrr.isObject() && jrr.isMember(jss::result) && + !jrr[jss::result].isMember(jss::error) && + jrr[jss::result].isMember(jss::node) && + jrr[jss::result][jss::node].isMember( + sfLedgerEntryType.jsonName) && + jrr[jss::result][jss::node][sfLedgerEntryType.jsonName] == + jss::DepositPreauth); + } + + { + // Failed, invalid account + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = to_string(xrpAccount()); + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(std::move(jo)); + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, duplicates in credentials + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(jo); + arr.append(std::move(jo)); + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, invalid credential_type + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + jo[jss::credential_type] = ""; + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, authorized and authorized_credentials both present + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + jvParams[jss::deposit_preauth][jss::authorized] = alice.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Failed, authorized_credentials is not an array + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + jvParams[jss::deposit_preauth][jss::authorized_credentials] = 42; + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Failed, authorized_credentials contains string data + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + arr.append("foobar"); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, authorized_credentials contains arrays + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + Json::Value payload = Json::arrayValue; + payload.append(42); + arr.append(std::move(payload)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, authorized_credentials is empty array + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, authorized_credentials is too long + + static const std::string_view credTypes[] = { + "cred1", + "cred2", + "cred3", + "cred4", + "cred5", + "cred6", + "cred7", + "cred8", + "cred9"}; + static_assert( + sizeof(credTypes) / sizeof(credTypes[0]) > + maxCredentialsArraySize); + + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + for (unsigned i = 0; i < sizeof(credTypes) / sizeof(credTypes[0]); + ++i) + { + Json::Value jo; + jo[jss::issuer] = issuer.human(); + jo[jss::credential_type] = + strHex(std::string_view(credTypes[i])); + arr.append(std::move(jo)); + } + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, issuer is not set + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, issuer isn't string + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = 42; + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, issuer is an array + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + Json::Value payload = Json::arrayValue; + payload.append(42); + jo[jss::issuer] = std::move(payload); + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, issuer isn't valid encoded account + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = "invalid_account"; + jo[jss::credential_type] = strHex(std::string_view(credType)); + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, credential_type is not set + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, credential_type isn't string + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + jo[jss::credential_type] = 42; + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, credential_type is an array + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + Json::Value payload = Json::arrayValue; + payload.append(42); + jo[jss::credential_type] = std::move(payload); + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + + { + // Failed, credential_type isn't hex encoded + Json::Value jvParams; + jvParams[jss::ledger_index] = jss::validated; + jvParams[jss::deposit_preauth][jss::owner] = bob.human(); + + jvParams[jss::deposit_preauth][jss::authorized_credentials] = + Json::arrayValue; + auto& arr( + jvParams[jss::deposit_preauth][jss::authorized_credentials]); + + Json::Value jo; + jo[jss::issuer] = issuer.human(); + jo[jss::credential_type] = "12KK"; + arr.append(std::move(jo)); + + auto const jrr = + env.rpc("json", "ledger_entry", to_string(jvParams)); + checkErrorValue( + jrr[jss::result], "malformedAuthorizedCredentials", ""); + } + } + + void + testLedgerEntryDirectory() + { + testcase("ledger_entry Request Directory"); + using namespace test::jtx; + Env env{*this}; + Account const alice{"alice"}; + Account const gw{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(10000), alice, gw); + env.close(); + + env.trust(USD(1000), alice); + env.close(); + + // Run up the number of directory entries so alice has two + // directory nodes. + for (int d = 1'000'032; d >= 1'000'000; --d) + { + env(offer(alice, USD(1), drops(d))); + } + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + { + // Exercise ledger_closed along the way. + Json::Value const jrr = env.rpc("ledger_closed")[jss::result]; + BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash); + BEAST_EXPECT(jrr[jss::ledger_index] == 5); + } + + std::string const dirRootIndex = + "A33EC6BB85FB5674074C4A3A43373BB17645308F3EAE1933E3E35252162B217D"; + { + // Locate directory by index. + Json::Value jvParams; + jvParams[jss::directory] = dirRootIndex; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 32); + } + { + // Locate directory by directory root. + Json::Value jvParams; + jvParams[jss::directory] = Json::objectValue; + jvParams[jss::directory][jss::dir_root] = dirRootIndex; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::index] == dirRootIndex); + } + { + // Locate directory by owner. + Json::Value jvParams; + jvParams[jss::directory] = Json::objectValue; + jvParams[jss::directory][jss::owner] = alice.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::index] == dirRootIndex); + } + { + // Locate directory by directory root and sub_index. + Json::Value jvParams; + jvParams[jss::directory] = Json::objectValue; + jvParams[jss::directory][jss::dir_root] = dirRootIndex; + jvParams[jss::directory][jss::sub_index] = 1; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::index] != dirRootIndex); + BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2); + } + { + // Locate directory by owner and sub_index. + Json::Value jvParams; + jvParams[jss::directory] = Json::objectValue; + jvParams[jss::directory][jss::owner] = alice.human(); + jvParams[jss::directory][jss::sub_index] = 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::index] != dirRootIndex); + BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2); + } + { + // Null directory argument. + Json::Value jvParams; + jvParams[jss::directory] = Json::nullValue; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Non-integer sub_index. + Json::Value jvParams; + jvParams[jss::directory] = Json::objectValue; + jvParams[jss::directory][jss::dir_root] = dirRootIndex; + jvParams[jss::directory][jss::sub_index] = 1.5; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed owner entry. + Json::Value jvParams; + jvParams[jss::directory] = Json::objectValue; + + std::string const badAddress = makeBadAddress(alice.human()); + jvParams[jss::directory][jss::owner] = badAddress; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedAddress", ""); + } + { + // Malformed directory object. Specify both dir_root and owner. + Json::Value jvParams; + jvParams[jss::directory] = Json::objectValue; + jvParams[jss::directory][jss::owner] = alice.human(); + jvParams[jss::directory][jss::dir_root] = dirRootIndex; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Incomplete directory object. Missing both dir_root and owner. + Json::Value jvParams; + jvParams[jss::directory] = Json::objectValue; + jvParams[jss::directory][jss::sub_index] = 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + } + + void + testLedgerEntryEscrow() + { + testcase("ledger_entry Request Escrow"); + using namespace test::jtx; + Env env{*this}; + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + // Lambda to create an escrow. + auto escrowCreate = [](test::jtx::Account const& account, + test::jtx::Account const& to, + STAmount const& amount, + NetClock::time_point const& cancelAfter) { + Json::Value jv; + jv[jss::TransactionType] = jss::EscrowCreate; + jv[jss::Flags] = tfUniversal; + jv[jss::Account] = account.human(); + jv[jss::Destination] = to.human(); + jv[jss::Amount] = amount.getJson(JsonOptions::none); + jv[sfFinishAfter.jsonName] = + cancelAfter.time_since_epoch().count() + 2; + return jv; + }; + + using namespace std::chrono_literals; + env(escrowCreate(alice, alice, XRP(333), env.now() + 2s)); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + std::string escrowIndex; + { + // Request the escrow using owner and sequence. + Json::Value jvParams; + jvParams[jss::escrow] = Json::objectValue; + jvParams[jss::escrow][jss::owner] = alice.human(); + jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][jss::Amount] == XRP(333).value().getText()); + escrowIndex = jrr[jss::index].asString(); + } + { + // Request the escrow by index. + Json::Value jvParams; + jvParams[jss::escrow] = escrowIndex; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][jss::Amount] == XRP(333).value().getText()); + } + { + // Malformed owner entry. + Json::Value jvParams; + jvParams[jss::escrow] = Json::objectValue; + + std::string const badAddress = makeBadAddress(alice.human()); + jvParams[jss::escrow][jss::owner] = badAddress; + jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedOwner", ""); + } + { + // Missing owner. + Json::Value jvParams; + jvParams[jss::escrow] = Json::objectValue; + jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Missing sequence. + Json::Value jvParams; + jvParams[jss::escrow] = Json::objectValue; + jvParams[jss::escrow][jss::owner] = alice.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Non-integer sequence. + Json::Value jvParams; + jvParams[jss::escrow] = Json::objectValue; + jvParams[jss::escrow][jss::owner] = alice.human(); + jvParams[jss::escrow][jss::seq] = + std::to_string(env.seq(alice) - 1); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + } + + void + testLedgerEntryHook() + { + testcase("ledger_entry Request Hook"); + using namespace test::jtx; + Env env{*this}; + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + // Lambda to create a hook. + auto setHook = [](test::jtx::Account const& account) { + std::string const createCodeHex = + "0061736D0100000001130360037F7F7E017E60027F7F017F60017F017E0217" + "0203656E7606616363657074000003656E76025F6700010302010205030100" + "02062B077F01418088040B7F004180080B7F004180080B7F004180080B7F00" + "418088040B7F0041000B7F0041010B07080104686F6F6B00020AB5800001B1" + "800001017F230041106B220124002001200036020C41002200200042001000" + "1A41012200200010011A200141106A240042000B"; + Json::Value jv = + ripple::test::jtx::hook(account, {{hso(createCodeHex)}}, 0); + return jv; + }; + + using namespace std::chrono_literals; + env(setHook(alice), HSFEE); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + + std::string hookIndex; + { + // Request the hook using account. + Json::Value jvParams; + jvParams[jss::hook] = Json::objectValue; + jvParams[jss::hook][jss::account] = alice.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human()); + hookIndex = jrr[jss::index].asString(); + } + { + // Request the hook by index. + Json::Value jvParams; + jvParams[jss::hook] = hookIndex; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human()); + } + { + // Malformed account entry. + Json::Value jvParams; + jvParams[jss::hook] = Json::objectValue; + std::string const badAddress = makeBadAddress(alice.human()); + jvParams[jss::hook][jss::account] = badAddress; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedAddress", ""); + } + { + // Missing account. + Json::Value jvParams; + jvParams[jss::hook] = Json::objectValue; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + } + + void + testLedgerEntryHookDefinition() + { + testcase("ledger_entry Request Hook Definition"); + using namespace test::jtx; + Env env{*this}; + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + // Lambda to create a hook. + auto setHook = [](test::jtx::Account const& account) { + std::string const createCodeHex = + "0061736D0100000001130360037F7F7E017E60027F7F017F60017F017E0217" + "0203656E7606616363657074000003656E76025F6700010302010205030100" + "02062B077F01418088040B7F004180080B7F004180080B7F004180080B7F00" + "418088040B7F0041000B7F0041010B07080104686F6F6B00020AB5800001B1" + "800001017F230041106B220124002001200036020C41002200200042001000" + "1A41012200200010011A200141106A240042000B"; + Json::Value jv = + ripple::test::jtx::hook(account, {{hso(createCodeHex)}}, 0); + return jv; + }; + + using namespace std::chrono_literals; + env(setHook(alice), HSFEE); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + + auto const hook = env.le(keylet::hook(alice.id())); + auto const& hooks = hook->getFieldArray(sfHooks); + uint256 hookHash = hooks[0].getFieldH256(sfHookHash); + + { + // Request the hook_definition using hash. + Json::Value jvParams; + jvParams[jss::hook_definition] = to_string(hookHash); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][jss::HookHash] == to_string(hookHash)); + } + { + // Malformed hook_definition entry. + Json::Value jvParams; + jvParams[jss::hook_definition] = Json::objectValue; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Request an index that is not a payment channel. + Json::Value jvParams; + jvParams[jss::hook_definition] = ledgerHash; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + + void + testLedgerEntryHookState() + { + testcase("ledger_entry Request Hook State"); + using namespace test::jtx; + Env env{*this}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(10000), alice, bob); + env.close(); + + auto const key = uint256::fromVoid( + (std::array{ + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 'k', 'e', 'y', 0x00U}) + .data()); + + auto const ns = uint256::fromVoid( + (std::array{ + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU}) + .data()); + + auto const nons = uint256::fromVoid( + (std::array{ + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, + 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFFU}) + .data()); + + // Lambda to create a hook. + auto setHook = [](test::jtx::Account const& account) { + std::string const createCodeHex = + "0061736D01000000011B0460027F7F017F60047F7F7F7F017E60037F7F7E01" + "7E60017F017E02270303656E76025F67000003656E760973746174655F7365" + "74000103656E76066163636570740002030201030503010002062B077F0141" + "9088040B7F004180080B7F00418A080B7F004180080B7F00419088040B7F00" + "41000B7F0041010B07080104686F6F6B00030AE7800001E3800002017F017E" + "230041106B220124002001200036020C41012200200010001A200141800828" + "0000360208200141046A410022002F0088083B010020012000280084083602" + "004100200020014106200141086A4104100110022102200141106A24002002" + "0B0B1001004180080B096B65790076616C7565"; + std::string ns_str = + "CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECA" + "FE"; + Json::Value jv = + ripple::test::jtx::hook(account, {{hso(createCodeHex)}}, 0); + jv[jss::Hooks][0U][jss::Hook][jss::HookNamespace] = ns_str; + return jv; + }; + + using namespace std::chrono_literals; + env(setHook(alice), HSFEE); + env.close(); + + env(pay(bob, alice, XRP(1)), fee(XRP(1))); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + + { + // Request the hook_state using hash. + Json::Value jvParams; + jvParams[jss::hook_state] = Json::objectValue; + jvParams[jss::hook_state][jss::account] = alice.human(); + jvParams[jss::hook_state][jss::key] = to_string(key); + jvParams[jss::hook_state][jss::namespace_id] = to_string(ns); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][jss::HookStateData] == "76616C756500"); + BEAST_EXPECT(jrr[jss::node][jss::HookStateKey] == to_string(key)); + } + { + // Malformed hook_state object. Missing account member. + Json::Value jvParams; + jvParams[jss::hook_state] = Json::objectValue; + jvParams[jss::hook_state][jss::key] = to_string(key); + jvParams[jss::hook_state][jss::namespace_id] = to_string(ns); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed hook_state object. Missing key member. + Json::Value jvParams; + jvParams[jss::hook_state] = Json::objectValue; + jvParams[jss::hook_state][jss::account] = alice.human(); + jvParams[jss::hook_state][jss::namespace_id] = to_string(ns); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed hook_state object. Missing namespace member. + Json::Value jvParams; + jvParams[jss::hook_state] = Json::objectValue; + jvParams[jss::hook_state][jss::account] = alice.human(); + jvParams[jss::hook_state][jss::key] = to_string(key); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed hook_state account. + Json::Value jvParams; + jvParams[jss::hook_state] = Json::objectValue; + jvParams[jss::hook_state][jss::account] = + makeBadAddress(alice.human()); + jvParams[jss::hook_state][jss::key] = to_string(key); + jvParams[jss::hook_state][jss::namespace_id] = to_string(ns); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedAddress", ""); + } + { + // Request a hook_state that doesn't exist. + Json::Value jvParams; + jvParams[jss::hook_state] = Json::objectValue; + jvParams[jss::hook_state][jss::account] = alice.human(); + jvParams[jss::hook_state][jss::key] = to_string(key); + jvParams[jss::hook_state][jss::namespace_id] = to_string(nons); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + + void + testLedgerEntryImportVLSeq() + { + testcase("ledger_entry Request ImportVLSeq"); + using namespace test::jtx; + + std::vector const keys = { + "ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC" + "1"}; + Env env{*this, network::makeNetworkVLConfig(21337, keys)}; + + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + auto const master = Account("masterpassphrase"); + env(noop(master), fee(10'000'000'000), ter(tesSUCCESS)); + env.close(); + env(import::import( + alice, import::loadXpop(test::ImportTCAccountSet::w_seed)), + fee(10 * 10), + ter(tesSUCCESS)); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + + auto const pk = PublicKey(makeSlice(*strUnHex(keys[0u]))); + uint256 const importvlIndex{keylet::import_vlseq(pk).key}; + { + // Request the import vl using its index. + Json::Value jvParams; + jvParams[jss::import_vlseq] = to_string(importvlIndex); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfPublicKey.jsonName] == keys[0u]); + } + { + // Request the import vl using its public key. + Json::Value jvParams; + jvParams[jss::import_vlseq] = Json::objectValue; + jvParams[jss::import_vlseq][jss::public_key] = keys[0u]; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfPublicKey.jsonName] == keys[0u]); + } + { + // Malformed import vl object. Missing public key. + Json::Value jvParams; + jvParams[jss::import_vlseq] = Json::objectValue; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed import vl object. Bad public key. + Json::Value jvParams; + jvParams[jss::import_vlseq] = Json::objectValue; + jvParams[jss::import_vlseq][jss::public_key] = 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed import vl object. Bad public key. + Json::Value jvParams; + jvParams[jss::import_vlseq] = Json::objectValue; + jvParams[jss::import_vlseq][jss::public_key] = "DEADBEEF"; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Request an index that is not a uritoken. + Json::Value jvParams; + jvParams[jss::import_vlseq] = ledgerHash; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + + void + testLedgerEntryOffer() + { + testcase("ledger_entry Request Offer"); + using namespace test::jtx; + Env env{*this}; + Account const alice{"alice"}; + Account const gw{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(10000), alice, gw); + env.close(); + + env(offer(alice, USD(321), XRP(322))); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + std::string offerIndex; + { + // Request the offer using owner and sequence. + Json::Value jvParams; + jvParams[jss::offer] = Json::objectValue; + jvParams[jss::offer][jss::account] = alice.human(); + jvParams[jss::offer][jss::seq] = env.seq(alice) - 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000"); + offerIndex = jrr[jss::index].asString(); + } + { + // Request the offer using its index. + Json::Value jvParams; + jvParams[jss::offer] = offerIndex; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000"); + } + { + // Malformed account entry. + Json::Value jvParams; + jvParams[jss::offer] = Json::objectValue; + + std::string const badAddress = makeBadAddress(alice.human()); + jvParams[jss::offer][jss::account] = badAddress; + jvParams[jss::offer][jss::seq] = env.seq(alice) - 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedAddress", ""); + } + { + // Malformed offer object. Missing account member. + Json::Value jvParams; + jvParams[jss::offer] = Json::objectValue; + jvParams[jss::offer][jss::seq] = env.seq(alice) - 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed offer object. Missing seq member. + Json::Value jvParams; + jvParams[jss::offer] = Json::objectValue; + jvParams[jss::offer][jss::account] = alice.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed offer object. Non-integral seq member. + Json::Value jvParams; + jvParams[jss::offer] = Json::objectValue; + jvParams[jss::offer][jss::account] = alice.human(); + jvParams[jss::offer][jss::seq] = std::to_string(env.seq(alice) - 1); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + } + + void + testLedgerEntryPayChan() + { + testcase("ledger_entry Request Pay Chan"); + using namespace test::jtx; + using namespace std::literals::chrono_literals; + Env env{*this}; + Account const alice{"alice"}; + + env.fund(XRP(10000), alice); + env.close(); + + // Lambda to create a PayChan. + auto payChanCreate = [](test::jtx::Account const& account, + test::jtx::Account const& to, + STAmount const& amount, + NetClock::duration const& settleDelay, + PublicKey const& pk) { + Json::Value jv; + jv[jss::TransactionType] = jss::PaymentChannelCreate; + jv[jss::Account] = account.human(); + jv[jss::Destination] = to.human(); + jv[jss::Amount] = amount.getJson(JsonOptions::none); + jv[sfSettleDelay.jsonName] = settleDelay.count(); + jv[sfPublicKey.jsonName] = strHex(pk.slice()); + return jv; + }; + + env(payChanCreate(alice, env.master, XRP(57), 18s, alice.pk())); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + + uint256 const payChanIndex{ + keylet::payChan(alice, env.master, env.seq(alice) - 1).key}; + { + // Request the payment channel using its index. + Json::Value jvParams; + jvParams[jss::payment_channel] = to_string(payChanIndex); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "57000000"); + BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "0"); + BEAST_EXPECT(jrr[jss::node][sfSettleDelay.jsonName] == 18); + } + { + // Request an index that is not a payment channel. + Json::Value jvParams; + jvParams[jss::payment_channel] = ledgerHash; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + + void + testLedgerEntryRippleState() + { + testcase("ledger_entry Request RippleState"); + using namespace test::jtx; + Env env{*this}; + Account const alice{"alice"}; + Account const gw{"gateway"}; + auto const USD = gw["USD"]; + env.fund(XRP(10000), alice, gw); + env.close(); + + env.trust(USD(999), alice); + env.close(); + + env(pay(gw, alice, USD(97))); + env.close(); + + // check both aliases + for (auto const& fieldName : {jss::ripple_state, jss::state}) + { + 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", ""); + } + } + } + + void + testLedgerEntryTicket() + { + testcase("ledger_entry Request Ticket"); + using namespace test::jtx; + Env env{*this}; + env.close(); + + // Create two tickets. + std::uint32_t const tkt1{env.seq(env.master) + 1}; + env(ticket::create(env.master, 2)); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + // Request four tickets: one before the first one we created, the + // two created tickets, and the ticket that would come after the + // last created ticket. + { + // Not a valid ticket requested by index. + Json::Value jvParams; + jvParams[jss::ticket] = + to_string(getTicketIndex(env.master, tkt1 - 1)); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + { + // First real ticket requested by index. + Json::Value jvParams; + jvParams[jss::ticket] = to_string(getTicketIndex(env.master, tkt1)); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Ticket); + BEAST_EXPECT(jrr[jss::node][sfTicketSequence.jsonName] == tkt1); + } + { + // Second real ticket requested by account and sequence. + Json::Value jvParams; + jvParams[jss::ticket] = Json::objectValue; + jvParams[jss::ticket][jss::account] = env.master.human(); + jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][jss::index] == + to_string(getTicketIndex(env.master, tkt1 + 1))); + } + { + // Not a valid ticket requested by account and sequence. + Json::Value jvParams; + jvParams[jss::ticket] = Json::objectValue; + jvParams[jss::ticket][jss::account] = env.master.human(); + jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 2; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + { + // Request a ticket using an account root entry. + Json::Value jvParams; + jvParams[jss::ticket] = to_string(keylet::account(env.master).key); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "unexpectedLedgerType", ""); + } + { + // Malformed account entry. + Json::Value jvParams; + jvParams[jss::ticket] = Json::objectValue; + + std::string const badAddress = makeBadAddress(env.master.human()); + jvParams[jss::ticket][jss::account] = badAddress; + jvParams[jss::ticket][jss::ticket_seq] = env.seq(env.master) - 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedAddress", ""); + } + { + // Malformed ticket object. Missing account member. + Json::Value jvParams; + jvParams[jss::ticket] = Json::objectValue; + jvParams[jss::ticket][jss::ticket_seq] = env.seq(env.master) - 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed ticket object. Missing seq member. + Json::Value jvParams; + jvParams[jss::ticket] = Json::objectValue; + jvParams[jss::ticket][jss::account] = env.master.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed ticket object. Non-integral seq member. + Json::Value jvParams; + jvParams[jss::ticket] = Json::objectValue; + jvParams[jss::ticket][jss::account] = env.master.human(); + jvParams[jss::ticket][jss::ticket_seq] = + std::to_string(env.seq(env.master) - 1); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + } + + void + testLedgerEntryDID() + { + testcase("ledger_entry Request DID"); + using namespace test::jtx; + using namespace std::literals::chrono_literals; + Env env{*this, supported_amendments() | featureDID}; + Account const alice{"alice"}; + + env.fund(XRP(10000), alice); + env.close(); + + // Lambda to create a DID. + auto didCreate = [](test::jtx::Account const& account) { + Json::Value jv; + jv[jss::TransactionType] = jss::DIDSet; + jv[jss::Account] = account.human(); + jv[sfDIDDocument.jsonName] = strHex(std::string{"data"}); + jv[sfURI.jsonName] = strHex(std::string{"uri"}); + return jv; + }; + + env(didCreate(alice)); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + + { + // Request the DID using its index. + Json::Value jvParams; + jvParams[jss::did] = alice.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfDIDDocument.jsonName] == + strHex(std::string{"data"})); + BEAST_EXPECT( + jrr[jss::node][sfURI.jsonName] == strHex(std::string{"uri"})); + } + { + // Request an index that is not a DID. + Json::Value jvParams; + jvParams[jss::did] = env.master.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + + void + testLedgerEntryInvalidParams(unsigned int apiVersion) + { + testcase( + "ledger_entry Request With Invalid Parameters v" + + std::to_string(apiVersion)); + using namespace test::jtx; + Env env{*this}; + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + + auto makeParams = [&apiVersion](std::function f) { + Json::Value params; + params[jss::api_version] = apiVersion; + f(params); + return params; + }; + // "features" is not an option supported by ledger_entry. + { + auto const jvParams = + makeParams([&ledgerHash](Json::Value& jvParams) { + jvParams[jss::features] = ledgerHash; + jvParams[jss::ledger_hash] = ledgerHash; + }); + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + if (apiVersion < 2u) + checkErrorValue(jrr, "unknownOption", ""); + else + checkErrorValue(jrr, "invalidParams", ""); + } + Json::Value const injectObject = []() { + Json::Value obj(Json::objectValue); + obj[jss::account] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU"; + obj[jss::ledger_index] = "validated"; + return obj; + }(); + Json::Value const injectArray = []() { + Json::Value arr(Json::arrayValue); + arr[0u] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU"; + arr[1u] = "validated"; + return arr; + }(); + + // invalid input for fields that can handle an object, but can't handle + // an array + for (auto const& field : + {jss::directory, jss::escrow, jss::offer, jss::ticket, jss::amm}) + { + auto const jvParams = + makeParams([&field, &injectArray](Json::Value& jvParams) { + jvParams[field] = injectArray; + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + if (apiVersion < 2u) + checkErrorValue(jrr, "internal", "Internal error."); + else + checkErrorValue(jrr, "invalidParams", ""); + } + // Fields that can handle objects just fine + for (auto const& field : + {jss::directory, jss::escrow, jss::offer, jss::ticket, jss::amm}) + { + auto const jvParams = + makeParams([&field, &injectObject](Json::Value& jvParams) { + jvParams[field] = injectObject; + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + checkErrorValue(jrr, "malformedRequest", ""); + } + + for (auto const& inject : {injectObject, injectArray}) + { + // invalid input for fields that can't handle an object or an array + for (auto const& field : + {jss::index, + jss::account_root, + jss::check, + jss::payment_channel}) + { + auto const jvParams = + makeParams([&field, &inject](Json::Value& jvParams) { + jvParams[field] = inject; + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + if (apiVersion < 2u) + checkErrorValue(jrr, "internal", "Internal error."); + else + checkErrorValue(jrr, "invalidParams", ""); + } + // directory sub-fields + for (auto const& field : {jss::dir_root, jss::owner}) + { + auto const jvParams = + makeParams([&field, &inject](Json::Value& jvParams) { + jvParams[jss::directory][field] = inject; + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + if (apiVersion < 2u) + checkErrorValue(jrr, "internal", "Internal error."); + else + checkErrorValue(jrr, "invalidParams", ""); + } + // escrow sub-fields + { + auto const jvParams = + makeParams([&inject](Json::Value& jvParams) { + jvParams[jss::escrow][jss::owner] = inject; + jvParams[jss::escrow][jss::seq] = 99; + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + if (apiVersion < 2u) + checkErrorValue(jrr, "internal", "Internal error."); + else + checkErrorValue(jrr, "invalidParams", ""); + } + // offer sub-fields + { + auto const jvParams = + makeParams([&inject](Json::Value& jvParams) { + jvParams[jss::offer][jss::account] = inject; + jvParams[jss::offer][jss::seq] = 99; + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + if (apiVersion < 2u) + checkErrorValue(jrr, "internal", "Internal error."); + else + checkErrorValue(jrr, "invalidParams", ""); + } + // ripple_state sub-fields + { + auto const jvParams = + makeParams([&inject](Json::Value& jvParams) { + Json::Value rs(Json::objectValue); + rs[jss::currency] = "FOO"; + rs[jss::accounts] = Json::Value(Json::arrayValue); + rs[jss::accounts][0u] = + "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU"; + rs[jss::accounts][1u] = + "rKssEq6pg1KbqEqAFnua5mFAL6Ggpsh2wv"; + rs[jss::currency] = inject; + jvParams[jss::ripple_state] = std::move(rs); + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + if (apiVersion < 2u) + checkErrorValue(jrr, "internal", "Internal error."); + else + checkErrorValue(jrr, "invalidParams", ""); + } + // ticket sub-fields + { + auto const jvParams = + makeParams([&inject](Json::Value& jvParams) { + jvParams[jss::ticket][jss::account] = inject; + jvParams[jss::ticket][jss::ticket_seq] = 99; + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + if (apiVersion < 2u) + checkErrorValue(jrr, "internal", "Internal error."); + else + checkErrorValue(jrr, "invalidParams", ""); + } + + // Fields that can handle malformed inputs just fine + for (auto const& field : {jss::nft_page, jss::deposit_preauth}) + { + auto const jvParams = + makeParams([&field, &inject](Json::Value& jvParams) { + jvParams[field] = inject; + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + checkErrorValue(jrr, "malformedRequest", ""); + } + // Subfields of deposit_preauth that can handle malformed inputs + // fine + for (auto const& field : {jss::owner, jss::authorized}) + { + auto const jvParams = + makeParams([&field, &inject](Json::Value& jvParams) { + auto pa = Json::Value(Json::objectValue); + pa[jss::owner] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU"; + pa[jss::authorized] = + "rKssEq6pg1KbqEqAFnua5mFAL6Ggpsh2wv"; + pa[field] = inject; + jvParams[jss::deposit_preauth] = std::move(pa); + }); + + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + checkErrorValue(jrr, "malformedRequest", ""); + } + } + } + + void + testInvalidOracleLedgerEntry() + { + testcase("Invalid Oracle Ledger Entry"); + using namespace ripple::test::jtx; + using namespace ripple::test::jtx::oracle; + + Env env(*this); + Account const owner("owner"); + env.fund(XRP(1'000), owner); + Oracle oracle( + env, + {.owner = owner, + .fee = static_cast(env.current()->fees().base.drops())}); + + // Malformed document id + auto res = Oracle::ledgerEntry(env, owner, NoneTag); + BEAST_EXPECT(res[jss::error].asString() == "invalidParams"); + std::vector invalid = {-1, 1.2, "", "Invalid"}; + for (auto const& v : invalid) + { + auto const res = Oracle::ledgerEntry(env, owner, v); + BEAST_EXPECT(res[jss::error].asString() == "malformedDocumentID"); + } + // Missing document id + res = Oracle::ledgerEntry(env, owner, std::nullopt); + BEAST_EXPECT(res[jss::error].asString() == "malformedRequest"); + + // Missing account + res = Oracle::ledgerEntry(env, std::nullopt, 1); + BEAST_EXPECT(res[jss::error].asString() == "malformedRequest"); + + // Malformed account + std::string malfAccount = to_string(owner.id()); + malfAccount.replace(10, 1, 1, '!'); + res = Oracle::ledgerEntry(env, malfAccount, 1); + BEAST_EXPECT(res[jss::error].asString() == "malformedAddress"); + } + + void + testOracleLedgerEntry() + { + testcase("Oracle Ledger Entry"); + using namespace ripple::test::jtx; + using namespace ripple::test::jtx::oracle; + + Env env(*this); + auto const baseFee = + static_cast(env.current()->fees().base.drops()); + std::vector accounts; + std::vector oracles; + for (int i = 0; i < 10; ++i) + { + Account const owner(std::string("owner") + std::to_string(i)); + env.fund(XRP(1'000), owner); + // different accounts can have the same asset pair + Oracle oracle( + env, {.owner = owner, .documentID = i, .fee = baseFee}); + accounts.push_back(owner.id()); + oracles.push_back(oracle.documentID()); + // same account can have different asset pair + Oracle oracle1( + env, {.owner = owner, .documentID = i + 10, .fee = baseFee}); + accounts.push_back(owner.id()); + oracles.push_back(oracle1.documentID()); + } + for (int i = 0; i < accounts.size(); ++i) + { + auto const jv = [&]() { + // document id is uint32 + if (i % 2) + return Oracle::ledgerEntry(env, accounts[i], oracles[i]); + // document id is string + return Oracle::ledgerEntry( + env, accounts[i], std::to_string(oracles[i])); + }(); + try + { + BEAST_EXPECT( + jv[jss::node][jss::Owner] == to_string(accounts[i])); + } + catch (...) + { + fail(); + } + } + } + + void + testLedgerEntryMPT() + { + testcase("ledger_entry Request MPT"); + using namespace test::jtx; + using namespace std::literals::chrono_literals; + Env env{*this, supported_amendments() | featureMPTokensV1}; + Account const alice{"alice"}; + Account const bob("bob"); + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.transferFee = 10, + .metadata = "123", + .ownerCount = 1, + .flags = tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | + tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback}); + mptAlice.authorize({.account = bob, .holderCount = 1}); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + + std::string const badMptID = + "00000193B9DDCAF401B5B3B26875986043F82CD0D13B4315"; + { + // Request the MPTIssuance using its MPTIssuanceID. + Json::Value jvParams; + jvParams[jss::mpt_issuance] = strHex(mptAlice.issuanceID()); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfMPTokenMetadata.jsonName] == + strHex(std::string{"123"})); + BEAST_EXPECT( + jrr[jss::node][jss::mpt_issuance_id] == + strHex(mptAlice.issuanceID())); + } + { + // Request an index that is not a MPTIssuance. + Json::Value jvParams; + jvParams[jss::mpt_issuance] = badMptID; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + { + // Request the MPToken using its owner + mptIssuanceID. + Json::Value jvParams; + jvParams[jss::mptoken] = Json::objectValue; + jvParams[jss::mptoken][jss::account] = bob.human(); + jvParams[jss::mptoken][jss::mpt_issuance_id] = + strHex(mptAlice.issuanceID()); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfMPTokenIssuanceID.jsonName] == + strHex(mptAlice.issuanceID())); + } + { + // Request the MPToken using a bad mptIssuanceID. + Json::Value jvParams; + jvParams[jss::mptoken] = Json::objectValue; + jvParams[jss::mptoken][jss::account] = bob.human(); + jvParams[jss::mptoken][jss::mpt_issuance_id] = badMptID; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + + void + testLedgerEntryPermissionedDomain() + { + testcase("ledger_entry PermissionedDomain"); + + using namespace test::jtx; + + Env env( + *this, + supported_amendments() | featureCredentials | + featurePermissionedDomains); + Account const issuer{"issuer"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + + env.fund(XRP(5000), issuer, alice, bob); + env.close(); + + auto const seq = env.seq(alice); + env(pdomain::setTx(alice, {{alice, "first credential"}})); + env.close(); + auto const objects = pdomain::getObjects(alice, env); + if (!BEAST_EXPECT(objects.size() == 1)) + return; + + { + // Succeed + Json::Value params; + params[jss::ledger_index] = jss::validated; + params[jss::permissioned_domain][jss::account] = alice.human(); + params[jss::permissioned_domain][jss::seq] = seq; + auto jv = env.rpc("json", "ledger_entry", to_string(params)); + BEAST_EXPECT( + jv.isObject() && jv.isMember(jss::result) && + !jv[jss::result].isMember(jss::error) && + jv[jss::result].isMember(jss::node) && + jv[jss::result][jss::node].isMember( + sfLedgerEntryType.jsonName) && + jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == + jss::PermissionedDomain); + + std::string const pdIdx = jv[jss::result][jss::index].asString(); + BEAST_EXPECT( + strHex(keylet::permissionedDomain(alice, seq).key) == pdIdx); + + params.clear(); + params[jss::ledger_index] = jss::validated; + params[jss::permissioned_domain] = pdIdx; + jv = env.rpc("json", "ledger_entry", to_string(params)); + BEAST_EXPECT( + jv.isObject() && jv.isMember(jss::result) && + !jv[jss::result].isMember(jss::error) && + jv[jss::result].isMember(jss::node) && + jv[jss::result][jss::node].isMember( + sfLedgerEntryType.jsonName) && + jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == + jss::PermissionedDomain); + } + + { + // Fail, invalid permissioned domain index + Json::Value params; + params[jss::ledger_index] = jss::validated; + params[jss::permissioned_domain] = + "12F1F1F1F180D67377B2FAB292A31C922470326268D2B9B74CD1E582645B9A" + "DE"; + auto const jrr = env.rpc("json", "ledger_entry", to_string(params)); + checkErrorValue(jrr[jss::result], "entryNotFound", ""); + } + + { + // Fail, invalid permissioned domain index + Json::Value params; + params[jss::ledger_index] = jss::validated; + params[jss::permissioned_domain] = "NotAHexString"; + auto const jrr = env.rpc("json", "ledger_entry", to_string(params)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, permissioned domain is not an object + Json::Value params; + params[jss::ledger_index] = jss::validated; + params[jss::permissioned_domain] = 10; + auto const jrr = env.rpc("json", "ledger_entry", to_string(params)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + + { + // Fail, invalid account + Json::Value params; + params[jss::ledger_index] = jss::validated; + params[jss::permissioned_domain][jss::account] = 1; + params[jss::permissioned_domain][jss::seq] = seq; + auto const jrr = env.rpc("json", "ledger_entry", to_string(params)); + checkErrorValue(jrr[jss::result], "malformedAddress", ""); + } + + { + // Fail, account is an object + Json::Value params; + params[jss::ledger_index] = jss::validated; + params[jss::permissioned_domain][jss::account] = + Json::Value{Json::ValueType::objectValue}; + params[jss::permissioned_domain][jss::seq] = seq; + auto const jrr = env.rpc("json", "ledger_entry", to_string(params)); + checkErrorValue(jrr[jss::result], "malformedAddress", ""); + } + + { + // Fail, no account + Json::Value params; + params[jss::ledger_index] = jss::validated; + params[jss::permissioned_domain][jss::account] = ""; + params[jss::permissioned_domain][jss::seq] = seq; + auto const jrr = env.rpc("json", "ledger_entry", to_string(params)); + checkErrorValue(jrr[jss::result], "malformedAddress", ""); + } + + { + // Fail, invalid sequence + Json::Value params; + params[jss::ledger_index] = jss::validated; + params[jss::permissioned_domain][jss::account] = alice.human(); + params[jss::permissioned_domain][jss::seq] = "12g"; + auto const jrr = env.rpc("json", "ledger_entry", to_string(params)); + checkErrorValue(jrr[jss::result], "malformedRequest", ""); + } + } + + void + testLedgerEntryURIToken() + { + testcase("ledger_entry Request URIToken"); + using namespace test::jtx; + Env env{*this}; + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + // Lambda to create an uritoken. + auto mint = [](test::jtx::Account const& account, + std::string const& uri) { + Json::Value jv; + jv[jss::TransactionType] = jss::URITokenMint; + jv[jss::Flags] = tfBurnable; + jv[jss::Account] = account.human(); + jv[sfURI.jsonName] = strHex(uri); + return jv; + }; + + using namespace std::chrono_literals; + std::string const uri(maxTokenURILength, '?'); + env(mint(alice, uri)); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + + uint256 const uritokenIndex{ + keylet::uritoken(alice, Blob(uri.begin(), uri.end())).key}; + { + // Request the uritoken using its index. + Json::Value jvParams; + jvParams[jss::uri_token] = to_string(uritokenIndex); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfURI.jsonName] == strHex(uri)); + BEAST_EXPECT(jrr[jss::node][sfFlags.jsonName] == lsfBurnable); + } + { + // Request the uritoken using its account and uri. + Json::Value jvParams; + jvParams[jss::uri_token] = Json::objectValue; + jvParams[jss::uri_token][jss::account] = alice.human(); + jvParams[jss::uri_token][jss::uri] = uri; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfURI.jsonName] == strHex(uri)); + BEAST_EXPECT(jrr[jss::node][sfFlags.jsonName] == lsfBurnable); + } + { + // Malformed uritoken object. Missing account member. + Json::Value jvParams; + jvParams[jss::uri_token] = Json::objectValue; + jvParams[jss::uri_token][jss::uri] = uri; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed uritoken object. Missing seq member. + Json::Value jvParams; + jvParams[jss::uri_token] = Json::objectValue; + jvParams[jss::uri_token][jss::account] = env.master.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Request an index that is not a uritoken. + Json::Value jvParams; + jvParams[jss::uri_token] = ledgerHash; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + + 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 + { + testLedgerEntryInvalid(); + testLedgerEntryAccountRoot(); + testLedgerEntryCheck(); + testLedgerEntryCredentials(); + testLedgerEntryCron(); + testLedgerEntryDepositPreauth(); + testLedgerEntryDepositPreauthCred(); + testLedgerEntryDirectory(); + testLedgerEntryEscrow(); + testLedgerEntryHook(); + testLedgerEntryHookDefinition(); + testLedgerEntryHookState(); + testLedgerEntryImportVLSeq(); + testLedgerEntryOffer(); + testLedgerEntryPayChan(); + testLedgerEntryRippleState(); + testLedgerEntryTicket(); + testLedgerEntryDID(); + testInvalidOracleLedgerEntry(); + testOracleLedgerEntry(); + testLedgerEntryMPT(); + testLedgerEntryPermissionedDomain(); + testLedgerEntryURIToken(); + testLedgerEntryCLI(); + + forAllApiVersions(std::bind_front( + &LedgerEntry_test::testLedgerEntryInvalidParams, this)); + } +}; + +class LedgerEntry_XChain_test : public beast::unit_test::suite, + public test::jtx::XChainBridgeObjects +{ + void + checkErrorValue( + Json::Value const& jv, + std::string const& err, + std::string const& msg) + { + if (BEAST_EXPECT(jv.isMember(jss::status))) + BEAST_EXPECT(jv[jss::status] == "error"); + if (BEAST_EXPECT(jv.isMember(jss::error))) + BEAST_EXPECT(jv[jss::error] == err); + if (msg.empty()) + { + BEAST_EXPECT( + jv[jss::error_message] == Json::nullValue || + jv[jss::error_message] == ""); + } + else if (BEAST_EXPECT(jv.isMember(jss::error_message))) + BEAST_EXPECT(jv[jss::error_message] == msg); + } + + void + testLedgerEntryBridge() + { + testcase("ledger_entry: bridge"); + using namespace test::jtx; + + Env mcEnv{*this, features}; + Env scEnv(*this, envconfig(), features); + + createBridgeObjects(mcEnv, scEnv); + + std::string const ledgerHash{to_string(mcEnv.closed()->info().hash)}; + std::string bridge_index; + Json::Value mcBridge; + { + // request the bridge via RPC + Json::Value jvParams; + jvParams[jss::bridge_account] = mcDoor.human(); + jvParams[jss::bridge] = jvb; + Json::Value const jrr = mcEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + BEAST_EXPECT(jrr.isMember(jss::node)); + auto r = jrr[jss::node]; + // std::cout << to_string(r) << '\n'; + + BEAST_EXPECT(r.isMember(jss::Account)); + BEAST_EXPECT(r[jss::Account] == mcDoor.human()); + + BEAST_EXPECT(r.isMember(jss::Flags)); + + BEAST_EXPECT(r.isMember(sfLedgerEntryType.jsonName)); + BEAST_EXPECT(r[sfLedgerEntryType.jsonName] == jss::Bridge); + + // we not created an account yet + BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName)); + BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 0); + + // we have not claimed a locking chain tx yet + BEAST_EXPECT(r.isMember(sfXChainAccountClaimCount.jsonName)); + BEAST_EXPECT(r[sfXChainAccountClaimCount.jsonName].asInt() == 0); + + BEAST_EXPECT(r.isMember(jss::index)); + bridge_index = r[jss::index].asString(); + mcBridge = r; + } + { + // request the bridge via RPC by index + Json::Value jvParams; + jvParams[jss::index] = bridge_index; + Json::Value const jrr = mcEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + BEAST_EXPECT(jrr.isMember(jss::node)); + BEAST_EXPECT(jrr[jss::node] == mcBridge); + } + { + // swap door accounts and make sure we get an error value + Json::Value jvParams; + // Sidechain door account is "master", not scDoor + jvParams[jss::bridge_account] = Account::master.human(); + jvParams[jss::bridge] = jvb; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = mcEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + checkErrorValue(jrr, "entryNotFound", ""); + } + { + // create two claim ids and verify that the bridge counter was + // incremented + mcEnv(xchain_create_claim_id(mcAlice, jvb, reward, scAlice)); + mcEnv.close(); + mcEnv(xchain_create_claim_id(mcBob, jvb, reward, scBob)); + mcEnv.close(); + + // request the bridge via RPC + Json::Value jvParams; + jvParams[jss::bridge_account] = mcDoor.human(); + jvParams[jss::bridge] = jvb; + // std::cout << to_string(jvParams) << '\n'; + Json::Value const jrr = mcEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + BEAST_EXPECT(jrr.isMember(jss::node)); + auto r = jrr[jss::node]; + + // we executed two create claim id txs + BEAST_EXPECT(r.isMember(sfXChainClaimID.jsonName)); + BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2); + } + } + + void + testLedgerEntryClaimID() + { + testcase("ledger_entry: xchain_claim_id"); + using namespace test::jtx; + + Env mcEnv{*this, features}; + Env scEnv(*this, envconfig(), features); + + createBridgeObjects(mcEnv, scEnv); + + scEnv(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)); + scEnv.close(); + scEnv(xchain_create_claim_id(scBob, jvb, reward, mcBob)); + scEnv.close(); + + std::string bridge_index; + { + // request the xchain_claim_id via RPC + Json::Value jvParams; + jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC; + jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] = + 1; + // std::cout << to_string(jvParams) << '\n'; + Json::Value const jrr = scEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + BEAST_EXPECT(jrr.isMember(jss::node)); + auto r = jrr[jss::node]; + // std::cout << to_string(r) << '\n'; + + BEAST_EXPECT(r.isMember(jss::Account)); + BEAST_EXPECT(r[jss::Account] == scAlice.human()); + BEAST_EXPECT( + r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID); + BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 1); + BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0); + } + + { + // request the xchain_claim_id via RPC + Json::Value jvParams; + jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC; + jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] = + 2; + Json::Value const jrr = scEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + + BEAST_EXPECT(jrr.isMember(jss::node)); + auto r = jrr[jss::node]; + // std::cout << to_string(r) << '\n'; + + BEAST_EXPECT(r.isMember(jss::Account)); + BEAST_EXPECT(r[jss::Account] == scBob.human()); + BEAST_EXPECT( + r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID); + BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2); + BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0); + } + } + + void + testLedgerEntryCreateAccountClaimID() + { + testcase("ledger_entry: xchain_create_account_claim_id"); + using namespace test::jtx; + + Env mcEnv{*this, features}; + Env scEnv(*this, envconfig(), features); + + // note: signers.size() and quorum are both 5 in createBridgeObjects + createBridgeObjects(mcEnv, scEnv); + + auto scCarol = + Account("scCarol"); // Don't fund it - it will be created with the + // xchain transaction + auto const amt = XRP(1000); + mcEnv(sidechain_xchain_account_create( + mcAlice, jvb, scCarol, amt, reward)); + mcEnv.close(); + + // send less than quorum of attestations (otherwise funds are + // immediately transferred and no "claim" object is created) + size_t constexpr num_attest = 3; + auto attestations = create_account_attestations( + scAttester, + jvb, + mcAlice, + amt, + reward, + payee, + /*wasLockingChainSend*/ true, + 1, + scCarol, + signers, + UT_XCHAIN_DEFAULT_NUM_SIGNERS); + for (size_t i = 0; i < num_attest; ++i) + { + scEnv(attestations[i]); + } + scEnv.close(); + + { + // request the create account claim_id via RPC + Json::Value jvParams; + jvParams[jss::xchain_owned_create_account_claim_id] = + jvXRPBridgeRPC; + jvParams[jss::xchain_owned_create_account_claim_id] + [jss::xchain_owned_create_account_claim_id] = 1; + // std::cout << to_string(jvParams) << '\n'; + Json::Value const jrr = scEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + // std::cout << to_string(jrr) << '\n'; + + BEAST_EXPECT(jrr.isMember(jss::node)); + auto r = jrr[jss::node]; + + BEAST_EXPECT(r.isMember(jss::Account)); + BEAST_EXPECT(r[jss::Account] == Account::master.human()); + + BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName)); + BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 1); + + BEAST_EXPECT( + r.isMember(sfXChainCreateAccountAttestations.jsonName)); + auto attest = r[sfXChainCreateAccountAttestations.jsonName]; + BEAST_EXPECT(attest.isArray()); + BEAST_EXPECT(attest.size() == 3); + BEAST_EXPECT(attest[Json::Value::UInt(0)].isMember( + sfXChainCreateAccountProofSig.jsonName)); + Json::Value a[num_attest]; + for (size_t i = 0; i < num_attest; ++i) + { + a[i] = attest[Json::Value::UInt(0)] + [sfXChainCreateAccountProofSig.jsonName]; + BEAST_EXPECT( + a[i].isMember(jss::Amount) && + a[i][jss::Amount].asInt() == 1000 * drop_per_xrp); + BEAST_EXPECT( + a[i].isMember(jss::Destination) && + a[i][jss::Destination] == scCarol.human()); + BEAST_EXPECT( + a[i].isMember(sfAttestationSignerAccount.jsonName) && + std::any_of( + signers.begin(), signers.end(), [&](signer const& s) { + return a[i][sfAttestationSignerAccount.jsonName] == + s.account.human(); + })); + BEAST_EXPECT( + a[i].isMember(sfAttestationRewardAccount.jsonName) && + std::any_of( + payee.begin(), + payee.end(), + [&](Account const& account) { + return a[i][sfAttestationRewardAccount.jsonName] == + account.human(); + })); + BEAST_EXPECT( + a[i].isMember(sfWasLockingChainSend.jsonName) && + a[i][sfWasLockingChainSend.jsonName] == 1); + BEAST_EXPECT( + a[i].isMember(sfSignatureReward.jsonName) && + a[i][sfSignatureReward.jsonName].asInt() == + 1 * drop_per_xrp); + } + } + + // complete attestations quorum - CreateAccountClaimID should not be + // present anymore + for (size_t i = num_attest; i < UT_XCHAIN_DEFAULT_NUM_SIGNERS; ++i) + { + scEnv(attestations[i]); + } + scEnv.close(); + { + // request the create account claim_id via RPC + Json::Value jvParams; + jvParams[jss::xchain_owned_create_account_claim_id] = + jvXRPBridgeRPC; + jvParams[jss::xchain_owned_create_account_claim_id] + [jss::xchain_owned_create_account_claim_id] = 1; + // std::cout << to_string(jvParams) << '\n'; + Json::Value const jrr = scEnv.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + +public: + void + run() override + { + testLedgerEntryBridge(); + testLedgerEntryClaimID(); + testLedgerEntryCreateAccountClaimID(); + } +}; + +BEAST_DEFINE_TESTSUITE(LedgerEntry, app, ripple); +BEAST_DEFINE_TESTSUITE(LedgerEntry_XChain, app, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index 043ac0230..1caeea188 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -17,7 +17,6 @@ */ //============================================================================== -#include #include #include #include @@ -29,325 +28,12 @@ #include #include #include -#include #include #include namespace ripple { -class LedgerRPC_XChain_test : public beast::unit_test::suite, - public test::jtx::XChainBridgeObjects -{ - void - checkErrorValue( - Json::Value const& jv, - std::string const& err, - std::string const& msg) - { - if (BEAST_EXPECT(jv.isMember(jss::status))) - BEAST_EXPECT(jv[jss::status] == "error"); - if (BEAST_EXPECT(jv.isMember(jss::error))) - BEAST_EXPECT(jv[jss::error] == err); - if (msg.empty()) - { - BEAST_EXPECT( - jv[jss::error_message] == Json::nullValue || - jv[jss::error_message] == ""); - } - else if (BEAST_EXPECT(jv.isMember(jss::error_message))) - BEAST_EXPECT(jv[jss::error_message] == msg); - } - - void - testLedgerEntryBridge() - { - testcase("ledger_entry: bridge"); - using namespace test::jtx; - - Env mcEnv{*this, features}; - Env scEnv(*this, envconfig(), features); - - createBridgeObjects(mcEnv, scEnv); - - std::string const ledgerHash{to_string(mcEnv.closed()->info().hash)}; - std::string bridge_index; - Json::Value mcBridge; - { - // request the bridge via RPC - Json::Value jvParams; - jvParams[jss::bridge_account] = mcDoor.human(); - jvParams[jss::bridge] = jvb; - Json::Value const jrr = mcEnv.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - BEAST_EXPECT(jrr.isMember(jss::node)); - auto r = jrr[jss::node]; - // std::cout << to_string(r) << '\n'; - - BEAST_EXPECT(r.isMember(jss::Account)); - BEAST_EXPECT(r[jss::Account] == mcDoor.human()); - - BEAST_EXPECT(r.isMember(jss::Flags)); - - BEAST_EXPECT(r.isMember(sfLedgerEntryType.jsonName)); - BEAST_EXPECT(r[sfLedgerEntryType.jsonName] == jss::Bridge); - - // we not created an account yet - BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName)); - BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 0); - - // we have not claimed a locking chain tx yet - BEAST_EXPECT(r.isMember(sfXChainAccountClaimCount.jsonName)); - BEAST_EXPECT(r[sfXChainAccountClaimCount.jsonName].asInt() == 0); - - BEAST_EXPECT(r.isMember(jss::index)); - bridge_index = r[jss::index].asString(); - mcBridge = r; - } - { - // request the bridge via RPC by index - Json::Value jvParams; - jvParams[jss::index] = bridge_index; - Json::Value const jrr = mcEnv.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - BEAST_EXPECT(jrr.isMember(jss::node)); - BEAST_EXPECT(jrr[jss::node] == mcBridge); - } - { - // swap door accounts and make sure we get an error value - Json::Value jvParams; - // Sidechain door account is "master", not scDoor - jvParams[jss::bridge_account] = Account::master.human(); - jvParams[jss::bridge] = jvb; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = mcEnv.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - checkErrorValue(jrr, "entryNotFound", ""); - } - { - // create two claim ids and verify that the bridge counter was - // incremented - mcEnv(xchain_create_claim_id(mcAlice, jvb, reward, scAlice)); - mcEnv.close(); - mcEnv(xchain_create_claim_id(mcBob, jvb, reward, scBob)); - mcEnv.close(); - - // request the bridge via RPC - Json::Value jvParams; - jvParams[jss::bridge_account] = mcDoor.human(); - jvParams[jss::bridge] = jvb; - // std::cout << to_string(jvParams) << '\n'; - Json::Value const jrr = mcEnv.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - BEAST_EXPECT(jrr.isMember(jss::node)); - auto r = jrr[jss::node]; - - // we executed two create claim id txs - BEAST_EXPECT(r.isMember(sfXChainClaimID.jsonName)); - BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2); - } - } - - void - testLedgerEntryClaimID() - { - testcase("ledger_entry: xchain_claim_id"); - using namespace test::jtx; - - Env mcEnv{*this, features}; - Env scEnv(*this, envconfig(), features); - - createBridgeObjects(mcEnv, scEnv); - - scEnv(xchain_create_claim_id(scAlice, jvb, reward, mcAlice)); - scEnv.close(); - scEnv(xchain_create_claim_id(scBob, jvb, reward, mcBob)); - scEnv.close(); - - std::string bridge_index; - { - // request the xchain_claim_id via RPC - Json::Value jvParams; - jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC; - jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] = - 1; - // std::cout << to_string(jvParams) << '\n'; - Json::Value const jrr = scEnv.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - BEAST_EXPECT(jrr.isMember(jss::node)); - auto r = jrr[jss::node]; - // std::cout << to_string(r) << '\n'; - - BEAST_EXPECT(r.isMember(jss::Account)); - BEAST_EXPECT(r[jss::Account] == scAlice.human()); - BEAST_EXPECT( - r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID); - BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 1); - BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0); - } - - { - // request the xchain_claim_id via RPC - Json::Value jvParams; - jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC; - jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] = - 2; - Json::Value const jrr = scEnv.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - BEAST_EXPECT(jrr.isMember(jss::node)); - auto r = jrr[jss::node]; - // std::cout << to_string(r) << '\n'; - - BEAST_EXPECT(r.isMember(jss::Account)); - BEAST_EXPECT(r[jss::Account] == scBob.human()); - BEAST_EXPECT( - r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID); - BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2); - BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0); - } - } - - void - testLedgerEntryCreateAccountClaimID() - { - testcase("ledger_entry: xchain_create_account_claim_id"); - using namespace test::jtx; - - Env mcEnv{*this, features}; - Env scEnv(*this, envconfig(), features); - - // note: signers.size() and quorum are both 5 in createBridgeObjects - createBridgeObjects(mcEnv, scEnv); - - auto scCarol = - Account("scCarol"); // Don't fund it - it will be created with the - // xchain transaction - auto const amt = XRP(1000); - mcEnv(sidechain_xchain_account_create( - mcAlice, jvb, scCarol, amt, reward)); - mcEnv.close(); - - // send less than quorum of attestations (otherwise funds are - // immediately transferred and no "claim" object is created) - size_t constexpr num_attest = 3; - auto attestations = create_account_attestations( - scAttester, - jvb, - mcAlice, - amt, - reward, - payee, - /*wasLockingChainSend*/ true, - 1, - scCarol, - signers, - UT_XCHAIN_DEFAULT_NUM_SIGNERS); - for (size_t i = 0; i < num_attest; ++i) - { - scEnv(attestations[i]); - } - scEnv.close(); - - { - // request the create account claim_id via RPC - Json::Value jvParams; - jvParams[jss::xchain_owned_create_account_claim_id] = - jvXRPBridgeRPC; - jvParams[jss::xchain_owned_create_account_claim_id] - [jss::xchain_owned_create_account_claim_id] = 1; - // std::cout << to_string(jvParams) << '\n'; - Json::Value const jrr = scEnv.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - // std::cout << to_string(jrr) << '\n'; - - BEAST_EXPECT(jrr.isMember(jss::node)); - auto r = jrr[jss::node]; - - BEAST_EXPECT(r.isMember(jss::Account)); - BEAST_EXPECT(r[jss::Account] == Account::master.human()); - - BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName)); - BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 1); - - BEAST_EXPECT( - r.isMember(sfXChainCreateAccountAttestations.jsonName)); - auto attest = r[sfXChainCreateAccountAttestations.jsonName]; - BEAST_EXPECT(attest.isArray()); - BEAST_EXPECT(attest.size() == 3); - BEAST_EXPECT(attest[Json::Value::UInt(0)].isMember( - sfXChainCreateAccountProofSig.jsonName)); - Json::Value a[num_attest]; - for (size_t i = 0; i < num_attest; ++i) - { - a[i] = attest[Json::Value::UInt(0)] - [sfXChainCreateAccountProofSig.jsonName]; - BEAST_EXPECT( - a[i].isMember(jss::Amount) && - a[i][jss::Amount].asInt() == 1000 * drop_per_xrp); - BEAST_EXPECT( - a[i].isMember(jss::Destination) && - a[i][jss::Destination] == scCarol.human()); - BEAST_EXPECT( - a[i].isMember(sfAttestationSignerAccount.jsonName) && - std::any_of( - signers.begin(), signers.end(), [&](signer const& s) { - return a[i][sfAttestationSignerAccount.jsonName] == - s.account.human(); - })); - BEAST_EXPECT( - a[i].isMember(sfAttestationRewardAccount.jsonName) && - std::any_of( - payee.begin(), - payee.end(), - [&](Account const& account) { - return a[i][sfAttestationRewardAccount.jsonName] == - account.human(); - })); - BEAST_EXPECT( - a[i].isMember(sfWasLockingChainSend.jsonName) && - a[i][sfWasLockingChainSend.jsonName] == 1); - BEAST_EXPECT( - a[i].isMember(sfSignatureReward.jsonName) && - a[i][sfSignatureReward.jsonName].asInt() == - 1 * drop_per_xrp); - } - } - - // complete attestations quorum - CreateAccountClaimID should not be - // present anymore - for (size_t i = num_attest; i < UT_XCHAIN_DEFAULT_NUM_SIGNERS; ++i) - { - scEnv(attestations[i]); - } - scEnv.close(); - { - // request the create account claim_id via RPC - Json::Value jvParams; - jvParams[jss::xchain_owned_create_account_claim_id] = - jvXRPBridgeRPC; - jvParams[jss::xchain_owned_create_account_claim_id] - [jss::xchain_owned_create_account_claim_id] = 1; - // std::cout << to_string(jvParams) << '\n'; - Json::Value const jrr = scEnv.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "entryNotFound", ""); - } - } - -public: - void - run() override - { - testLedgerEntryBridge(); - testLedgerEntryClaimID(); - testLedgerEntryCreateAccountClaimID(); - } -}; +namespace test { class LedgerRPC_test : public beast::unit_test::suite { @@ -497,18 +183,6 @@ public: "json", "ledger", "{ \"ledger_index\" : 1000000000000000 }"); checkErrorValue(ret, "invalidParams", "Invalid parameters."); } - - { - // ask for an zero index - Json::Value jvParams; - jvParams[jss::ledger_index] = "validated"; - jvParams[jss::index] = - "00000000000000000000000000000000000000000000000000000000000000" - "0000"; - auto const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } } void @@ -529,25 +203,6 @@ public: } } - void - testMissingLedgerEntryLedgerHash() - { - testcase("Missing ledger_entry ledger_hash"); - using namespace test::jtx; - Env env{*this}; - Account const alice{"alice"}; - env.fund(XRP(10000), alice); - env.close(); - - Json::Value jvParams; - jvParams[jss::account_root] = alice.human(); - jvParams[jss::ledger_hash] = - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound"); - } - void testLedgerFull() { @@ -607,2562 +262,6 @@ public: BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 3u); } - void - testLedgerEntryAccountRoot() - { - testcase("ledger_entry Request AccountRoot"); - using namespace test::jtx; - - auto cfg = envconfig(); - cfg->FEES.reference_fee = 10; - Env env{ - *this, - std::move(cfg), - supported_amendments() - featureXahauGenesis - fixHookAPI20251128}; - Account const alice{"alice"}; - env.fund(XRP(10000), alice); - env.close(); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - { - // Exercise ledger_closed along the way. - Json::Value const jrr = env.rpc("ledger_closed")[jss::result]; - BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash); - BEAST_EXPECT(jrr[jss::ledger_index] == 3); - } - - std::string accountRootIndex; - { - // Request alice's account root. - Json::Value jvParams; - jvParams[jss::account_root] = alice.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr.isMember(jss::node)); - BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human()); - BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000"); - accountRootIndex = jrr[jss::index].asString(); - } - { - constexpr char alicesAcctRootBinary[]{ - "1100612200800000240000000425000000032D00000000559CE54C3B934E4" - "73A995B477E92EC229F99CED5B62BF4D2ACE4DC42719103AE2F6240000002" - "540BE4008114AE123A8556F3CF91154711376AFB0F894F832B3D"}; - - // Request alice's account root, but with binary == true; - Json::Value jvParams; - jvParams[jss::account_root] = alice.human(); - jvParams[jss::binary] = 1; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr.isMember(jss::node_binary)); - BEAST_EXPECT(jrr[jss::node_binary] == alicesAcctRootBinary); - } - { - // Request alice's account root using the index. - Json::Value jvParams; - jvParams[jss::index] = accountRootIndex; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(!jrr.isMember(jss::node_binary)); - BEAST_EXPECT(jrr.isMember(jss::node)); - BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human()); - BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000"); - } - { - // Request alice's account root by index, but with binary == false. - Json::Value jvParams; - jvParams[jss::index] = accountRootIndex; - jvParams[jss::binary] = 0; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr.isMember(jss::node)); - BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human()); - BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000"); - } - { - // Request using a corrupted AccountID. - Json::Value jvParams; - jvParams[jss::account_root] = makeBadAddress(alice.human()); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedAddress", ""); - } - { - // Request an account that is not in the ledger. - Json::Value jvParams; - jvParams[jss::account_root] = Account("bob").human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "entryNotFound", ""); - } - } - - void - testLedgerEntryCheck() - { - testcase("ledger_entry Request Check"); - 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 jvParams; - jvParams[jss::check] = to_string(checkId.key); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT( - jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check); - BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000"); - } - { - // Request an index that is not a check. We'll use alice's - // account root index. - std::string accountRootIndex; - { - Json::Value jvParams; - jvParams[jss::account_root] = alice.human(); - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - accountRootIndex = jrr[jss::index].asString(); - } - Json::Value jvParams; - jvParams[jss::check] = accountRootIndex; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "unexpectedLedgerType", ""); - } - } - - void - testLedgerEntryCredentials() - { - testcase("ledger_entry credentials"); - - using namespace test::jtx; - - Env env(*this, supported_amendments() | featureCredentials); - Account const issuer{"issuer"}; - Account const alice{"alice"}; - Account const bob{"bob"}; - const char credType[] = "abcde"; - - env.fund(XRP(5000), issuer, alice, bob); - env.close(); - - // Setup credentials with DepositAuth object for Alice and Bob - env(credentials::create(alice, issuer, credType)); - env.close(); - - { - // Succeed - auto jv = credentials::ledgerEntry(env, alice, issuer, credType); - BEAST_EXPECT( - jv.isObject() && jv.isMember(jss::result) && - !jv[jss::result].isMember(jss::error) && - jv[jss::result].isMember(jss::node) && - jv[jss::result][jss::node].isMember( - sfLedgerEntryType.jsonName) && - jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == - jss::Credential); - - std::string const credIdx = jv[jss::result][jss::index].asString(); - - jv = credentials::ledgerEntry(env, credIdx); - BEAST_EXPECT( - jv.isObject() && jv.isMember(jss::result) && - !jv[jss::result].isMember(jss::error) && - jv[jss::result].isMember(jss::node) && - jv[jss::result][jss::node].isMember( - sfLedgerEntryType.jsonName) && - jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == - jss::Credential); - } - - { - // Fail, index not a hash - auto const jv = credentials::ledgerEntry(env, ""); - checkErrorValue(jv[jss::result], "malformedRequest", ""); - } - - { - // Fail, credential doesn't exist - auto const jv = credentials::ledgerEntry( - env, - "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" - "E4"); - checkErrorValue(jv[jss::result], "entryNotFound", ""); - } - - { - // Fail, invalid subject - Json::Value jv; - jv[jss::ledger_index] = jss::validated; - jv[jss::credential][jss::subject] = 42; - jv[jss::credential][jss::issuer] = issuer.human(); - jv[jss::credential][jss::credential_type] = - strHex(std::string_view(credType)); - auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - - { - // Fail, invalid issuer - Json::Value jv; - jv[jss::ledger_index] = jss::validated; - jv[jss::credential][jss::subject] = alice.human(); - jv[jss::credential][jss::issuer] = 42; - jv[jss::credential][jss::credential_type] = - strHex(std::string_view(credType)); - auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - - { - // Fail, invalid credentials type - Json::Value jv; - jv[jss::ledger_index] = jss::validated; - jv[jss::credential][jss::subject] = alice.human(); - jv[jss::credential][jss::issuer] = issuer.human(); - jv[jss::credential][jss::credential_type] = 42; - auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - - { - // Fail, empty subject - Json::Value jv; - jv[jss::ledger_index] = jss::validated; - jv[jss::credential][jss::subject] = ""; - jv[jss::credential][jss::issuer] = issuer.human(); - jv[jss::credential][jss::credential_type] = - strHex(std::string_view(credType)); - auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - - { - // Fail, empty issuer - Json::Value jv; - jv[jss::ledger_index] = jss::validated; - jv[jss::credential][jss::subject] = alice.human(); - jv[jss::credential][jss::issuer] = ""; - jv[jss::credential][jss::credential_type] = - strHex(std::string_view(credType)); - auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - - { - // Fail, empty credentials type - Json::Value jv; - jv[jss::ledger_index] = jss::validated; - jv[jss::credential][jss::subject] = alice.human(); - jv[jss::credential][jss::issuer] = issuer.human(); - jv[jss::credential][jss::credential_type] = ""; - auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - - { - // Fail, no subject - Json::Value jv; - jv[jss::ledger_index] = jss::validated; - jv[jss::credential][jss::issuer] = issuer.human(); - jv[jss::credential][jss::credential_type] = - strHex(std::string_view(credType)); - auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - - { - // Fail, no issuer - Json::Value jv; - jv[jss::ledger_index] = jss::validated; - jv[jss::credential][jss::subject] = alice.human(); - jv[jss::credential][jss::credential_type] = - strHex(std::string_view(credType)); - auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - - { - // Fail, no credentials type - Json::Value jv; - jv[jss::ledger_index] = jss::validated; - jv[jss::credential][jss::subject] = alice.human(); - jv[jss::credential][jss::issuer] = issuer.human(); - auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - - { - // Fail, not AccountID subject - Json::Value jv; - jv[jss::ledger_index] = jss::validated; - jv[jss::credential][jss::subject] = "wehsdbvasbdfvj"; - jv[jss::credential][jss::issuer] = issuer.human(); - jv[jss::credential][jss::credential_type] = - strHex(std::string_view(credType)); - auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - - { - // Fail, not AccountID issuer - Json::Value jv; - jv[jss::ledger_index] = jss::validated; - jv[jss::credential][jss::subject] = alice.human(); - jv[jss::credential][jss::issuer] = "c4p93ugndfbsiu"; - jv[jss::credential][jss::credential_type] = - strHex(std::string_view(credType)); - auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - - { - // Fail, credentials type isn't hex encoded - Json::Value jv; - jv[jss::ledger_index] = jss::validated; - jv[jss::credential][jss::subject] = alice.human(); - jv[jss::credential][jss::issuer] = issuer.human(); - jv[jss::credential][jss::credential_type] = "12KK"; - auto const jrr = env.rpc("json", "ledger_entry", to_string(jv)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - } - - void - testLedgerEntryDepositPreauth() - { - testcase("ledger_entry Deposit Preauth"); - - using namespace test::jtx; - - Env env{*this}; - Account const alice{"alice"}; - Account const becky{"becky"}; - - env.fund(XRP(10000), alice, becky); - env.close(); - - env(deposit::auth(alice, becky)); - env.close(); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - std::string depositPreauthIndex; - { - // Request a depositPreauth by owner and authorized. - Json::Value jvParams; - jvParams[jss::deposit_preauth][jss::owner] = alice.human(); - jvParams[jss::deposit_preauth][jss::authorized] = becky.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - BEAST_EXPECT( - jrr[jss::node][sfLedgerEntryType.jsonName] == - jss::DepositPreauth); - BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human()); - BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human()); - depositPreauthIndex = jrr[jss::node][jss::index].asString(); - } - { - // Request a depositPreauth by index. - Json::Value jvParams; - jvParams[jss::deposit_preauth] = depositPreauthIndex; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - BEAST_EXPECT( - jrr[jss::node][sfLedgerEntryType.jsonName] == - jss::DepositPreauth); - BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human()); - BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human()); - } - { - // Malformed request: deposit_preauth neither object nor string. - Json::Value jvParams; - jvParams[jss::deposit_preauth] = -5; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed request: deposit_preauth not hex string. - Json::Value jvParams; - jvParams[jss::deposit_preauth] = "0123456789ABCDEFG"; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed request: missing [jss::deposit_preauth][jss::owner] - Json::Value jvParams; - jvParams[jss::deposit_preauth][jss::authorized] = becky.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed request: [jss::deposit_preauth][jss::owner] not string. - Json::Value jvParams; - jvParams[jss::deposit_preauth][jss::owner] = 7; - jvParams[jss::deposit_preauth][jss::authorized] = becky.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed: missing [jss::deposit_preauth][jss::authorized] - Json::Value jvParams; - jvParams[jss::deposit_preauth][jss::owner] = alice.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed: [jss::deposit_preauth][jss::authorized] not string. - Json::Value jvParams; - jvParams[jss::deposit_preauth][jss::owner] = alice.human(); - jvParams[jss::deposit_preauth][jss::authorized] = 47; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed: [jss::deposit_preauth][jss::owner] is malformed. - Json::Value jvParams; - jvParams[jss::deposit_preauth][jss::owner] = - "rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas"; - - jvParams[jss::deposit_preauth][jss::authorized] = becky.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedOwner", ""); - } - { - // Malformed: [jss::deposit_preauth][jss::authorized] is malformed. - Json::Value jvParams; - jvParams[jss::deposit_preauth][jss::owner] = alice.human(); - jvParams[jss::deposit_preauth][jss::authorized] = - "rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas"; - - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedAuthorized", ""); - } - } - - void - testLedgerEntryDepositPreauthCred() - { - testcase("ledger_entry Deposit Preauth with credentials"); - - using namespace test::jtx; - - Env env(*this, supported_amendments() | featureCredentials); - Account const issuer{"issuer"}; - Account const alice{"alice"}; - Account const bob{"bob"}; - const char credType[] = "abcde"; - - env.fund(XRP(5000), issuer, alice, bob); - env.close(); - - { - // Setup Bob with DepositAuth - env(fset(bob, asfDepositAuth)); - env.close(); - env(deposit::authCredentials(bob, {{issuer, credType}})); - env.close(); - } - - { - // Succeed - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - - Json::Value jo; - jo[jss::issuer] = issuer.human(); - jo[jss::credential_type] = strHex(std::string_view(credType)); - arr.append(std::move(jo)); - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - - BEAST_EXPECT( - jrr.isObject() && jrr.isMember(jss::result) && - !jrr[jss::result].isMember(jss::error) && - jrr[jss::result].isMember(jss::node) && - jrr[jss::result][jss::node].isMember( - sfLedgerEntryType.jsonName) && - jrr[jss::result][jss::node][sfLedgerEntryType.jsonName] == - jss::DepositPreauth); - } - - { - // Failed, invalid account - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - - Json::Value jo; - jo[jss::issuer] = to_string(xrpAccount()); - jo[jss::credential_type] = strHex(std::string_view(credType)); - arr.append(std::move(jo)); - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue( - jrr[jss::result], "malformedAuthorizedCredentials", ""); - } - - { - // Failed, duplicates in credentials - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - - Json::Value jo; - jo[jss::issuer] = issuer.human(); - jo[jss::credential_type] = strHex(std::string_view(credType)); - arr.append(jo); - arr.append(std::move(jo)); - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue( - jrr[jss::result], "malformedAuthorizedCredentials", ""); - } - - { - // Failed, invalid credential_type - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - - Json::Value jo; - jo[jss::issuer] = issuer.human(); - jo[jss::credential_type] = ""; - arr.append(std::move(jo)); - - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue( - jrr[jss::result], "malformedAuthorizedCredentials", ""); - } - - { - // Failed, authorized and authorized_credentials both present - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - jvParams[jss::deposit_preauth][jss::authorized] = alice.human(); - - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - - Json::Value jo; - jo[jss::issuer] = issuer.human(); - jo[jss::credential_type] = strHex(std::string_view(credType)); - arr.append(std::move(jo)); - - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - - { - // Failed, authorized_credentials is not an array - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - jvParams[jss::deposit_preauth][jss::authorized_credentials] = 42; - - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - - { - // Failed, authorized_credentials contains string data - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - arr.append("foobar"); - - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue( - jrr[jss::result], "malformedAuthorizedCredentials", ""); - } - - { - // Failed, authorized_credentials contains arrays - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - Json::Value payload = Json::arrayValue; - payload.append(42); - arr.append(std::move(payload)); - - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue( - jrr[jss::result], "malformedAuthorizedCredentials", ""); - } - - { - // Failed, authorized_credentials is empty array - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue( - jrr[jss::result], "malformedAuthorizedCredentials", ""); - } - - { - // Failed, authorized_credentials is too long - - static const std::string_view credTypes[] = { - "cred1", - "cred2", - "cred3", - "cred4", - "cred5", - "cred6", - "cred7", - "cred8", - "cred9"}; - static_assert( - sizeof(credTypes) / sizeof(credTypes[0]) > - maxCredentialsArraySize); - - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - - for (unsigned i = 0; i < sizeof(credTypes) / sizeof(credTypes[0]); - ++i) - { - Json::Value jo; - jo[jss::issuer] = issuer.human(); - jo[jss::credential_type] = - strHex(std::string_view(credTypes[i])); - arr.append(std::move(jo)); - } - - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue( - jrr[jss::result], "malformedAuthorizedCredentials", ""); - } - - { - // Failed, issuer is not set - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - - Json::Value jo; - jo[jss::credential_type] = strHex(std::string_view(credType)); - arr.append(std::move(jo)); - - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue( - jrr[jss::result], "malformedAuthorizedCredentials", ""); - } - - { - // Failed, issuer isn't string - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - - Json::Value jo; - jo[jss::issuer] = 42; - jo[jss::credential_type] = strHex(std::string_view(credType)); - arr.append(std::move(jo)); - - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue( - jrr[jss::result], "malformedAuthorizedCredentials", ""); - } - - { - // Failed, issuer is an array - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - - Json::Value jo; - Json::Value payload = Json::arrayValue; - payload.append(42); - jo[jss::issuer] = std::move(payload); - jo[jss::credential_type] = strHex(std::string_view(credType)); - arr.append(std::move(jo)); - - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue( - jrr[jss::result], "malformedAuthorizedCredentials", ""); - } - - { - // Failed, issuer isn't valid encoded account - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - - Json::Value jo; - jo[jss::issuer] = "invalid_account"; - jo[jss::credential_type] = strHex(std::string_view(credType)); - arr.append(std::move(jo)); - - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue( - jrr[jss::result], "malformedAuthorizedCredentials", ""); - } - - { - // Failed, credential_type is not set - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - - Json::Value jo; - jo[jss::issuer] = issuer.human(); - arr.append(std::move(jo)); - - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue( - jrr[jss::result], "malformedAuthorizedCredentials", ""); - } - - { - // Failed, credential_type isn't string - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - - Json::Value jo; - jo[jss::issuer] = issuer.human(); - jo[jss::credential_type] = 42; - arr.append(std::move(jo)); - - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue( - jrr[jss::result], "malformedAuthorizedCredentials", ""); - } - - { - // Failed, credential_type is an array - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - - Json::Value jo; - jo[jss::issuer] = issuer.human(); - Json::Value payload = Json::arrayValue; - payload.append(42); - jo[jss::credential_type] = std::move(payload); - arr.append(std::move(jo)); - - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue( - jrr[jss::result], "malformedAuthorizedCredentials", ""); - } - - { - // Failed, credential_type isn't hex encoded - Json::Value jvParams; - jvParams[jss::ledger_index] = jss::validated; - jvParams[jss::deposit_preauth][jss::owner] = bob.human(); - - jvParams[jss::deposit_preauth][jss::authorized_credentials] = - Json::arrayValue; - auto& arr( - jvParams[jss::deposit_preauth][jss::authorized_credentials]); - - Json::Value jo; - jo[jss::issuer] = issuer.human(); - jo[jss::credential_type] = "12KK"; - arr.append(std::move(jo)); - - auto const jrr = - env.rpc("json", "ledger_entry", to_string(jvParams)); - checkErrorValue( - jrr[jss::result], "malformedAuthorizedCredentials", ""); - } - } - - void - testLedgerEntryDirectory() - { - testcase("ledger_entry Request Directory"); - using namespace test::jtx; - Env env{*this}; - Account const alice{"alice"}; - Account const gw{"gateway"}; - auto const USD = gw["USD"]; - env.fund(XRP(10000), alice, gw); - env.close(); - - env.trust(USD(1000), alice); - env.close(); - - // Run up the number of directory entries so alice has two - // directory nodes. - for (int d = 1'000'032; d >= 1'000'000; --d) - { - env(offer(alice, USD(1), drops(d))); - } - env.close(); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - { - // Exercise ledger_closed along the way. - Json::Value const jrr = env.rpc("ledger_closed")[jss::result]; - BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash); - BEAST_EXPECT(jrr[jss::ledger_index] == 5); - } - - std::string const dirRootIndex = - "A33EC6BB85FB5674074C4A3A43373BB17645308F3EAE1933E3E35252162B217D"; - { - // Locate directory by index. - Json::Value jvParams; - jvParams[jss::directory] = dirRootIndex; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 32); - } - { - // Locate directory by directory root. - Json::Value jvParams; - jvParams[jss::directory] = Json::objectValue; - jvParams[jss::directory][jss::dir_root] = dirRootIndex; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::index] == dirRootIndex); - } - { - // Locate directory by owner. - Json::Value jvParams; - jvParams[jss::directory] = Json::objectValue; - jvParams[jss::directory][jss::owner] = alice.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::index] == dirRootIndex); - } - { - // Locate directory by directory root and sub_index. - Json::Value jvParams; - jvParams[jss::directory] = Json::objectValue; - jvParams[jss::directory][jss::dir_root] = dirRootIndex; - jvParams[jss::directory][jss::sub_index] = 1; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::index] != dirRootIndex); - BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2); - } - { - // Locate directory by owner and sub_index. - Json::Value jvParams; - jvParams[jss::directory] = Json::objectValue; - jvParams[jss::directory][jss::owner] = alice.human(); - jvParams[jss::directory][jss::sub_index] = 1; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::index] != dirRootIndex); - BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2); - } - { - // Null directory argument. - Json::Value jvParams; - jvParams[jss::directory] = Json::nullValue; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Non-integer sub_index. - Json::Value jvParams; - jvParams[jss::directory] = Json::objectValue; - jvParams[jss::directory][jss::dir_root] = dirRootIndex; - jvParams[jss::directory][jss::sub_index] = 1.5; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed owner entry. - Json::Value jvParams; - jvParams[jss::directory] = Json::objectValue; - - std::string const badAddress = makeBadAddress(alice.human()); - jvParams[jss::directory][jss::owner] = badAddress; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedAddress", ""); - } - { - // Malformed directory object. Specify both dir_root and owner. - Json::Value jvParams; - jvParams[jss::directory] = Json::objectValue; - jvParams[jss::directory][jss::owner] = alice.human(); - jvParams[jss::directory][jss::dir_root] = dirRootIndex; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Incomplete directory object. Missing both dir_root and owner. - Json::Value jvParams; - jvParams[jss::directory] = Json::objectValue; - jvParams[jss::directory][jss::sub_index] = 1; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - } - - void - testLedgerEntryEmittedTxn() - { - testcase("ledger_entry Request Emitted Txn"); - using namespace test::jtx; - using namespace std::literals::chrono_literals; - Env env{*this}; - Account const alice{"alice"}; - - env.fund(XRP(10000), alice); - env.close(); - - auto setHook = [](test::jtx::Account const& account) { - std::string const createCodeHex = - "0061736D01000000012E0760057F7F7F7F7F017E60017F017E60047F7F7F7F" - "017E60037F7F7E017E6000017E60027F7F017E60027F7F017F02AD010B0365" - "6E76057472616365000003656E760C6574786E5F7265736572766500010365" - "6E760A7574696C5F6163636964000203656E760974726163655F6E756D0003" - "03656E760A6C65646765725F736571000403656E760C686F6F6B5F6163636F" - "756E74000503656E760C6574786E5F64657461696C73000503656E760D6574" - "786E5F6665655F62617365000503656E7604656D6974000203656E76025F67" - "000603656E7606616363657074000303030201010503010002062B077F0141" - "B089040B7F004180080B7F0041AF090B7F004180080B7F0041B089040B7F00" - "41000B7F0041010B070F02046362616B000B04686F6F6B000C0A8F900002AC" - "800001017F230041106B220124002001200036020C419409411A41E9084119" - "410010001A200141106A240042000BDC8F0001017F230041A0046B22012400" - "2001200036029C04418209411141B6084110410010001A410110011A200120" - "014180046A411441C608412310023703F803200142E8073703F00341A80841" - "0D20012903F00310031A2001200141E0016A22003602DC01200120012903F0" - "033703B801200141003602B401200141003602B001200110043E02AC012001" - "41C0016A411410051A200141003A00AB0120012802DC0141123A0000200128" - "02DC0120012D00AB014108763A000120012802DC0120012D00AB013A000220" - "0120012802DC0141036A3602DC0120014180808080783602A401200141023A" - "00A30120012802DC0120012D00A301410F7141206A3A000020012802DC0120" - "012802A4014118763A000120012802DC0120012802A4014110763A00022001" - "2802DC0120012802A4014108763A000320012802DC0120012802A4013A0004" - "200120012802DC0141056A3602DC01200120012802B00136029C0120014103" - "3A009B0120012802DC0120012D009B01410F7141206A3A000020012802DC01" - "200128029C014118763A000120012802DC01200128029C014110763A000220" - "012802DC01200128029C014108763A000320012802DC01200128029C013A00" - "04200120012802DC0141056A3602DC012001410036029401200141043A0093" - "0120012802DC0120012D009301410F7141206A3A000020012802DC01200128" - "0294014118763A000120012802DC012001280294014110763A000220012802" - "DC012001280294014108763A000320012802DC012001280294013A00042001" - "20012802DC0141056A3602DC01200120012802B40136028C012001410E3A00" - "8B0120012802DC0120012D008B01410F7141206A3A000020012802DC012001" - "28028C014118763A000120012802DC01200128028C014110763A0002200128" - "02DC01200128028C014108763A000320012802DC01200128028C013A000420" - "0120012802DC0141056A3602DC01200120012802AC0141016A360284012001" - "411A3A00830120012802DC0141203A000020012802DC0120012D0083013A00" - "0120012802DC012001280284014118763A000220012802DC01200128028401" - "4110763A000320012802DC012001280284014108763A000420012802DC0120" - "01280284013A0005200120012802DC0141066A3602DC01200120012802AC01" - "41056A36027C2001411B3A007B20012802DC0141203A000020012802DC0120" - "012D007B3A000120012802DC01200128027C4118763A000220012802DC0120" - "0128027C4110763A000320012802DC01200128027C4108763A000420012802" - "DC01200128027C3A0005200120012802DC0141066A3602DC01200141013A00" - "7A200120012903B80137037020012802DC0120012D007A410F7141E0006A3A" - "000020012802DC012001290370423888423F8342407D3C000120012802DC01" - "200129037042308842FF01833C000220012802DC01200129037042288842FF" - "01833C000320012802DC01200129037042208842FF01833C000420012802DC" - "01200129037042188842FF01833C000520012802DC01200129037042108842" - "FF01833C000620012802DC01200129037042088842FF01833C000720012802" - "DC01200129037042FF01833C0008200120012802DC0141096A3602DC012001" - "20012802DC0136026C200141083A006B2001420037036020012802DC012001" - "2D006B410F7141E0006A3A000020012802DC012001290360423888423F8342" - "407D3C000120012802DC01200129036042308842FF01833C000220012802DC" - "01200129036042288842FF01833C000320012802DC01200129036042208842" - "FF01833C000420012802DC01200129036042188842FF01833C000520012802" - "DC01200129036042108842FF01833C000620012802DC012001290360420888" - "42FF01833C000720012802DC01200129036042FF01833C0008200120012802" - "DC0141096A3602DC0120012802DC0141F3003A000020012802DC0141213A00" - "0120012802DC01420037030220012802DC01420037030A20012802DC014200" - "37031220012802DC014200370319200120012802DC0141236A3602DC012001" - "41013A005F20012802DC0120012D005F4180016A3A000020012802DC014114" - "3A000120012802DC0120012903C00137030220012802DC0120012903C80137" - "030A20012802DC0120012802D001360212200120012802DC0141166A3602DC" - "01200141033A005E20012802DC0120012D005E4180016A3A000020012802DC" - "0141143A000120012802DC0120012903800437030220012802DC0120012903" - "880437030A20012802DC01200128029004360212200120012802DC0141166A" - "3602DC012001418E0220012802DC0120006B6BAD370350200120012802DC01" - "2001290350A7100637034820012000418E021007370340200141083A003F20" - "012001290340370330200128026C20012D003F410F7141E0006A3A00002001" - "28026C2001290330423888423F8342407D3C0001200128026C200129033042" - "308842FF01833C0002200128026C200129033042288842FF01833C00032001" - "28026C200129033042208842FF01833C0004200128026C2001290330421888" - "42FF01833C0005200128026C200129033042108842FF01833C000620012802" - "6C200129033042088842FF01833C0007200128026C200129033042FF01833C" - "00082001200128026C41096A36026C2001200141106A41202000418E021008" - "370308418008410B200129030810031A41012200200010091A418C08411C42" - "00100A1A200141A0046A240042000B0BB60101004180080BAE01656D69745F" - "726573756C7400436172626F6E3A20456D6974746564207472616E73616374" - "696F6E0064726F70735F746F5F73656E6400436172626F6E3A207374617274" - "6564007266436172626F6E564E547558636B5836783271544D466D46536E6D" - "36644557475800436172626F6E3A2063616C6C6261636B2063616C6C65642E" - "0022436172626F6E3A2073746172746564220022436172626F6E3A2063616C" - "6C6261636B2063616C6C65642E22"; - Json::Value jhv = hso(createCodeHex); - jhv[jss::HookOn] = - "fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffbfff" - "ff"; - Json::Value jv = ripple::test::jtx::hook(account, {{jhv}}, 0); - return jv; - }; - - env(setHook(alice), HSFEE); - env.close(); - - Json::Value invoke; - invoke[jss::TransactionType] = "Invoke"; - invoke[jss::Account] = alice.human(); - env(invoke, fee(XRP(1000))); - env.close(); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - - std::optional emithash; - { - auto meta = env.meta(); - auto const hookExecutions = meta->getFieldArray(sfHookExecutions); - for (auto const& node : meta->getFieldArray(sfAffectedNodes)) - { - SField const& metaType = node.getFName(); - uint16_t nodeType = node.getFieldU16(sfLedgerEntryType); - if (metaType == sfCreatedNode && nodeType == ltEMITTED_TXN) - { - auto const& nf = const_cast(node) - .getField(sfNewFields) - .downcast(); - auto const& et = const_cast(nf) - .getField(sfEmittedTxn) - .downcast(); - Blob txBlob = et.getSerializer().getData(); - auto const tx = std::make_unique( - Slice{txBlob.data(), txBlob.size()}); - emithash = tx->getTransactionID(); - break; - } - } - } - - std::string emittedTxnIndex = to_string(*emithash); - { - // Request the emitted txn using its index. - Json::Value jvParams; - jvParams[jss::emitted_txn] = emittedTxnIndex; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - Json::Value const emtTx = jrr[jss::node][jss::EmittedTxn]; - BEAST_EXPECT(emtTx[sfAccount.jsonName] == alice.human()); - BEAST_EXPECT(emtTx[sfAmount.jsonName] == "1000"); - BEAST_EXPECT( - emtTx[sfDestination.jsonName] == - "rfCarbonVNTuXckX6x2qTMFmFSnm6dEWGX"); - BEAST_EXPECT(emtTx[sfDestinationTag.jsonName] == 0); - } - { - // Request an index that is not a emitted txn. - Json::Value jvParams; - jvParams[jss::emitted_txn] = ledgerHash; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "entryNotFound", ""); - } - } - - void - testLedgerEntryEscrow() - { - testcase("ledger_entry Request Escrow"); - using namespace test::jtx; - Env env{*this}; - Account const alice{"alice"}; - env.fund(XRP(10000), alice); - env.close(); - - // Lambda to create an escrow. - auto escrowCreate = [](test::jtx::Account const& account, - test::jtx::Account const& to, - STAmount const& amount, - NetClock::time_point const& cancelAfter) { - Json::Value jv; - jv[jss::TransactionType] = jss::EscrowCreate; - jv[jss::Flags] = tfUniversal; - jv[jss::Account] = account.human(); - jv[jss::Destination] = to.human(); - jv[jss::Amount] = amount.getJson(JsonOptions::none); - jv[sfFinishAfter.jsonName] = - cancelAfter.time_since_epoch().count() + 2; - return jv; - }; - - using namespace std::chrono_literals; - env(escrowCreate(alice, alice, XRP(333), env.now() + 2s)); - env.close(); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - std::string escrowIndex; - { - // Request the escrow using owner and sequence. - Json::Value jvParams; - jvParams[jss::escrow] = Json::objectValue; - jvParams[jss::escrow][jss::owner] = alice.human(); - jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT( - jrr[jss::node][jss::Amount] == XRP(333).value().getText()); - escrowIndex = jrr[jss::index].asString(); - } - { - // Request the escrow by index. - Json::Value jvParams; - jvParams[jss::escrow] = escrowIndex; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT( - jrr[jss::node][jss::Amount] == XRP(333).value().getText()); - } - { - // Malformed owner entry. - Json::Value jvParams; - jvParams[jss::escrow] = Json::objectValue; - - std::string const badAddress = makeBadAddress(alice.human()); - jvParams[jss::escrow][jss::owner] = badAddress; - jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedOwner", ""); - } - { - // Missing owner. - Json::Value jvParams; - jvParams[jss::escrow] = Json::objectValue; - jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Missing sequence. - Json::Value jvParams; - jvParams[jss::escrow] = Json::objectValue; - jvParams[jss::escrow][jss::owner] = alice.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Non-integer sequence. - Json::Value jvParams; - jvParams[jss::escrow] = Json::objectValue; - jvParams[jss::escrow][jss::owner] = alice.human(); - jvParams[jss::escrow][jss::seq] = - std::to_string(env.seq(alice) - 1); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - } - - void - testLedgerEntryHook() - { - testcase("ledger_entry Request Hook"); - using namespace test::jtx; - Env env{*this}; - Account const alice{"alice"}; - env.fund(XRP(10000), alice); - env.close(); - - // Lambda to create a hook. - auto setHook = [](test::jtx::Account const& account) { - std::string const createCodeHex = - "0061736D0100000001130360037F7F7E017E60027F7F017F60017F017E0217" - "0203656E7606616363657074000003656E76025F6700010302010205030100" - "02062B077F01418088040B7F004180080B7F004180080B7F004180080B7F00" - "418088040B7F0041000B7F0041010B07080104686F6F6B00020AB5800001B1" - "800001017F230041106B220124002001200036020C41002200200042001000" - "1A41012200200010011A200141106A240042000B"; - Json::Value jv = - ripple::test::jtx::hook(account, {{hso(createCodeHex)}}, 0); - return jv; - }; - - using namespace std::chrono_literals; - env(setHook(alice), HSFEE); - env.close(); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - - std::string hookIndex; - { - // Request the hook using account. - Json::Value jvParams; - jvParams[jss::hook] = Json::objectValue; - jvParams[jss::hook][jss::account] = alice.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human()); - hookIndex = jrr[jss::index].asString(); - } - { - // Request the hook by index. - Json::Value jvParams; - jvParams[jss::hook] = hookIndex; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human()); - } - { - // Malformed account entry. - Json::Value jvParams; - jvParams[jss::hook] = Json::objectValue; - std::string const badAddress = makeBadAddress(alice.human()); - jvParams[jss::hook][jss::account] = badAddress; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedAddress", ""); - } - { - // Missing account. - Json::Value jvParams; - jvParams[jss::hook] = Json::objectValue; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - } - - void - testLedgerEntryHookDefinition() - { - testcase("ledger_entry Request Hook Definition"); - using namespace test::jtx; - Env env{*this}; - Account const alice{"alice"}; - env.fund(XRP(10000), alice); - env.close(); - - // Lambda to create a hook. - auto setHook = [](test::jtx::Account const& account) { - std::string const createCodeHex = - "0061736D0100000001130360037F7F7E017E60027F7F017F60017F017E0217" - "0203656E7606616363657074000003656E76025F6700010302010205030100" - "02062B077F01418088040B7F004180080B7F004180080B7F004180080B7F00" - "418088040B7F0041000B7F0041010B07080104686F6F6B00020AB5800001B1" - "800001017F230041106B220124002001200036020C41002200200042001000" - "1A41012200200010011A200141106A240042000B"; - Json::Value jv = - ripple::test::jtx::hook(account, {{hso(createCodeHex)}}, 0); - return jv; - }; - - using namespace std::chrono_literals; - env(setHook(alice), HSFEE); - env.close(); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - - auto const hook = env.le(keylet::hook(alice.id())); - auto const& hooks = hook->getFieldArray(sfHooks); - uint256 hookHash = hooks[0].getFieldH256(sfHookHash); - - { - // Request the hook_definition using hash. - Json::Value jvParams; - jvParams[jss::hook_definition] = to_string(hookHash); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::node][jss::HookHash] == to_string(hookHash)); - } - { - // Malformed hook_definition entry. - Json::Value jvParams; - jvParams[jss::hook_definition] = Json::objectValue; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Request an index that is not a payment channel. - Json::Value jvParams; - jvParams[jss::hook_definition] = ledgerHash; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "entryNotFound", ""); - } - } - - void - testLedgerEntryHookState() - { - testcase("ledger_entry Request Hook State"); - using namespace test::jtx; - Env env{*this}; - Account const alice{"alice"}; - Account const bob{"bob"}; - env.fund(XRP(10000), alice, bob); - env.close(); - - auto const key = uint256::fromVoid( - (std::array{ - 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, - 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, - 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, - 0x00U, 0x00U, 0x00U, 0x00U, 'k', 'e', 'y', 0x00U}) - .data()); - - auto const ns = uint256::fromVoid( - (std::array{ - 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, - 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, - 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, - 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU}) - .data()); - - auto const nons = uint256::fromVoid( - (std::array{ - 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, - 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, - 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, - 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFEU, 0xCAU, 0xFFU}) - .data()); - - // Lambda to create a hook. - auto setHook = [](test::jtx::Account const& account) { - std::string const createCodeHex = - "0061736D01000000011B0460027F7F017F60047F7F7F7F017E60037F7F7E01" - "7E60017F017E02270303656E76025F67000003656E760973746174655F7365" - "74000103656E76066163636570740002030201030503010002062B077F0141" - "9088040B7F004180080B7F00418A080B7F004180080B7F00419088040B7F00" - "41000B7F0041010B07080104686F6F6B00030AE7800001E3800002017F017E" - "230041106B220124002001200036020C41012200200010001A200141800828" - "0000360208200141046A410022002F0088083B010020012000280084083602" - "004100200020014106200141086A4104100110022102200141106A24002002" - "0B0B1001004180080B096B65790076616C7565"; - std::string ns_str = - "CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECA" - "FE"; - Json::Value jv = - ripple::test::jtx::hook(account, {{hso(createCodeHex)}}, 0); - jv[jss::Hooks][0U][jss::Hook][jss::HookNamespace] = ns_str; - return jv; - }; - - using namespace std::chrono_literals; - env(setHook(alice), HSFEE); - env.close(); - - env(pay(bob, alice, XRP(1)), fee(XRP(1))); - env.close(); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - - { - // Request the hook_state using hash. - Json::Value jvParams; - jvParams[jss::hook_state] = Json::objectValue; - jvParams[jss::hook_state][jss::account] = alice.human(); - jvParams[jss::hook_state][jss::key] = to_string(key); - jvParams[jss::hook_state][jss::namespace_id] = to_string(ns); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::node][jss::HookStateData] == "76616C756500"); - BEAST_EXPECT(jrr[jss::node][jss::HookStateKey] == to_string(key)); - } - { - // Malformed hook_state object. Missing account member. - Json::Value jvParams; - jvParams[jss::hook_state] = Json::objectValue; - jvParams[jss::hook_state][jss::key] = to_string(key); - jvParams[jss::hook_state][jss::namespace_id] = to_string(ns); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed hook_state object. Missing key member. - Json::Value jvParams; - jvParams[jss::hook_state] = Json::objectValue; - jvParams[jss::hook_state][jss::account] = alice.human(); - jvParams[jss::hook_state][jss::namespace_id] = to_string(ns); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed hook_state object. Missing namespace member. - Json::Value jvParams; - jvParams[jss::hook_state] = Json::objectValue; - jvParams[jss::hook_state][jss::account] = alice.human(); - jvParams[jss::hook_state][jss::key] = to_string(key); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed hook_state account. - Json::Value jvParams; - jvParams[jss::hook_state] = Json::objectValue; - jvParams[jss::hook_state][jss::account] = - makeBadAddress(alice.human()); - jvParams[jss::hook_state][jss::key] = to_string(key); - jvParams[jss::hook_state][jss::namespace_id] = to_string(ns); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedAddress", ""); - } - { - // Request a hook_state that doesn't exist. - Json::Value jvParams; - jvParams[jss::hook_state] = Json::objectValue; - jvParams[jss::hook_state][jss::account] = alice.human(); - jvParams[jss::hook_state][jss::key] = to_string(key); - jvParams[jss::hook_state][jss::namespace_id] = to_string(nons); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "entryNotFound", ""); - } - } - - void - testLedgerEntryOffer() - { - testcase("ledger_entry Request Offer"); - using namespace test::jtx; - Env env{*this}; - Account const alice{"alice"}; - Account const gw{"gateway"}; - auto const USD = gw["USD"]; - env.fund(XRP(10000), alice, gw); - env.close(); - - env(offer(alice, USD(321), XRP(322))); - env.close(); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - std::string offerIndex; - { - // Request the offer using owner and sequence. - Json::Value jvParams; - jvParams[jss::offer] = Json::objectValue; - jvParams[jss::offer][jss::account] = alice.human(); - jvParams[jss::offer][jss::seq] = env.seq(alice) - 1; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000"); - offerIndex = jrr[jss::index].asString(); - } - { - // Request the offer using its index. - Json::Value jvParams; - jvParams[jss::offer] = offerIndex; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000"); - } - { - // Malformed account entry. - Json::Value jvParams; - jvParams[jss::offer] = Json::objectValue; - - std::string const badAddress = makeBadAddress(alice.human()); - jvParams[jss::offer][jss::account] = badAddress; - jvParams[jss::offer][jss::seq] = env.seq(alice) - 1; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedAddress", ""); - } - { - // Malformed offer object. Missing account member. - Json::Value jvParams; - jvParams[jss::offer] = Json::objectValue; - jvParams[jss::offer][jss::seq] = env.seq(alice) - 1; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed offer object. Missing seq member. - Json::Value jvParams; - jvParams[jss::offer] = Json::objectValue; - jvParams[jss::offer][jss::account] = alice.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed offer object. Non-integral seq member. - Json::Value jvParams; - jvParams[jss::offer] = Json::objectValue; - jvParams[jss::offer][jss::account] = alice.human(); - jvParams[jss::offer][jss::seq] = std::to_string(env.seq(alice) - 1); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - } - - void - testLedgerEntryPayChan() - { - testcase("ledger_entry Request Pay Chan"); - using namespace test::jtx; - using namespace std::literals::chrono_literals; - Env env{*this}; - Account const alice{"alice"}; - - env.fund(XRP(10000), alice); - env.close(); - - // Lambda to create a PayChan. - auto payChanCreate = [](test::jtx::Account const& account, - test::jtx::Account const& to, - STAmount const& amount, - NetClock::duration const& settleDelay, - PublicKey const& pk) { - Json::Value jv; - jv[jss::TransactionType] = jss::PaymentChannelCreate; - jv[jss::Account] = account.human(); - jv[jss::Destination] = to.human(); - jv[jss::Amount] = amount.getJson(JsonOptions::none); - jv[sfSettleDelay.jsonName] = settleDelay.count(); - jv[sfPublicKey.jsonName] = strHex(pk.slice()); - return jv; - }; - - env(payChanCreate(alice, env.master, XRP(57), 18s, alice.pk())); - env.close(); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - - uint256 const payChanIndex{ - keylet::payChan(alice, env.master, env.seq(alice) - 1).key}; - { - // Request the payment channel using its index. - Json::Value jvParams; - jvParams[jss::payment_channel] = to_string(payChanIndex); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "57000000"); - BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "0"); - BEAST_EXPECT(jrr[jss::node][sfSettleDelay.jsonName] == 18); - } - { - // Request an index that is not a payment channel. - Json::Value jvParams; - jvParams[jss::payment_channel] = ledgerHash; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "entryNotFound", ""); - } - } - - void - testLedgerEntryRippleState() - { - testcase("ledger_entry Request RippleState"); - using namespace test::jtx; - Env env{*this}; - Account const alice{"alice"}; - Account const gw{"gateway"}; - auto const USD = gw["USD"]; - env.fund(XRP(10000), alice, gw); - env.close(); - - env.trust(USD(999), alice); - env.close(); - - env(pay(gw, alice, USD(97))); - env.close(); - - // check both aliases - for (auto const& fieldName : {jss::ripple_state, jss::state}) - { - 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", ""); - } - } - } - - void - testLedgerEntryTicket() - { - testcase("ledger_entry Request Ticket"); - using namespace test::jtx; - Env env{*this}; - env.close(); - - // Create two tickets. - std::uint32_t const tkt1{env.seq(env.master) + 1}; - env(ticket::create(env.master, 2)); - env.close(); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - // Request four tickets: one before the first one we created, the - // two created tickets, and the ticket that would come after the - // last created ticket. - { - // Not a valid ticket requested by index. - Json::Value jvParams; - jvParams[jss::ticket] = - to_string(getTicketIndex(env.master, tkt1 - 1)); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "entryNotFound", ""); - } - { - // First real ticket requested by index. - Json::Value jvParams; - jvParams[jss::ticket] = to_string(getTicketIndex(env.master, tkt1)); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT( - jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Ticket); - BEAST_EXPECT(jrr[jss::node][sfTicketSequence.jsonName] == tkt1); - } - { - // Second real ticket requested by account and sequence. - Json::Value jvParams; - jvParams[jss::ticket] = Json::objectValue; - jvParams[jss::ticket][jss::account] = env.master.human(); - jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 1; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT( - jrr[jss::node][jss::index] == - to_string(getTicketIndex(env.master, tkt1 + 1))); - } - { - // Not a valid ticket requested by account and sequence. - Json::Value jvParams; - jvParams[jss::ticket] = Json::objectValue; - jvParams[jss::ticket][jss::account] = env.master.human(); - jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 2; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "entryNotFound", ""); - } - { - // Request a ticket using an account root entry. - Json::Value jvParams; - jvParams[jss::ticket] = to_string(keylet::account(env.master).key); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "unexpectedLedgerType", ""); - } - { - // Malformed account entry. - Json::Value jvParams; - jvParams[jss::ticket] = Json::objectValue; - - std::string const badAddress = makeBadAddress(env.master.human()); - jvParams[jss::ticket][jss::account] = badAddress; - jvParams[jss::ticket][jss::ticket_seq] = env.seq(env.master) - 1; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedAddress", ""); - } - { - // Malformed ticket object. Missing account member. - Json::Value jvParams; - jvParams[jss::ticket] = Json::objectValue; - jvParams[jss::ticket][jss::ticket_seq] = env.seq(env.master) - 1; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed ticket object. Missing seq member. - Json::Value jvParams; - jvParams[jss::ticket] = Json::objectValue; - jvParams[jss::ticket][jss::account] = env.master.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed ticket object. Non-integral seq member. - Json::Value jvParams; - jvParams[jss::ticket] = Json::objectValue; - jvParams[jss::ticket][jss::account] = env.master.human(); - jvParams[jss::ticket][jss::ticket_seq] = - std::to_string(env.seq(env.master) - 1); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - } - - void - testLedgerEntryURIToken() - { - testcase("ledger_entry Request URIToken"); - using namespace test::jtx; - Env env{*this}; - Account const alice{"alice"}; - env.fund(XRP(10000), alice); - env.close(); - - // Lambda to create an uritoken. - auto mint = [](test::jtx::Account const& account, - std::string const& uri) { - Json::Value jv; - jv[jss::TransactionType] = jss::URITokenMint; - jv[jss::Flags] = tfBurnable; - jv[jss::Account] = account.human(); - jv[sfURI.jsonName] = strHex(uri); - return jv; - }; - - using namespace std::chrono_literals; - std::string const uri(maxTokenURILength, '?'); - env(mint(alice, uri)); - env.close(); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - - uint256 const uritokenIndex{ - keylet::uritoken(alice, Blob(uri.begin(), uri.end())).key}; - { - // Request the uritoken using its index. - Json::Value jvParams; - jvParams[jss::uri_token] = to_string(uritokenIndex); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); - BEAST_EXPECT(jrr[jss::node][sfURI.jsonName] == strHex(uri)); - BEAST_EXPECT(jrr[jss::node][sfFlags.jsonName] == lsfBurnable); - } - { - // Request the uritoken using its account and uri. - Json::Value jvParams; - jvParams[jss::uri_token] = Json::objectValue; - jvParams[jss::uri_token][jss::account] = alice.human(); - jvParams[jss::uri_token][jss::uri] = uri; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); - BEAST_EXPECT(jrr[jss::node][sfURI.jsonName] == strHex(uri)); - BEAST_EXPECT(jrr[jss::node][sfFlags.jsonName] == lsfBurnable); - } - { - // Malformed uritoken object. Missing account member. - Json::Value jvParams; - jvParams[jss::uri_token] = Json::objectValue; - jvParams[jss::uri_token][jss::uri] = uri; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed uritoken object. Missing seq member. - Json::Value jvParams; - jvParams[jss::uri_token] = Json::objectValue; - jvParams[jss::uri_token][jss::account] = env.master.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Request an index that is not a uritoken. - Json::Value jvParams; - jvParams[jss::uri_token] = ledgerHash; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "entryNotFound", ""); - } - } - - void - testLedgerEntryImportVLSeq() - { - testcase("ledger_entry Request ImportVLSeq"); - using namespace test::jtx; - - std::vector const keys = { - "ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC" - "1"}; - Env env{*this, network::makeNetworkVLConfig(21337, keys)}; - - Account const alice{"alice"}; - env.fund(XRP(10000), alice); - env.close(); - - auto const master = Account("masterpassphrase"); - env(noop(master), fee(10'000'000'000), ter(tesSUCCESS)); - env.close(); - env(import::import( - alice, import::loadXpop(test::ImportTCAccountSet::w_seed)), - fee(10 * 10), - ter(tesSUCCESS)); - env.close(); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - - auto const pk = PublicKey(makeSlice(*strUnHex(keys[0u]))); - uint256 const importvlIndex{keylet::import_vlseq(pk).key}; - { - // Request the import vl using its index. - Json::Value jvParams; - jvParams[jss::import_vlseq] = to_string(importvlIndex); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::node][sfPublicKey.jsonName] == keys[0u]); - } - { - // Request the import vl using its public key. - Json::Value jvParams; - jvParams[jss::import_vlseq] = Json::objectValue; - jvParams[jss::import_vlseq][jss::public_key] = keys[0u]; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::node][sfPublicKey.jsonName] == keys[0u]); - } - { - // Malformed import vl object. Missing public key. - Json::Value jvParams; - jvParams[jss::import_vlseq] = Json::objectValue; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed import vl object. Bad public key. - Json::Value jvParams; - jvParams[jss::import_vlseq] = Json::objectValue; - jvParams[jss::import_vlseq][jss::public_key] = 1; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed import vl object. Bad public key. - Json::Value jvParams; - jvParams[jss::import_vlseq] = Json::objectValue; - jvParams[jss::import_vlseq][jss::public_key] = "DEADBEEF"; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Request an index that is not a uritoken. - Json::Value jvParams; - jvParams[jss::import_vlseq] = ledgerHash; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "entryNotFound", ""); - } - } - - void - testLedgerEntryCron() - { - testcase("ledger_entry Request Cron"); - using namespace test::jtx; - - Env env{*this}; - - Account const alice{"alice"}; - env.fund(XRP(10000), alice); - env.close(); - - auto const startTime = - env.current()->parentCloseTime().time_since_epoch().count() + 100; - env(cron::set(alice), - cron::startTime(startTime), - cron::delay(100), - cron::repeat(200), - fee(XRP(1)), - ter(tesSUCCESS)); - - env.close(); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - - uint256 const cronIndex{keylet::cron(startTime, alice).key}; - { - // Request the cron using its index. - Json::Value jvParams; - jvParams[jss::cron] = to_string(cronIndex); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); - BEAST_EXPECT(jrr[jss::node][sfStartTime.jsonName] == startTime); - BEAST_EXPECT(jrr[jss::node][sfDelaySeconds.jsonName] == 100); - BEAST_EXPECT(jrr[jss::node][sfRepeatCount.jsonName] == 200); - } - { - // Request the cron using its owner and time. - Json::Value jvParams; - jvParams[jss::cron] = Json::objectValue; - jvParams[jss::cron][jss::owner] = alice.human(); - jvParams[jss::cron][jss::time] = startTime; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); - BEAST_EXPECT(jrr[jss::node][sfStartTime.jsonName] == startTime); - BEAST_EXPECT(jrr[jss::node][sfDelaySeconds.jsonName] == 100); - BEAST_EXPECT(jrr[jss::node][sfRepeatCount.jsonName] == 200); - } - { - // Malformed uritoken object. Missing owner member. - Json::Value jvParams; - jvParams[jss::cron] = Json::objectValue; - jvParams[jss::cron][jss::time] = startTime; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Malformed uritoken object. Missing time member. - Json::Value jvParams; - jvParams[jss::cron] = Json::objectValue; - jvParams[jss::cron][jss::owner] = alice.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "malformedRequest", ""); - } - { - // Request an index that is not a uritoken. - Json::Value jvParams; - jvParams[jss::cron] = ledgerHash; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "entryNotFound", ""); - } - } - - void - testLedgerEntryDID() - { - testcase("ledger_entry Request DID"); - using namespace test::jtx; - using namespace std::literals::chrono_literals; - Env env{*this, supported_amendments() | featureDID}; - Account const alice{"alice"}; - - env.fund(XRP(10000), alice); - env.close(); - - // Lambda to create a DID. - auto didCreate = [](test::jtx::Account const& account) { - Json::Value jv; - jv[jss::TransactionType] = jss::DIDSet; - jv[jss::Account] = account.human(); - jv[sfDIDDocument.jsonName] = strHex(std::string{"data"}); - jv[sfURI.jsonName] = strHex(std::string{"uri"}); - return jv; - }; - - env(didCreate(alice)); - env.close(); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - - { - // Request the DID using its index. - Json::Value jvParams; - jvParams[jss::did] = alice.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT( - jrr[jss::node][sfDIDDocument.jsonName] == - strHex(std::string{"data"})); - BEAST_EXPECT( - jrr[jss::node][sfURI.jsonName] == strHex(std::string{"uri"})); - } - { - // Request an index that is not a DID. - Json::Value jvParams; - jvParams[jss::did] = env.master.human(); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "entryNotFound", ""); - } - } - - void - testLedgerEntryInvalidParams(unsigned int apiVersion) - { - testcase( - "ledger_entry Request With Invalid Parameters v" + - std::to_string(apiVersion)); - using namespace test::jtx; - Env env{*this}; - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - - auto makeParams = [&apiVersion](std::function f) { - Json::Value params; - params[jss::api_version] = apiVersion; - f(params); - return params; - }; - // "features" is not an option supported by ledger_entry. - { - auto const jvParams = - makeParams([&ledgerHash](Json::Value& jvParams) { - jvParams[jss::features] = ledgerHash; - jvParams[jss::ledger_hash] = ledgerHash; - }); - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - if (apiVersion < 2u) - checkErrorValue(jrr, "unknownOption", ""); - else - checkErrorValue(jrr, "invalidParams", ""); - } - Json::Value const injectObject = []() { - Json::Value obj(Json::objectValue); - obj[jss::account] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU"; - obj[jss::ledger_index] = "validated"; - return obj; - }(); - Json::Value const injectArray = []() { - Json::Value arr(Json::arrayValue); - arr[0u] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU"; - arr[1u] = "validated"; - return arr; - }(); - - // invalid input for fields that can handle an object, but can't handle - // an array - for (auto const& field : { - jss::directory, - jss::escrow, - jss::offer, - jss::ticket, - jss::amm, - jss::import_vlseq, - jss::uri_token, - jss::hook, - }) - { - auto const jvParams = - makeParams([&field, &injectArray](Json::Value& jvParams) { - jvParams[field] = injectArray; - }); - - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - if (apiVersion < 2u) - checkErrorValue(jrr, "internal", "Internal error."); - else - checkErrorValue(jrr, "invalidParams", ""); - } - // Fields that can handle objects just fine - for (auto const& field : { - jss::directory, - jss::escrow, - jss::offer, - jss::ticket, - jss::amm, - jss::import_vlseq, - jss::uri_token, - }) - { - auto const jvParams = - makeParams([&field, &injectObject](Json::Value& jvParams) { - jvParams[field] = injectObject; - }); - - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - checkErrorValue(jrr, "malformedRequest", ""); - } - - for (auto const& inject : {injectObject, injectArray}) - { - // invalid input for fields that can't handle an object or an array - for (auto const& field : - {jss::index, - jss::account_root, - jss::check, - jss::payment_channel}) - { - auto const jvParams = - makeParams([&field, &inject](Json::Value& jvParams) { - jvParams[field] = inject; - }); - - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - if (apiVersion < 2u) - checkErrorValue(jrr, "internal", "Internal error."); - else - checkErrorValue(jrr, "invalidParams", ""); - } - // directory sub-fields - for (auto const& field : {jss::dir_root, jss::owner}) - { - auto const jvParams = - makeParams([&field, &inject](Json::Value& jvParams) { - jvParams[jss::directory][field] = inject; - }); - - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - if (apiVersion < 2u) - checkErrorValue(jrr, "internal", "Internal error."); - else - checkErrorValue(jrr, "invalidParams", ""); - } - // escrow sub-fields - { - auto const jvParams = - makeParams([&inject](Json::Value& jvParams) { - jvParams[jss::escrow][jss::owner] = inject; - jvParams[jss::escrow][jss::seq] = 99; - }); - - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - if (apiVersion < 2u) - checkErrorValue(jrr, "internal", "Internal error."); - else - checkErrorValue(jrr, "invalidParams", ""); - } - // offer sub-fields - { - auto const jvParams = - makeParams([&inject](Json::Value& jvParams) { - jvParams[jss::offer][jss::account] = inject; - jvParams[jss::offer][jss::seq] = 99; - }); - - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - if (apiVersion < 2u) - checkErrorValue(jrr, "internal", "Internal error."); - else - checkErrorValue(jrr, "invalidParams", ""); - } - // ripple_state sub-fields - { - auto const jvParams = - makeParams([&inject](Json::Value& jvParams) { - Json::Value rs(Json::objectValue); - rs[jss::currency] = "FOO"; - rs[jss::accounts] = Json::Value(Json::arrayValue); - rs[jss::accounts][0u] = - "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU"; - rs[jss::accounts][1u] = - "rKssEq6pg1KbqEqAFnua5mFAL6Ggpsh2wv"; - rs[jss::currency] = inject; - jvParams[jss::ripple_state] = std::move(rs); - }); - - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - if (apiVersion < 2u) - checkErrorValue(jrr, "internal", "Internal error."); - else - checkErrorValue(jrr, "invalidParams", ""); - } - // ticket sub-fields - { - auto const jvParams = - makeParams([&inject](Json::Value& jvParams) { - jvParams[jss::ticket][jss::account] = inject; - jvParams[jss::ticket][jss::ticket_seq] = 99; - }); - - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - if (apiVersion < 2u) - checkErrorValue(jrr, "internal", "Internal error."); - else - checkErrorValue(jrr, "invalidParams", ""); - } - - // Fields that can handle malformed inputs just fine - for (auto const& field : {jss::nft_page, jss::deposit_preauth}) - { - auto const jvParams = - makeParams([&field, &inject](Json::Value& jvParams) { - jvParams[field] = inject; - }); - - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - checkErrorValue(jrr, "malformedRequest", ""); - } - // Subfields of deposit_preauth that can handle malformed inputs - // fine - for (auto const& field : {jss::owner, jss::authorized}) - { - auto const jvParams = - makeParams([&field, &inject](Json::Value& jvParams) { - auto pa = Json::Value(Json::objectValue); - pa[jss::owner] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU"; - pa[jss::authorized] = - "rKssEq6pg1KbqEqAFnua5mFAL6Ggpsh2wv"; - pa[field] = inject; - jvParams[jss::deposit_preauth] = std::move(pa); - }); - - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - - checkErrorValue(jrr, "malformedRequest", ""); - } - } - } - /// @brief ledger RPC requests as a way to drive /// input options to lookupLedger. The point of this test is /// coverage for lookupLedger, not so much the ledger @@ -3651,324 +750,6 @@ public: } } - void - testInvalidOracleLedgerEntry() - { - testcase("Invalid Oracle Ledger Entry"); - using namespace ripple::test::jtx; - using namespace ripple::test::jtx::oracle; - - Env env(*this); - Account const owner("owner"); - env.fund(XRP(1'000), owner); - Oracle oracle( - env, - {.owner = owner, - .fee = static_cast(env.current()->fees().base.drops())}); - - // Malformed document id - auto res = Oracle::ledgerEntry(env, owner, NoneTag); - BEAST_EXPECT(res[jss::error].asString() == "invalidParams"); - std::vector invalid = {-1, 1.2, "", "Invalid"}; - for (auto const& v : invalid) - { - auto const res = Oracle::ledgerEntry(env, owner, v); - BEAST_EXPECT(res[jss::error].asString() == "malformedDocumentID"); - } - // Missing document id - res = Oracle::ledgerEntry(env, owner, std::nullopt); - BEAST_EXPECT(res[jss::error].asString() == "malformedRequest"); - - // Missing account - res = Oracle::ledgerEntry(env, std::nullopt, 1); - BEAST_EXPECT(res[jss::error].asString() == "malformedRequest"); - - // Malformed account - std::string malfAccount = to_string(owner.id()); - malfAccount.replace(10, 1, 1, '!'); - res = Oracle::ledgerEntry(env, malfAccount, 1); - BEAST_EXPECT(res[jss::error].asString() == "malformedAddress"); - } - - void - testOracleLedgerEntry() - { - testcase("Oracle Ledger Entry"); - using namespace ripple::test::jtx; - using namespace ripple::test::jtx::oracle; - - Env env(*this); - auto const baseFee = - static_cast(env.current()->fees().base.drops()); - std::vector accounts; - std::vector oracles; - for (int i = 0; i < 10; ++i) - { - Account const owner(std::string("owner") + std::to_string(i)); - env.fund(XRP(1'000), owner); - // different accounts can have the same asset pair - Oracle oracle( - env, {.owner = owner, .documentID = i, .fee = baseFee}); - accounts.push_back(owner.id()); - oracles.push_back(oracle.documentID()); - // same account can have different asset pair - Oracle oracle1( - env, {.owner = owner, .documentID = i + 10, .fee = baseFee}); - accounts.push_back(owner.id()); - oracles.push_back(oracle1.documentID()); - } - for (int i = 0; i < accounts.size(); ++i) - { - auto const jv = [&]() { - // document id is uint32 - if (i % 2) - return Oracle::ledgerEntry(env, accounts[i], oracles[i]); - // document id is string - return Oracle::ledgerEntry( - env, accounts[i], std::to_string(oracles[i])); - }(); - try - { - BEAST_EXPECT( - jv[jss::node][jss::Owner] == to_string(accounts[i])); - } - catch (...) - { - fail(); - } - } - } - - void - testLedgerEntryMPT() - { - testcase("ledger_entry Request MPT"); - using namespace test::jtx; - using namespace std::literals::chrono_literals; - Env env{*this, supported_amendments() | featureMPTokensV1}; - Account const alice{"alice"}; - Account const bob("bob"); - - MPTTester mptAlice(env, alice, {.holders = {bob}}); - mptAlice.create( - {.transferFee = 10, - .metadata = "123", - .ownerCount = 1, - .flags = tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | - tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback}); - mptAlice.authorize({.account = bob, .holderCount = 1}); - - std::string const ledgerHash{to_string(env.closed()->info().hash)}; - - std::string const badMptID = - "00000193B9DDCAF401B5B3B26875986043F82CD0D13B4315"; - { - // Request the MPTIssuance using its MPTIssuanceID. - Json::Value jvParams; - jvParams[jss::mpt_issuance] = strHex(mptAlice.issuanceID()); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT( - jrr[jss::node][sfMPTokenMetadata.jsonName] == - strHex(std::string{"123"})); - BEAST_EXPECT( - jrr[jss::node][jss::mpt_issuance_id] == - strHex(mptAlice.issuanceID())); - } - { - // Request an index that is not a MPTIssuance. - Json::Value jvParams; - jvParams[jss::mpt_issuance] = badMptID; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "entryNotFound", ""); - } - { - // Request the MPToken using its owner + mptIssuanceID. - Json::Value jvParams; - jvParams[jss::mptoken] = Json::objectValue; - jvParams[jss::mptoken][jss::account] = bob.human(); - jvParams[jss::mptoken][jss::mpt_issuance_id] = - strHex(mptAlice.issuanceID()); - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - BEAST_EXPECT( - jrr[jss::node][sfMPTokenIssuanceID.jsonName] == - strHex(mptAlice.issuanceID())); - } - { - // Request the MPToken using a bad mptIssuanceID. - Json::Value jvParams; - jvParams[jss::mptoken] = Json::objectValue; - jvParams[jss::mptoken][jss::account] = bob.human(); - jvParams[jss::mptoken][jss::mpt_issuance_id] = badMptID; - jvParams[jss::ledger_hash] = ledgerHash; - Json::Value const jrr = env.rpc( - "json", "ledger_entry", to_string(jvParams))[jss::result]; - checkErrorValue(jrr, "entryNotFound", ""); - } - } - - 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"); - } - } - - void - testLedgerEntryPermissionedDomain() - { - testcase("ledger_entry PermissionedDomain"); - - using namespace test::jtx; - - Env env( - *this, - supported_amendments() | featureCredentials | - featurePermissionedDomains); - Account const issuer{"issuer"}; - Account const alice{"alice"}; - Account const bob{"bob"}; - - env.fund(XRP(5000), issuer, alice, bob); - env.close(); - - auto const seq = env.seq(alice); - env(pdomain::setTx(alice, {{alice, "first credential"}})); - env.close(); - auto const objects = pdomain::getObjects(alice, env); - if (!BEAST_EXPECT(objects.size() == 1)) - return; - - { - // Succeed - Json::Value params; - params[jss::ledger_index] = jss::validated; - params[jss::permissioned_domain][jss::account] = alice.human(); - params[jss::permissioned_domain][jss::seq] = seq; - auto jv = env.rpc("json", "ledger_entry", to_string(params)); - BEAST_EXPECT( - jv.isObject() && jv.isMember(jss::result) && - !jv[jss::result].isMember(jss::error) && - jv[jss::result].isMember(jss::node) && - jv[jss::result][jss::node].isMember( - sfLedgerEntryType.jsonName) && - jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == - jss::PermissionedDomain); - - std::string const pdIdx = jv[jss::result][jss::index].asString(); - BEAST_EXPECT( - strHex(keylet::permissionedDomain(alice, seq).key) == pdIdx); - - params.clear(); - params[jss::ledger_index] = jss::validated; - params[jss::permissioned_domain] = pdIdx; - jv = env.rpc("json", "ledger_entry", to_string(params)); - BEAST_EXPECT( - jv.isObject() && jv.isMember(jss::result) && - !jv[jss::result].isMember(jss::error) && - jv[jss::result].isMember(jss::node) && - jv[jss::result][jss::node].isMember( - sfLedgerEntryType.jsonName) && - jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == - jss::PermissionedDomain); - } - - { - // Fail, invalid permissioned domain index - Json::Value params; - params[jss::ledger_index] = jss::validated; - params[jss::permissioned_domain] = - "12F1F1F1F180D67377B2FAB292A31C922470326268D2B9B74CD1E582645B9A" - "DE"; - auto const jrr = env.rpc("json", "ledger_entry", to_string(params)); - checkErrorValue(jrr[jss::result], "entryNotFound", ""); - } - - { - // Fail, invalid permissioned domain index - Json::Value params; - params[jss::ledger_index] = jss::validated; - params[jss::permissioned_domain] = "NotAHexString"; - auto const jrr = env.rpc("json", "ledger_entry", to_string(params)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - - { - // Fail, permissioned domain is not an object - Json::Value params; - params[jss::ledger_index] = jss::validated; - params[jss::permissioned_domain] = 10; - auto const jrr = env.rpc("json", "ledger_entry", to_string(params)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - - { - // Fail, invalid account - Json::Value params; - params[jss::ledger_index] = jss::validated; - params[jss::permissioned_domain][jss::account] = 1; - params[jss::permissioned_domain][jss::seq] = seq; - auto const jrr = env.rpc("json", "ledger_entry", to_string(params)); - checkErrorValue(jrr[jss::result], "malformedAddress", ""); - } - - { - // Fail, account is an object - Json::Value params; - params[jss::ledger_index] = jss::validated; - params[jss::permissioned_domain][jss::account] = - Json::Value{Json::ValueType::objectValue}; - params[jss::permissioned_domain][jss::seq] = seq; - auto const jrr = env.rpc("json", "ledger_entry", to_string(params)); - checkErrorValue(jrr[jss::result], "malformedAddress", ""); - } - - { - // Fail, no account - Json::Value params; - params[jss::ledger_index] = jss::validated; - params[jss::permissioned_domain][jss::account] = ""; - params[jss::permissioned_domain][jss::seq] = seq; - auto const jrr = env.rpc("json", "ledger_entry", to_string(params)); - checkErrorValue(jrr[jss::result], "malformedAddress", ""); - } - - { - // Fail, invalid sequence - Json::Value params; - params[jss::ledger_index] = jss::validated; - params[jss::permissioned_domain][jss::account] = alice.human(); - params[jss::permissioned_domain][jss::seq] = "12g"; - auto const jrr = env.rpc("json", "ledger_entry", to_string(params)); - checkErrorValue(jrr[jss::result], "malformedRequest", ""); - } - } - public: void run() override @@ -3976,45 +757,17 @@ public: testLedgerRequest(); testBadInput(); testLedgerCurrent(); - testMissingLedgerEntryLedgerHash(); testLedgerFull(); testLedgerFullNonAdmin(); testLedgerAccounts(); - testLedgerEntryAccountRoot(); - testLedgerEntryCheck(); - testLedgerEntryCredentials(); - testLedgerEntryDepositPreauth(); - testLedgerEntryDepositPreauthCred(); - testLedgerEntryDirectory(); - testLedgerEntryEmittedTxn(); - testLedgerEntryEscrow(); - testLedgerEntryHook(); - testLedgerEntryHookDefinition(); - testLedgerEntryHookState(); - testLedgerEntryOffer(); - testLedgerEntryPayChan(); - testLedgerEntryRippleState(); - testLedgerEntryTicket(); - testLedgerEntryURIToken(); - testLedgerEntryImportVLSeq(); - testLedgerEntryCron(); testLookupLedger(); testNoQueue(); testQueue(); testLedgerAccountsOption(); - testLedgerEntryDID(); - testInvalidOracleLedgerEntry(); - testOracleLedgerEntry(); - testLedgerEntryMPT(); - testLedgerEntryCLI(); - testLedgerEntryPermissionedDomain(); - - forAllApiVersions(std::bind_front( - &LedgerRPC_test::testLedgerEntryInvalidParams, this)); } }; BEAST_DEFINE_TESTSUITE(LedgerRPC, app, ripple); -BEAST_DEFINE_TESTSUITE(LedgerRPC_XChain, app, ripple); +} // namespace test } // namespace ripple diff --git a/src/xrpld/rpc/handlers/LedgerEntry.cpp b/src/xrpld/rpc/handlers/LedgerEntry.cpp index 69b8a107e..9f9ee07e4 100644 --- a/src/xrpld/rpc/handlers/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/LedgerEntry.cpp @@ -38,6 +38,197 @@ namespace ripple { +static 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; +} + +static 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; +} + +static 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; + } +} + +static 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; +} + +static 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; +} + +static 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; +} + +static std::optional +parseCron(Json::Value const& cronJson, Json::Value& jvResult) +{ + if (!cronJson.isObject()) + { + uint256 uNodeIndex; + if (!uNodeIndex.parseHex(cronJson.asString())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + return uNodeIndex; + } + else if (!cronJson.isMember(jss::owner) || !cronJson.isMember(jss::time)) + { + jvResult[jss::error] = "malformedRequest"; + } + else + { + auto const id = parseBase58(cronJson[jss::owner].asString()); + if (!id) + jvResult[jss::error] = "malformedAddress"; + else + return keylet::cron(cronJson[jss::time].asUInt(), *id).key; + } + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; +} + static STArray parseAuthorizeCredentials(Json::Value const& jv) { @@ -69,45 +260,6 @@ parseAuthorizeCredentials(Json::Value const& jv) return arr; } -static 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; -} - -static 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; -} - -static 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; -} - static std::optional parseDepositPreauth(Json::Value const& dp, Json::Value& jvResult) { @@ -173,6 +325,19 @@ parseDepositPreauth(Json::Value const& dp, Json::Value& jvResult) return keylet::depositPreauth(*owner, sorted).key; } +static 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; +} + static std::optional parseDirectory(Json::Value const& params, Json::Value& jvResult) { @@ -272,6 +437,244 @@ parseEscrow(Json::Value const& params, Json::Value& jvResult) return keylet::escrow(*id, params[jss::seq].asUInt()).key; } +static 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; +} + +static 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; +} + +static 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; +} + +static 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; +} + +static 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; +} + +static 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; + } +} + +static 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; +} + +static 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; +} + static std::optional parseOffer(Json::Value const& params, Json::Value& jvResult) { @@ -303,6 +706,60 @@ parseOffer(Json::Value const& params, Json::Value& jvResult) return keylet::offer(*id, params[jss::seq].asUInt()).key; } +static 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; +} + static std::optional parsePaymentChannel(Json::Value const& params, Json::Value& jvResult) { @@ -316,6 +773,51 @@ parsePaymentChannel(Json::Value const& params, Json::Value& jvResult) return uNodeIndex; } +static std::optional +parsePermissionedDomains(Json::Value const& pd, Json::Value& jvResult) +{ + if (pd.isString()) + { + auto const index = parseIndex(pd, jvResult); + return index; + } + + if (!pd.isObject()) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + if (!pd.isMember(jss::account)) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + if (!pd[jss::account].isString()) + { + jvResult[jss::error] = "malformedAddress"; + return std::nullopt; + } + + if (!pd.isMember(jss::seq) || + (pd[jss::seq].isInt() && pd[jss::seq].asInt() < 0) || + (!pd[jss::seq].isInt() && !pd[jss::seq].isUInt())) + { + jvResult[jss::error] = "malformedRequest"; + return std::nullopt; + } + + auto const account = parseBase58(pd[jss::account].asString()); + if (!account) + { + jvResult[jss::error] = "malformedAddress"; + return std::nullopt; + } + + return keylet::permissionedDomain(*account, pd[jss::seq].asUInt()).key; +} + static std::optional parseRippleState(Json::Value const& jvRippleState, Json::Value& jvResult) { @@ -385,103 +887,35 @@ parseTicket(Json::Value const& params, Json::Value& jvResult) } static std::optional -parseNFTokenPage(Json::Value const& params, Json::Value& jvResult) +parseUriToken(Json::Value const& uriTokenJson, Json::Value& jvResult) { - if (params.isString()) + if (!uriTokenJson.isObject()) { uint256 uNodeIndex; - if (!uNodeIndex.parseHex(params.asString())) + if (!uNodeIndex.parseHex(uriTokenJson.asString())) { jvResult[jss::error] = "malformedRequest"; return std::nullopt; } return uNodeIndex; } - - jvResult[jss::error] = "malformedRequest"; - return std::nullopt; -} - -static 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)) + else if ( + !uriTokenJson.isMember(jss::account) || + !uriTokenJson.isMember(jss::uri)) { jvResult[jss::error] = "malformedRequest"; - return std::nullopt; } - - try + else { - auto const issue = issueFromJson(params[jss::asset]); - auto const issue2 = issueFromJson(params[jss::asset2]); - return keylet::amm(issue, issue2).key; + 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; } - catch (std::runtime_error const&) - { - jvResult[jss::error] = "malformedRequest"; - return std::nullopt; - } -} - -static 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; } @@ -634,440 +1068,6 @@ parseXChainOwnedCreateAccountClaimID( return std::nullopt; } -static 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; -} - -static 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; -} - -static 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; -} - -static 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; -} - -static 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; -} - -std::optional -parseCron(Json::Value const& cronJson, Json::Value& jvResult) -{ - if (!cronJson.isObject()) - { - uint256 uNodeIndex; - if (!uNodeIndex.parseHex(cronJson.asString())) - { - jvResult[jss::error] = "malformedRequest"; - return std::nullopt; - } - return uNodeIndex; - } - else if (!cronJson.isMember(jss::owner) || !cronJson.isMember(jss::time)) - { - jvResult[jss::error] = "malformedRequest"; - } - else - { - auto const id = parseBase58(cronJson[jss::owner].asString()); - if (!id) - jvResult[jss::error] = "malformedAddress"; - else - return keylet::cron(cronJson[jss::time].asUInt(), *id).key; - } - jvResult[jss::error] = "malformedRequest"; - return std::nullopt; -} - -static std::optional -parsePermissionedDomains(Json::Value const& pd, Json::Value& jvResult) -{ - if (pd.isString()) - { - auto const index = parseIndex(pd, jvResult); - return index; - } - - if (!pd.isObject()) - { - jvResult[jss::error] = "malformedRequest"; - return std::nullopt; - } - - if (!pd.isMember(jss::account)) - { - jvResult[jss::error] = "malformedRequest"; - return std::nullopt; - } - - if (!pd[jss::account].isString()) - { - jvResult[jss::error] = "malformedAddress"; - return std::nullopt; - } - - if (!pd.isMember(jss::seq) || - (pd[jss::seq].isInt() && pd[jss::seq].asInt() < 0) || - (!pd[jss::seq].isInt() && !pd[jss::seq].isUInt())) - { - jvResult[jss::error] = "malformedRequest"; - return std::nullopt; - } - - auto const account = parseBase58(pd[jss::account].asString()); - if (!account) - { - jvResult[jss::error] = "malformedAddress"; - return std::nullopt; - } - - return keylet::permissionedDomain(*account, pd[jss::seq].asUInt()).key; -} - using FunctionType = std::function(Json::Value const&, Json::Value&)>;