Add 'type' param to ledger_data and ledger rpc commands (RIPD-1446):

The 'type' field allows the rpc client to specify what type of ledger
entries to retrieve. The available types are:

    "account"
    "amendments"
    "directory"
    "fee"
    "hashes"
    "offer"
    "signer_list"
    "state"
    "ticket"
This commit is contained in:
Howard Hinnant
2017-03-17 16:20:45 -04:00
committed by Nik Bougalis
parent fab3ec0b56
commit 1a7a6f22e2
13 changed files with 293 additions and 62 deletions

View File

@@ -31,17 +31,12 @@ namespace ripple {
struct LedgerFill struct LedgerFill
{ {
LedgerFill (ReadView const& l, int o = 0) LedgerFill (ReadView const& l, int o = 0, std::vector<TxQ::TxDetails> q = {},
LedgerEntryType t = ltINVALID)
: ledger (l) : ledger (l)
, options (o) , options (o)
{ , txQueue(std::move(q))
} , type (t)
LedgerFill(ReadView const& l, int o,
std::vector<TxQ::TxDetails> q)
: ledger(l)
, options(o)
, txQueue(q)
{ {
} }
@@ -58,6 +53,7 @@ struct LedgerFill
ReadView const& ledger; ReadView const& ledger;
int options; int options;
std::vector<TxQ::TxDetails> txQueue; std::vector<TxQ::TxDetails> txQueue;
LedgerEntryType type;
}; };
/** Given a Ledger and options, fill a Json::Object or Json::Value with a /** Given a Ledger and options, fill a Json::Object or Json::Value with a

View File

@@ -170,16 +170,19 @@ void fillJsonState(Object& json, LedgerFill const& fill)
for(auto const& sle : ledger.sles) for(auto const& sle : ledger.sles)
{ {
if (binary) if (fill.type == ltINVALID || sle->getType () == fill.type)
{ {
auto&& obj = appendObject(array); if (binary)
obj[jss::hash] = to_string(sle->key()); {
obj[jss::tx_blob] = serializeHex(*sle); auto&& obj = appendObject(array);
obj[jss::hash] = to_string(sle->key());
obj[jss::tx_blob] = serializeHex(*sle);
}
else if (expanded)
array.append(sle->getJson(0));
else
array.append(to_string(sle->key()));
} }
else if (expanded)
array.append(sle->getJson(0));
else
array.append(to_string(sle->key()));
} }
} }

View File

@@ -21,6 +21,7 @@
#define RIPPLE_PROTOCOL_LEDGERFORMATS_H_INCLUDED #define RIPPLE_PROTOCOL_LEDGERFORMATS_H_INCLUDED
#include <ripple/protocol/KnownFormats.h> #include <ripple/protocol/KnownFormats.h>
#include <ripple/rpc/Status.h>
namespace ripple { namespace ripple {
@@ -163,6 +164,9 @@ private:
void addCommonFields (Item& item); void addCommonFields (Item& item);
}; };
std::pair<RPC::Status, LedgerEntryType>
chooseLedgerEntryType(Json::Value const& params);
} // ripple } // ripple
#endif #endif

View File

@@ -18,7 +18,12 @@
//============================================================================== //==============================================================================
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/LedgerFormats.h> #include <ripple/protocol/LedgerFormats.h>
#include <algorithm>
#include <array>
#include <utility>
namespace ripple { namespace ripple {
@@ -165,4 +170,49 @@ LedgerFormats::getInstance ()
return instance; return instance;
} }
std::pair<RPC::Status, LedgerEntryType>
chooseLedgerEntryType(Json::Value const& params)
{
std::pair<RPC::Status, LedgerEntryType> result{RPC::Status::OK, ltINVALID};
if (params.isMember(jss::type))
{
static
std::array<std::pair<char const *, LedgerEntryType>, 9> const types
{{
{ jss::account, ltACCOUNT_ROOT },
{ jss::amendments, ltAMENDMENTS },
{ jss::directory, ltDIR_NODE },
{ jss::fee, ltFEE_SETTINGS },
{ jss::hashes, ltLEDGER_HASHES },
{ jss::offer, ltOFFER },
{ jss::signer_list, ltSIGNER_LIST },
{ jss::state, ltRIPPLE_STATE },
{ jss::ticket, ltTICKET }
}};
auto const& p = params[jss::type];
if (!p.isString())
{
result.first = RPC::Status{rpcINVALID_PARAMS,
"Invalid field 'type', not string."};
assert(result.first.type() == RPC::Status::Type::error_code_i);
return result;
}
auto const filter = p.asString ();
auto iter = std::find_if (types.begin (), types.end (),
[&filter](decltype (types.front ())& t)
{ return t.first == filter; });
if (iter == types.end ())
{
result.first = RPC::Status{rpcINVALID_PARAMS,
"Invalid field 'type'."};
assert(result.first.type() == RPC::Status::Type::error_code_i);
return result;
}
result.second = iter->second;
}
return result;
}
} // ripple } // ripple

View File

@@ -43,7 +43,7 @@ public:
using Code = int; using Code = int;
using Strings = std::vector <std::string>; using Strings = std::vector <std::string>;
static const Code OK = 0; static constexpr Code OK = 0;
Status () = default; Status () = default;

View File

@@ -25,6 +25,7 @@
#include <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
#include <ripple/protocol/JsonFields.h> #include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/LedgerFormats.h>
#include <ripple/resource/Fees.h> #include <ripple/resource/Fees.h>
#include <ripple/rpc/Context.h> #include <ripple/rpc/Context.h>
#include <ripple/rpc/impl/RPCHelpers.h> #include <ripple/rpc/impl/RPCHelpers.h>
@@ -72,36 +73,12 @@ Json::Value doAccountObjects (RPC::Context& context)
if (! ledger->exists(keylet::account (accountID))) if (! ledger->exists(keylet::account (accountID)))
return rpcError (rpcACT_NOT_FOUND); return rpcError (rpcACT_NOT_FOUND);
auto type = ltINVALID; auto type = chooseLedgerEntryType(params);
if (params.isMember (jss::type)) if (type.first)
{ {
static result.clear();
std::array<std::pair<char const *, LedgerEntryType>, 9> const type.first.inject(result);
types return result;
{{
{ jss::account, ltACCOUNT_ROOT },
{ jss::amendments, ltAMENDMENTS },
{ jss::directory, ltDIR_NODE },
{ jss::fee, ltFEE_SETTINGS },
{ jss::hashes, ltLEDGER_HASHES },
{ jss::offer, ltOFFER },
{ jss::signer_list, ltSIGNER_LIST },
{ jss::state, ltRIPPLE_STATE },
{ jss::ticket, ltTICKET }
}};
auto const& p = params[jss::type];
if (! p.isString ())
return RPC::expected_field_error (jss::type, "string");
auto const filter = p.asString ();
auto iter = std::find_if (types.begin (), types.end (),
[&filter](decltype (types.front ())& t)
{ return t.first == filter; });
if (iter == types.end ())
return RPC::invalid_field_error (jss::type);
type = iter->second;
} }
unsigned int limit; unsigned int limit;
@@ -131,7 +108,7 @@ Json::Value doAccountObjects (RPC::Context& context)
return RPC::invalid_field_error (jss::marker); return RPC::invalid_field_error (jss::marker);
} }
if (! RPC::getAccountObjects (*ledger, accountID, type, if (! RPC::getAccountObjects (*ledger, accountID, type.second,
dirIndex, entryIndex, limit, result)) dirIndex, entryIndex, limit, result))
{ {
result[jss::account_objects] = Json::arrayValue; result[jss::account_objects] = Json::arrayValue;

View File

@@ -22,6 +22,7 @@
#include <ripple/ledger/ReadView.h> #include <ripple/ledger/ReadView.h>
#include <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/JsonFields.h> #include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/LedgerFormats.h>
#include <ripple/rpc/impl/RPCHelpers.h> #include <ripple/rpc/impl/RPCHelpers.h>
#include <ripple/rpc/impl/Tuning.h> #include <ripple/rpc/impl/Tuning.h>
#include <ripple/rpc/Context.h> #include <ripple/rpc/Context.h>
@@ -34,6 +35,7 @@ namespace ripple {
// limit: integer, maximum number of entries // limit: integer, maximum number of entries
// marker: opaque, resume point // marker: opaque, resume point
// binary: boolean, format // binary: boolean, format
// type: string // optional, defaults to all ledger node types
// Outputs: // Outputs:
// ledger_hash: chosen ledger's hash // ledger_hash: chosen ledger's hash
// ledger_index: chosen ledger's index // ledger_index: chosen ledger's index
@@ -84,6 +86,13 @@ Json::Value doLedgerData (RPC::Context& context)
LedgerFill::Options::binary : 0)); LedgerFill::Options::binary : 0));
} }
auto type = chooseLedgerEntryType(params);
if (type.first)
{
jvResult.clear();
type.first.inject(jvResult);
return jvResult;
}
Json::Value& nodes = jvResult[jss::state]; Json::Value& nodes = jvResult[jss::state];
auto e = lpLedger->sles.end(); auto e = lpLedger->sles.end();
@@ -98,16 +107,19 @@ Json::Value doLedgerData (RPC::Context& context)
break; break;
} }
if (isBinary) if (type.second == ltINVALID || sle->getType () == type.second)
{ {
Json::Value& entry = nodes.append (Json::objectValue); if (isBinary)
entry[jss::data] = serializeHex(*sle); {
entry[jss::index] = to_string(sle->key()); Json::Value& entry = nodes.append (Json::objectValue);
} entry[jss::data] = serializeHex(*sle);
else entry[jss::index] = to_string(sle->key());
{ }
Json::Value& entry = nodes.append (sle->getJson (0)); else
entry[jss::index] = to_string(sle->key()); {
Json::Value& entry = nodes.append (sle->getJson (0));
entry[jss::index] = to_string(sle->key());
}
} }
} }

View File

@@ -55,6 +55,10 @@ Status LedgerHandler::check()
bool const binary = params[jss::binary].asBool(); bool const binary = params[jss::binary].asBool();
bool const owner_funds = params[jss::owner_funds].asBool(); bool const owner_funds = params[jss::owner_funds].asBool();
bool const queue = params[jss::queue].asBool(); bool const queue = params[jss::queue].asBool();
auto type = chooseLedgerEntryType(params);
if (type.first)
return type.first;
type_ = type.second;
options_ = (full ? LedgerFill::full : 0) options_ = (full ? LedgerFill::full : 0)
| (expand ? LedgerFill::expand : 0) | (expand ? LedgerFill::expand : 0)

View File

@@ -76,6 +76,7 @@ private:
std::vector<TxQ::TxDetails> queueTxs_; std::vector<TxQ::TxDetails> queueTxs_;
Json::Value result_; Json::Value result_;
int options_ = 0; int options_ = 0;
LedgerEntryType type_;
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -89,7 +90,7 @@ void LedgerHandler::writeResult (Object& value)
if (ledger_) if (ledger_)
{ {
Json::copyFrom (value, result_); Json::copyFrom (value, result_);
addJson (value, {*ledger_, options_, queueTxs_}); addJson (value, {*ledger_, options_, queueTxs_, type_});
} }
else else
{ {

View File

@@ -139,7 +139,6 @@ error_code_i fillHandler (Context& context,
JLOG (context.j.trace()) << "COMMAND:" << strCommand; JLOG (context.j.trace()) << "COMMAND:" << strCommand;
JLOG (context.j.trace()) << "REQUEST:" << context.params; JLOG (context.j.trace()) << "REQUEST:" << context.params;
auto handler = getHandler(strCommand); auto handler = getHandler(strCommand);
if (!handler) if (!handler)

View File

@@ -23,6 +23,8 @@
namespace ripple { namespace ripple {
namespace RPC { namespace RPC {
constexpr Status::Code Status::OK;
std::string Status::codeString () const std::string Status::codeString () const
{ {
if (!*this) if (!*this)

View File

@@ -18,6 +18,7 @@
//============================================================================== //==============================================================================
#include <ripple/basics/StringUtilities.h> #include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/JsonFields.h> #include <ripple/protocol/JsonFields.h>
#include <test/jtx.h> #include <test/jtx.h>
@@ -58,7 +59,6 @@ public:
} }
env.close(); env.close();
// no limit specified
// with no limit specified, we get the max_limit if the total number of // with no limit specified, we get the max_limit if the total number of
// accounts is greater than max, which it is here // accounts is greater than max, which it is here
Json::Value jvParams; Json::Value jvParams;
@@ -101,7 +101,6 @@ public:
} }
env.close(); env.close();
// no limit specified
// with no limit specified, we should get all of our fund entries // with no limit specified, we should get all of our fund entries
// plus three more related to the gateway setup // plus three more related to the gateway setup
Json::Value jvParams; Json::Value jvParams;
@@ -189,7 +188,6 @@ public:
} }
env.close(); env.close();
// no limit specified
// with no limit specified, we should get all of our fund entries // with no limit specified, we should get all of our fund entries
// plus three more related to the gateway setup // plus three more related to the gateway setup
Json::Value jvParams; Json::Value jvParams;
@@ -264,6 +262,151 @@ public:
} }
} }
void testLedgerType()
{
// Put a bunch of different LedgerEntryTypes into a ledger
using namespace test::jtx;
Env env { *this, envconfig(validator, ""),
features(featureMultiSign, featureTickets) };
Account const gw { "gateway" };
auto const USD = gw["USD"];
env.fund(XRP(100000), gw);
int const num_accounts = 10;
for (auto i = 0; i < num_accounts; i++)
{
Account const bob { std::string("bob") + std::to_string(i) };
env.fund(XRP(1000), bob);
}
env(offer(Account{"bob0"}, USD(100), XRP(100)));
env.trust(Account{"bob2"}["USD"](100), Account{"bob3"});
auto majorities = getMajorityAmendments(*env.closed());
for (int i = 0; i <= 256; ++i)
{
env.close();
majorities = getMajorityAmendments(*env.closed());
if (!majorities.empty())
break;
}
env(signers(Account{"bob0"}, 1, {{Account{"bob1"}, 1}, {Account{"bob2"}, 1}}));
env(ticket::create(env.master));
env.close();
// Now fetch each type
{ // jvParams[jss::type] = "account";
Json::Value jvParams;
jvParams[jss::ledger_index] = "current";
jvParams[jss::type] = "account";
auto const jrr = env.rpc ( "json", "ledger_data",
boost::lexical_cast<std::string>(jvParams)) [jss::result];
BEAST_EXPECT( checkArraySize(jrr[jss::state], 12) );
for (auto const& j : jrr[jss::state])
BEAST_EXPECT( j["LedgerEntryType"] == "AccountRoot" );
}
{ // jvParams[jss::type] = "amendments";
Json::Value jvParams;
jvParams[jss::ledger_index] = "current";
jvParams[jss::type] = "amendments";
auto const jrr = env.rpc ( "json", "ledger_data",
boost::lexical_cast<std::string>(jvParams)) [jss::result];
BEAST_EXPECT( checkArraySize(jrr[jss::state], 1) );
for (auto const& j : jrr[jss::state])
BEAST_EXPECT( j["LedgerEntryType"] == "Amendments" );
}
{ // jvParams[jss::type] = "directory";
Json::Value jvParams;
jvParams[jss::ledger_index] = "current";
jvParams[jss::type] = "directory";
auto const jrr = env.rpc ( "json", "ledger_data",
boost::lexical_cast<std::string>(jvParams)) [jss::result];
BEAST_EXPECT( checkArraySize(jrr[jss::state], 5) );
for (auto const& j : jrr[jss::state])
BEAST_EXPECT( j["LedgerEntryType"] == "DirectoryNode" );
}
{ // jvParams[jss::type] = "fee";
Json::Value jvParams;
jvParams[jss::ledger_index] = "current";
jvParams[jss::type] = "fee";
auto const jrr = env.rpc ( "json", "ledger_data",
boost::lexical_cast<std::string>(jvParams)) [jss::result];
BEAST_EXPECT( checkArraySize(jrr[jss::state], 1) );
for (auto const& j : jrr[jss::state])
BEAST_EXPECT( j["LedgerEntryType"] == "FeeSettings" );
}
{ // jvParams[jss::type] = "hashes";
Json::Value jvParams;
jvParams[jss::ledger_index] = "current";
jvParams[jss::type] = "hashes";
auto const jrr = env.rpc ( "json", "ledger_data",
boost::lexical_cast<std::string>(jvParams)) [jss::result];
BEAST_EXPECT( checkArraySize(jrr[jss::state], 2) );
for (auto const& j : jrr[jss::state])
BEAST_EXPECT( j["LedgerEntryType"] == "LedgerHashes" );
}
{ // jvParams[jss::type] = "offer";
Json::Value jvParams;
jvParams[jss::ledger_index] = "current";
jvParams[jss::type] = "offer";
auto const jrr = env.rpc ( "json", "ledger_data",
boost::lexical_cast<std::string>(jvParams)) [jss::result];
BEAST_EXPECT( checkArraySize(jrr[jss::state], 1) );
for (auto const& j : jrr[jss::state])
BEAST_EXPECT( j["LedgerEntryType"] == "Offer" );
}
{ // jvParams[jss::type] = "signer_list";
Json::Value jvParams;
jvParams[jss::ledger_index] = "current";
jvParams[jss::type] = "signer_list";
auto const jrr = env.rpc ( "json", "ledger_data",
boost::lexical_cast<std::string>(jvParams)) [jss::result];
BEAST_EXPECT( checkArraySize(jrr[jss::state], 1) );
for (auto const& j : jrr[jss::state])
BEAST_EXPECT( j["LedgerEntryType"] == "SignerList" );
}
{ // jvParams[jss::type] = "state";
Json::Value jvParams;
jvParams[jss::ledger_index] = "current";
jvParams[jss::type] = "state";
auto const jrr = env.rpc ( "json", "ledger_data",
boost::lexical_cast<std::string>(jvParams)) [jss::result];
BEAST_EXPECT( checkArraySize(jrr[jss::state], 1) );
for (auto const& j : jrr[jss::state])
BEAST_EXPECT( j["LedgerEntryType"] == "RippleState" );
}
{ // jvParams[jss::type] = "ticket";
Json::Value jvParams;
jvParams[jss::ledger_index] = "current";
jvParams[jss::type] = "ticket";
auto const jrr = env.rpc ( "json", "ledger_data",
boost::lexical_cast<std::string>(jvParams)) [jss::result];
BEAST_EXPECT( checkArraySize(jrr[jss::state], 1) );
for (auto const& j : jrr[jss::state])
BEAST_EXPECT( j["LedgerEntryType"] == "Ticket" );
}
{ // jvParams[jss::type] = "misspelling";
Json::Value jvParams;
jvParams[jss::ledger_index] = "current";
jvParams[jss::type] = "misspelling";
auto const jrr = env.rpc ( "json", "ledger_data",
boost::lexical_cast<std::string>(jvParams)) [jss::result];
BEAST_EXPECT( jrr.isMember("error") );
BEAST_EXPECT( jrr["error"] == "invalidParams" );
BEAST_EXPECT( jrr["error_message"] == "Invalid field 'type'." );
}
}
void run() void run()
{ {
testCurrentLedgerToLimits(true); testCurrentLedgerToLimits(true);
@@ -272,6 +415,7 @@ public:
testBadInput(); testBadInput();
testMarkerFollow(); testMarkerFollow();
testLedgerHeader(); testLedgerHeader();
testLedgerType();
} }
}; };

View File

@@ -652,6 +652,44 @@ class LedgerRPC_test : public beast::unit_test::suite
} }
void testLedgerAccountsType()
{
testcase("Ledger Request, Accounts Option");
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"]
== "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: public:
void run () void run ()
{ {
@@ -668,6 +706,7 @@ public:
testLookupLedger(); testLookupLedger();
testNoQueue(); testNoQueue();
testQueue(); testQueue();
testLedgerAccountsType();
} }
}; };