Files
rippled/src/test/rpc/LedgerRPC_test.cpp
yinyiqian1 2db2791805 Add PermissionDelegation feature (#5354)
This change implements the account permission delegation described in XLS-75d, see https://github.com/XRPLF/XRPL-Standards/pull/257.

* Introduces transaction-level and granular permissions that can be delegated to other accounts.
* Adds `DelegateSet` transaction to grant specified permissions to another account.
* Adds `ltDelegate` ledger object to maintain the permission list for delegating/delegated account pair.
* Adds an optional `Delegate` field in common fields, allowing a delegated account to send transactions on behalf of the delegating account within the granted permission scope. The `Account` field remains the delegating account; the `Delegate` field specifies the delegated account. The transaction is signed by the delegated account.
2025-05-08 06:14:02 -04:00

766 lines
27 KiB
C++

//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2016 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 <test/jtx.h>
#include <test/jtx/Oracle.h>
#include <test/jtx/attester.h>
#include <test/jtx/delegate.h>
#include <test/jtx/multisign.h>
#include <test/jtx/xchain_bridge.h>
#include <xrpld/app/misc/TxQ.h>
#include <xrpl/beast/unit_test.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
namespace ripple {
namespace test {
class LedgerRPC_test : public beast::unit_test::suite
{
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
testLedgerRequest()
{
testcase("Basic Request");
using namespace test::jtx;
Env env{*this};
env.close();
BEAST_EXPECT(env.current()->info().seq == 4);
{
Json::Value jvParams;
// can be either numeric or quoted numeric
jvParams[jss::ledger_index] = 1;
auto const jrr =
env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::ledger][jss::closed] == true);
BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "1");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "1";
auto const jrr =
env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::ledger][jss::closed] == true);
BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "1");
}
{
// using current identifier
auto const jrr = env.rpc("ledger", "current")[jss::result];
BEAST_EXPECT(jrr[jss::ledger][jss::closed] == false);
BEAST_EXPECT(
jrr[jss::ledger][jss::ledger_index] ==
std::to_string(env.current()->info().seq));
BEAST_EXPECT(
jrr[jss::ledger_current_index] == env.current()->info().seq);
}
}
void
testBadInput()
{
testcase("Bad Input");
using namespace test::jtx;
Env env{*this};
Account const gw{"gateway"};
auto const USD = gw["USD"];
Account const bob{"bob"};
env.fund(XRP(10000), gw, bob);
env.close();
env.trust(USD(1000), bob);
env.close();
{
// ask for an arbitrary string - index
Json::Value jvParams;
jvParams[jss::ledger_index] = "potato";
auto const jrr =
env.rpc("json", "ledger", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "invalidParams", "ledgerIndexMalformed");
}
{
// ask for a negative index
Json::Value jvParams;
jvParams[jss::ledger_index] = -1;
auto const jrr =
env.rpc("json", "ledger", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "invalidParams", "ledgerIndexMalformed");
}
{
// ask for a bad ledger index
Json::Value jvParams;
jvParams[jss::ledger_index] = 10u;
auto const jrr =
env.rpc("json", "ledger", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
}
{
// unrecognized string arg -- error
auto const jrr = env.rpc("ledger", "arbitrary_text")[jss::result];
checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
}
{
// Request queue for closed ledger
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::queue] = true;
auto const jrr =
env.rpc("json", "ledger", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "invalidParams", "Invalid parameters.");
}
{
// Request a ledger with a very large (double) sequence.
auto const ret =
env.rpc("json", "ledger", "{ \"ledger_index\" : 2e15 }");
BEAST_EXPECT(RPC::contains_error(ret));
BEAST_EXPECT(ret[jss::error_message] == "Invalid parameters.");
}
{
// Request a ledger with very large (integer) sequence.
auto const ret = env.rpc(
"json", "ledger", "{ \"ledger_index\" : 1000000000000000 }");
checkErrorValue(ret, "invalidParams", "Invalid parameters.");
}
}
void
testLedgerCurrent()
{
testcase("ledger_current Request");
using namespace test::jtx;
Env env{*this};
env.close();
BEAST_EXPECT(env.current()->info().seq == 4);
{
auto const jrr = env.rpc("ledger_current")[jss::result];
BEAST_EXPECT(
jrr[jss::ledger_current_index] == env.current()->info().seq);
}
}
void
testLedgerFull()
{
testcase("Ledger Request, Full Option");
using namespace test::jtx;
Env env{*this};
env.close();
Json::Value jvParams;
jvParams[jss::ledger_index] = 3u;
jvParams[jss::full] = true;
auto const jrr =
env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 3u);
}
void
testLedgerFullNonAdmin()
{
testcase("Ledger Request, Full Option Without Admin");
using namespace test::jtx;
Env env{*this, envconfig(no_admin)};
// env.close();
Json::Value jvParams;
jvParams[jss::ledger_index] = 1u;
jvParams[jss::full] = true;
auto const jrr =
env.rpc("json", "ledger", to_string(jvParams))[jss::result];
checkErrorValue(
jrr, "noPermission", "You don't have permission for this command.");
}
void
testLedgerAccounts()
{
testcase("Ledger Request, Accounts Option");
using namespace test::jtx;
Env env{*this};
env.close();
Json::Value jvParams;
jvParams[jss::ledger_index] = 3u;
jvParams[jss::accounts] = true;
auto const jrr =
env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 3u);
}
/// @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
/// RPC request.
void
testLookupLedger()
{
testcase("Lookup ledger");
using namespace test::jtx;
auto cfg = envconfig();
cfg->FEES.reference_fee = 10;
Env env{
*this, std::move(cfg), FeatureBitset{}}; // hashes requested below
// assume no amendments
env.fund(XRP(10000), "alice");
env.close();
env.fund(XRP(10000), "bob");
env.close();
env.fund(XRP(10000), "jim");
env.close();
env.fund(XRP(10000), "jill");
{
// access via the legacy ledger field, keyword index values
Json::Value jvParams;
jvParams[jss::ledger] = "closed";
auto jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr.isMember(jss::ledger));
BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
jvParams[jss::ledger] = "validated";
jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr.isMember(jss::ledger));
BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
jvParams[jss::ledger] = "current";
jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr.isMember(jss::ledger));
BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "6");
// ask for a bad ledger keyword
jvParams[jss::ledger] = "invalid";
jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] == "ledgerIndexMalformed");
// numeric index
jvParams[jss::ledger] = 4;
jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr.isMember(jss::ledger));
BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "4");
// numeric index - out of range
jvParams[jss::ledger] = 20;
jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
}
{
std::string const hash3{
"E86DE7F3D7A4D9CE17EF7C8BA08A8F4D"
"8F643B9552F0D895A31CDA78F541DE4E"};
// access via the ledger_hash field
Json::Value jvParams;
jvParams[jss::ledger_hash] = hash3;
auto jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr.isMember(jss::ledger));
BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "3");
// extra leading hex chars in hash are not allowed
jvParams[jss::ledger_hash] = "DEADBEEF" + hash3;
jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] == "ledgerHashMalformed");
// request with non-string ledger_hash
jvParams[jss::ledger_hash] = 2;
jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] == "ledgerHashNotString");
// malformed (non hex) hash
jvParams[jss::ledger_hash] =
"2E81FC6EC0DD943197EGC7E3FBE9AE30"
"7F2775F2F7485BB37307984C3C0F2340";
jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] == "ledgerHashMalformed");
// properly formed, but just doesn't exist
jvParams[jss::ledger_hash] =
"8C3EEDB3124D92E49E75D81A8826A2E6"
"5A75FD71FC3FD6F36FEB803C5F1D812D";
jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
}
{
// access via the ledger_index field, keyword index values
Json::Value jvParams;
jvParams[jss::ledger_index] = "closed";
auto jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr.isMember(jss::ledger));
BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
BEAST_EXPECT(jrr.isMember(jss::ledger_index));
jvParams[jss::ledger_index] = "validated";
jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr.isMember(jss::ledger));
BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
jvParams[jss::ledger_index] = "current";
jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr.isMember(jss::ledger));
BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "6");
BEAST_EXPECT(jrr.isMember(jss::ledger_current_index));
// ask for a bad ledger keyword
jvParams[jss::ledger_index] = "invalid";
jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] == "ledgerIndexMalformed");
// numeric index
for (auto i : {1, 2, 3, 4, 5, 6})
{
jvParams[jss::ledger_index] = i;
jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr.isMember(jss::ledger));
if (i < 6)
BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
BEAST_EXPECT(
jrr[jss::ledger][jss::ledger_index] == std::to_string(i));
}
// numeric index - out of range
jvParams[jss::ledger_index] = 7;
jrr = env.rpc(
"json",
"ledger",
boost::lexical_cast<std::string>(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
}
}
void
testNoQueue()
{
testcase("Ledger with queueing disabled");
using namespace test::jtx;
Env env{*this};
Json::Value jv;
jv[jss::ledger_index] = "current";
jv[jss::queue] = true;
jv[jss::expand] = true;
auto jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
BEAST_EXPECT(!jrr.isMember(jss::queue_data));
}
void
testQueue()
{
testcase("Ledger with Queued Transactions");
using namespace test::jtx;
auto cfg = envconfig([](std::unique_ptr<Config> cfg) {
auto& section = cfg->section("transaction_queue");
section.set("minimum_txn_in_ledger_standalone", "3");
section.set("normal_consensus_increase_percent", "0");
return cfg;
});
cfg->FEES.reference_fee = 10;
Env env(*this, std::move(cfg));
Json::Value jv;
jv[jss::ledger_index] = "current";
jv[jss::queue] = true;
jv[jss::expand] = true;
Account const alice{"alice"};
Account const bob{"bob"};
Account const charlie{"charlie"};
Account const daria{"daria"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
env.close();
env.fund(XRP(10000), charlie);
env.fund(XRP(10000), daria);
env.close();
auto jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
BEAST_EXPECT(!jrr.isMember(jss::queue_data));
// Fill the open ledger
for (;;)
{
auto metrics = env.app().getTxQ().getMetrics(*env.current());
if (metrics.openLedgerFeeLevel > metrics.minProcessingFeeLevel)
break;
env(noop(alice));
}
BEAST_EXPECT(env.current()->info().seq == 5);
// Put some txs in the queue
// Alice
auto aliceSeq = env.seq(alice);
env(pay(alice, "george", XRP(1000)),
json(R"({"LastLedgerSequence":7})"),
ter(terQUEUED));
env(offer(alice, XRP(50000), alice["USD"](5000)),
seq(aliceSeq + 1),
ter(terQUEUED));
env(noop(alice), seq(aliceSeq + 2), ter(terQUEUED));
// Bob
auto batch = [&env](Account a) {
auto aSeq = env.seq(a);
// Enough fee to get in front of alice in the queue
for (int i = 0; i < 10; ++i)
{
env(noop(a), fee(1000 + i), seq(aSeq + i), ter(terQUEUED));
}
};
batch(bob);
// Charlie
batch(charlie);
// Daria
batch(daria);
jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
BEAST_EXPECT(jrr[jss::queue_data].size() == 33);
// Close enough ledgers so that alice's first tx expires.
env.close();
env.close();
env.close();
BEAST_EXPECT(env.current()->info().seq == 8);
jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
BEAST_EXPECT(jrr[jss::queue_data].size() == 11);
env.close();
jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
const std::string txid0 = [&]() {
auto const& parentHash = env.current()->info().parentHash;
if (BEAST_EXPECT(jrr[jss::queue_data].size() == 2))
{
const std::string txid1 = [&]() {
auto const& txj = jrr[jss::queue_data][1u];
BEAST_EXPECT(txj[jss::account] == alice.human());
BEAST_EXPECT(txj[jss::fee_level] == "256");
BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
BEAST_EXPECT(txj["retries_remaining"] == 10);
BEAST_EXPECT(txj.isMember(jss::tx));
auto const& tx = txj[jss::tx];
BEAST_EXPECT(tx[jss::Account] == alice.human());
BEAST_EXPECT(tx[jss::TransactionType] == jss::AccountSet);
return tx[jss::hash].asString();
}();
auto const& txj = jrr[jss::queue_data][0u];
BEAST_EXPECT(txj[jss::account] == alice.human());
BEAST_EXPECT(txj[jss::fee_level] == "256");
BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
BEAST_EXPECT(txj["retries_remaining"] == 10);
BEAST_EXPECT(txj.isMember(jss::tx));
auto const& tx = txj[jss::tx];
BEAST_EXPECT(tx[jss::Account] == alice.human());
BEAST_EXPECT(tx[jss::TransactionType] == jss::OfferCreate);
const auto txid0 = tx[jss::hash].asString();
uint256 tx0, tx1;
BEAST_EXPECT(tx0.parseHex(txid0));
BEAST_EXPECT(tx1.parseHex(txid1));
BEAST_EXPECT((tx0 ^ parentHash) < (tx1 ^ parentHash));
return txid0;
}
return std::string{};
}();
env.close();
jv[jss::expand] = false;
jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
if (BEAST_EXPECT(jrr[jss::queue_data].size() == 2))
{
auto const& parentHash = env.current()->info().parentHash;
auto const txid1 = [&]() {
auto const& txj = jrr[jss::queue_data][1u];
BEAST_EXPECT(txj[jss::account] == alice.human());
BEAST_EXPECT(txj[jss::fee_level] == "256");
BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
BEAST_EXPECT(txj.isMember(jss::tx));
return txj[jss::tx].asString();
}();
auto const& txj = jrr[jss::queue_data][0u];
BEAST_EXPECT(txj[jss::account] == alice.human());
BEAST_EXPECT(txj[jss::fee_level] == "256");
BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
BEAST_EXPECT(txj["retries_remaining"] == 9);
BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
BEAST_EXPECT(txj.isMember(jss::tx));
BEAST_EXPECT(txj[jss::tx] == txid0);
uint256 tx0, tx1;
BEAST_EXPECT(tx0.parseHex(txid0));
BEAST_EXPECT(tx1.parseHex(txid1));
BEAST_EXPECT((tx0 ^ parentHash) < (tx1 ^ parentHash));
}
env.close();
jv[jss::expand] = true;
jv[jss::binary] = true;
jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
if (BEAST_EXPECT(jrr[jss::queue_data].size() == 2))
{
auto const& txj = jrr[jss::queue_data][1u];
BEAST_EXPECT(txj[jss::account] == alice.human());
BEAST_EXPECT(txj[jss::fee_level] == "256");
BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
BEAST_EXPECT(txj["retries_remaining"] == 8);
BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
BEAST_EXPECT(txj.isMember(jss::tx));
BEAST_EXPECT(txj[jss::tx].isMember(jss::tx_blob));
auto const& txj2 = jrr[jss::queue_data][0u];
BEAST_EXPECT(txj2[jss::account] == alice.human());
BEAST_EXPECT(txj2[jss::fee_level] == "256");
BEAST_EXPECT(txj2["preflight_result"] == "tesSUCCESS");
BEAST_EXPECT(txj2["retries_remaining"] == 10);
BEAST_EXPECT(!txj2.isMember("last_result"));
BEAST_EXPECT(txj2.isMember(jss::tx));
BEAST_EXPECT(txj2[jss::tx].isMember(jss::tx_blob));
}
for (int i = 0; i != 9; ++i)
{
env.close();
}
jv[jss::expand] = false;
jv[jss::binary] = false;
jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
const std::string txid2 = [&]() {
if (BEAST_EXPECT(jrr[jss::queue_data].size() == 1))
{
auto const& txj = jrr[jss::queue_data][0u];
BEAST_EXPECT(txj[jss::account] == alice.human());
BEAST_EXPECT(txj[jss::fee_level] == "256");
BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
BEAST_EXPECT(txj["retries_remaining"] == 1);
BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
BEAST_EXPECT(txj.isMember(jss::tx));
BEAST_EXPECT(txj[jss::tx] != txid0);
return txj[jss::tx].asString();
}
return std::string{};
}();
jv[jss::full] = true;
jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
if (BEAST_EXPECT(jrr[jss::queue_data].size() == 1))
{
auto const& txj = jrr[jss::queue_data][0u];
BEAST_EXPECT(txj[jss::account] == alice.human());
BEAST_EXPECT(txj[jss::fee_level] == "256");
BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
BEAST_EXPECT(txj["retries_remaining"] == 1);
BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
BEAST_EXPECT(txj.isMember(jss::tx));
auto const& tx = txj[jss::tx];
BEAST_EXPECT(tx[jss::Account] == alice.human());
BEAST_EXPECT(tx[jss::TransactionType] == jss::AccountSet);
BEAST_EXPECT(tx[jss::hash] == txid2);
}
}
void
testLedgerAccountsOption()
{
testcase("Ledger Request, Accounts Hashes");
using namespace test::jtx;
Env env{*this};
env.close();
std::string index;
{
Json::Value jvParams;
jvParams[jss::ledger_index] = 3u;
jvParams[jss::accounts] = true;
jvParams[jss::expand] = true;
jvParams[jss::type] = "hashes";
auto const jrr =
env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 1u);
BEAST_EXPECT(
jrr[jss::ledger][jss::accountState][0u]["LedgerEntryType"] ==
jss::LedgerHashes);
index = jrr[jss::ledger][jss::accountState][0u]["index"].asString();
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = 3u;
jvParams[jss::accounts] = true;
jvParams[jss::expand] = false;
jvParams[jss::type] = "hashes";
auto const jrr =
env.rpc("json", "ledger", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 1u);
BEAST_EXPECT(jrr[jss::ledger][jss::accountState][0u] == index);
}
}
public:
void
run() override
{
testLedgerRequest();
testBadInput();
testLedgerCurrent();
testLedgerFull();
testLedgerFullNonAdmin();
testLedgerAccounts();
testLookupLedger();
testNoQueue();
testQueue();
testLedgerAccountsOption();
}
};
BEAST_DEFINE_TESTSUITE(LedgerRPC, app, ripple);
} // namespace test
} // namespace ripple