mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
feat(rpc): add server_definitions method (#4703)
Add a new RPC / WS call for `server_definitions`, which returns an
SDK-compatible `definitions.json` (binary enum definitions) generated by
the server. This enables clients/libraries to dynamically work with new
fields and features, such as ones that may become available on side
chains. Clients query `server_definitions` on a node from the network
they want to work with, and immediately know how to speak that node's
binary "language", even if new features are added to it in the future
(as long as there are no new serialized types that the software doesn't
know how to serialize/deserialize).
Example:
```js
> {"command": "server_definitions"}
< {
"result": {
"FIELDS": [
[
"Generic",
{
"isSerialized": false,
"isSigningField": false,
"isVLEncoded": false,
"nth": 0,
"type": "Unknown"
}
],
[
"Invalid",
{
"isSerialized": false,
"isSigningField": false,
"isVLEncoded": false,
"nth": -1,
"type": "Unknown"
}
],
[
"ObjectEndMarker",
{
"isSerialized": false,
"isSigningField": true,
"isVLEncoded": false,
"nth": 1,
"type": "STObject"
}
],
...
```
Close #3657
---------
Co-authored-by: Richard Holland <richard.holland@starstone.co.nz>
This commit is contained in:
@@ -169,6 +169,7 @@ printHelp(const po::options_description& desc)
|
||||
" peer_reservations_list\n"
|
||||
" ripple ...\n"
|
||||
" ripple_path_find <json> [<ledger>]\n"
|
||||
" server_definitions [<hash>]\n"
|
||||
" server_info [counters]\n"
|
||||
" server_state [counters]\n"
|
||||
" sign <private_key> <tx_json> [offline]\n"
|
||||
|
||||
@@ -1194,6 +1194,20 @@ private:
|
||||
return jvRequest;
|
||||
}
|
||||
|
||||
// server_definitions [hash]
|
||||
Json::Value
|
||||
parseServerDefinitions(Json::Value const& jvParams)
|
||||
{
|
||||
Json::Value jvRequest{Json::objectValue};
|
||||
|
||||
if (jvParams.size() == 1)
|
||||
{
|
||||
jvRequest[jss::hash] = jvParams[0u].asString();
|
||||
}
|
||||
|
||||
return jvRequest;
|
||||
}
|
||||
|
||||
// server_info [counters]
|
||||
Json::Value
|
||||
parseServerInfo(Json::Value const& jvParams)
|
||||
@@ -1255,6 +1269,7 @@ public:
|
||||
{"channel_verify", &RPCParser::parseChannelVerify, 4, 4},
|
||||
{"connect", &RPCParser::parseConnect, 1, 2},
|
||||
{"consensus_info", &RPCParser::parseAsIs, 0, 0},
|
||||
{"crawl_shards", &RPCParser::parseAsIs, 0, 2},
|
||||
{"deposit_authorized", &RPCParser::parseDepositAuthorized, 2, 3},
|
||||
{"download_shard", &RPCParser::parseDownloadShard, 2, -1},
|
||||
{"feature", &RPCParser::parseFeature, 0, 2},
|
||||
@@ -1292,14 +1307,14 @@ public:
|
||||
1},
|
||||
{"peer_reservations_list", &RPCParser::parseAsIs, 0, 0},
|
||||
{"ripple_path_find", &RPCParser::parseRipplePathFind, 1, 2},
|
||||
{"sign", &RPCParser::parseSignSubmit, 2, 3},
|
||||
{"sign_for", &RPCParser::parseSignFor, 3, 4},
|
||||
{"submit", &RPCParser::parseSignSubmit, 1, 3},
|
||||
{"submit_multisigned", &RPCParser::parseSubmitMultiSigned, 1, 1},
|
||||
{"server_definitions", &RPCParser::parseServerDefinitions, 0, 1},
|
||||
{"server_info", &RPCParser::parseServerInfo, 0, 1},
|
||||
{"server_state", &RPCParser::parseServerInfo, 0, 1},
|
||||
{"crawl_shards", &RPCParser::parseAsIs, 0, 2},
|
||||
{"sign", &RPCParser::parseSignSubmit, 2, 3},
|
||||
{"sign_for", &RPCParser::parseSignFor, 3, 4},
|
||||
{"stop", &RPCParser::parseAsIs, 0, 0},
|
||||
{"submit", &RPCParser::parseSignSubmit, 1, 3},
|
||||
{"submit_multisigned", &RPCParser::parseSubmitMultiSigned, 1, 1},
|
||||
{"transaction_entry", &RPCParser::parseTransactionEntry, 2, 2},
|
||||
{"tx", &RPCParser::parseTx, 1, 4},
|
||||
{"tx_account", &RPCParser::parseTxAccount, 1, 7},
|
||||
|
||||
@@ -52,43 +52,65 @@ class STInteger;
|
||||
class STXChainBridge;
|
||||
class STVector256;
|
||||
|
||||
enum SerializedTypeID {
|
||||
// special types
|
||||
STI_UNKNOWN = -2,
|
||||
STI_NOTPRESENT = 0,
|
||||
#pragma push_macro("XMACRO")
|
||||
#undef XMACRO
|
||||
|
||||
// // types (common)
|
||||
STI_UINT16 = 1,
|
||||
STI_UINT32 = 2,
|
||||
STI_UINT64 = 3,
|
||||
STI_UINT128 = 4,
|
||||
STI_UINT256 = 5,
|
||||
STI_AMOUNT = 6,
|
||||
STI_VL = 7,
|
||||
STI_ACCOUNT = 8,
|
||||
// 9-13 are reserved
|
||||
STI_OBJECT = 14,
|
||||
STI_ARRAY = 15,
|
||||
#define XMACRO(STYPE) \
|
||||
/* special types */ \
|
||||
STYPE(STI_UNKNOWN, -2) \
|
||||
STYPE(STI_NOTPRESENT, 0) \
|
||||
STYPE(STI_UINT16, 1) \
|
||||
\
|
||||
/* types (common) */ \
|
||||
STYPE(STI_UINT32, 2) \
|
||||
STYPE(STI_UINT64, 3) \
|
||||
STYPE(STI_UINT128, 4) \
|
||||
STYPE(STI_UINT256, 5) \
|
||||
STYPE(STI_AMOUNT, 6) \
|
||||
STYPE(STI_VL, 7) \
|
||||
STYPE(STI_ACCOUNT, 8) \
|
||||
\
|
||||
/* 9-13 are reserved */ \
|
||||
STYPE(STI_OBJECT, 14) \
|
||||
STYPE(STI_ARRAY, 15) \
|
||||
\
|
||||
/* types (uncommon) */ \
|
||||
STYPE(STI_UINT8, 16) \
|
||||
STYPE(STI_UINT160, 17) \
|
||||
STYPE(STI_PATHSET, 18) \
|
||||
STYPE(STI_VECTOR256, 19) \
|
||||
STYPE(STI_UINT96, 20) \
|
||||
STYPE(STI_UINT192, 21) \
|
||||
STYPE(STI_UINT384, 22) \
|
||||
STYPE(STI_UINT512, 23) \
|
||||
STYPE(STI_ISSUE, 24) \
|
||||
STYPE(STI_XCHAIN_BRIDGE, 25) \
|
||||
\
|
||||
/* high-level types */ \
|
||||
/* cannot be serialized inside other types */ \
|
||||
STYPE(STI_TRANSACTION, 10001) \
|
||||
STYPE(STI_LEDGERENTRY, 10002) \
|
||||
STYPE(STI_VALIDATION, 10003) \
|
||||
STYPE(STI_METADATA, 10004)
|
||||
|
||||
// types (uncommon)
|
||||
STI_UINT8 = 16,
|
||||
STI_UINT160 = 17,
|
||||
STI_PATHSET = 18,
|
||||
STI_VECTOR256 = 19,
|
||||
STI_UINT96 = 20,
|
||||
STI_UINT192 = 21,
|
||||
STI_UINT384 = 22,
|
||||
STI_UINT512 = 23,
|
||||
STI_ISSUE = 24,
|
||||
STI_XCHAIN_BRIDGE = 25,
|
||||
#pragma push_macro("TO_ENUM")
|
||||
#undef TO_ENUM
|
||||
#pragma push_macro("TO_MAP")
|
||||
#undef TO_MAP
|
||||
|
||||
// high level types
|
||||
// cannot be serialized inside other types
|
||||
STI_TRANSACTION = 10001,
|
||||
STI_LEDGERENTRY = 10002,
|
||||
STI_VALIDATION = 10003,
|
||||
STI_METADATA = 10004,
|
||||
};
|
||||
#define TO_ENUM(name, value) name = value,
|
||||
#define TO_MAP(name, value) {#name, value},
|
||||
|
||||
enum SerializedTypeID { XMACRO(TO_ENUM) };
|
||||
|
||||
static std::map<std::string, int> const sTypeMap = {XMACRO(TO_MAP)};
|
||||
|
||||
#undef XMACRO
|
||||
#undef TO_ENUM
|
||||
|
||||
#pragma pop_macro("XMACRO")
|
||||
#pragma pop_macro("TO_ENUM")
|
||||
#pragma pop_macro("TO_MAP")
|
||||
|
||||
// constexpr
|
||||
inline int
|
||||
@@ -266,6 +288,12 @@ public:
|
||||
static int
|
||||
compare(const SField& f1, const SField& f2);
|
||||
|
||||
static std::map<int, SField const*> const&
|
||||
getKnownCodeToField()
|
||||
{
|
||||
return knownCodeToField;
|
||||
}
|
||||
|
||||
private:
|
||||
static int num;
|
||||
static std::map<int, SField const*> knownCodeToField;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -644,6 +645,11 @@ isTecClaim(TER x)
|
||||
return ((x) >= tecCLAIM);
|
||||
}
|
||||
|
||||
std::unordered_map<
|
||||
TERUnderlyingType,
|
||||
std::pair<char const* const, char const* const>> const&
|
||||
transResults();
|
||||
|
||||
bool
|
||||
transResultInfo(TER code, std::string& token, std::string& text);
|
||||
|
||||
|
||||
@@ -20,13 +20,10 @@
|
||||
#include <ripple/protocol/TER.h>
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace detail {
|
||||
|
||||
static std::unordered_map<
|
||||
std::unordered_map<
|
||||
TERUnderlyingType,
|
||||
std::pair<char const* const, char const* const>> const&
|
||||
transResults()
|
||||
@@ -225,12 +222,10 @@ transResults()
|
||||
return results;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
bool
|
||||
transResultInfo(TER code, std::string& token, std::string& text)
|
||||
{
|
||||
auto& results = detail::transResults();
|
||||
auto& results = transResults();
|
||||
|
||||
auto const r = results.find(TERtoInt(code));
|
||||
|
||||
@@ -264,7 +259,7 @@ std::optional<TER>
|
||||
transCode(std::string const& token)
|
||||
{
|
||||
static auto const results = [] {
|
||||
auto& byTer = detail::transResults();
|
||||
auto& byTer = transResults();
|
||||
auto range = boost::make_iterator_range(byTer.begin(), byTer.end());
|
||||
auto tRange = boost::adaptors::transform(range, [](auto const& r) {
|
||||
return std::make_pair(r.second.first, r.first);
|
||||
|
||||
@@ -321,6 +321,8 @@ JSS(fee_level); // out: AccountInfo
|
||||
JSS(fee_mult_max); // in: TransactionSign
|
||||
JSS(fee_ref); // out: NetworkOPs, DEPRECATED
|
||||
JSS(fetch_pack); // out: NetworkOPs
|
||||
JSS(FIELDS); // out: RPC server_definitions
|
||||
// matches definitions.json format
|
||||
JSS(first); // out: rpc/Version
|
||||
JSS(firstSequence); // out: NodeToShardStatus
|
||||
JSS(firstShardIndex); // out: NodeToShardStatus
|
||||
@@ -365,6 +367,12 @@ JSS(invalid_API_version); // out: Many, when a request has an invalid
|
||||
JSS(io_latency_ms); // out: NetworkOPs
|
||||
JSS(ip); // in: Connect, out: OverlayImpl
|
||||
JSS(is_burned); // out: nft_info (clio)
|
||||
JSS(isSerialized); // out: RPC server_definitions
|
||||
// matches definitions.json format
|
||||
JSS(isSigningField); // out: RPC server_definitions
|
||||
// matches definitions.json format
|
||||
JSS(isVLEncoded); // out: RPC server_definitions
|
||||
// matches definitions.json format
|
||||
JSS(issuer); // in: RipplePathFind, Subscribe,
|
||||
// Unsubscribe, BookOffers
|
||||
// out: STPathSet, STAmount
|
||||
@@ -404,6 +412,8 @@ JSS(ledger_index_min); // in, out: AccountTx*
|
||||
JSS(ledger_max); // in, out: AccountTx*
|
||||
JSS(ledger_min); // in, out: AccountTx*
|
||||
JSS(ledger_time); // out: NetworkOPs
|
||||
JSS(LEDGER_ENTRY_TYPES); // out: RPC server_definitions
|
||||
// matches definitions.json format
|
||||
JSS(levels); // LogLevels
|
||||
JSS(limit); // in/out: AccountTx*, AccountOffers,
|
||||
// AccountLines, AccountObjects
|
||||
@@ -490,6 +500,7 @@ JSS(node_written_bytes); // out: GetCounts
|
||||
JSS(node_writes_duration_us); // out: GetCounts
|
||||
JSS(node_write_retries); // out: GetCounts
|
||||
JSS(node_writes_delayed); // out::GetCounts
|
||||
JSS(nth); // out: RPC server_definitions
|
||||
JSS(obligations); // out: GatewayBalances
|
||||
JSS(offer); // in: LedgerEntry
|
||||
JSS(offers); // out: NetworkOPs, AccountOffers, Subscribe
|
||||
@@ -649,6 +660,12 @@ JSS(transaction); // in: Tx
|
||||
JSS(transaction_hash); // out: RCLCxPeerPos, LedgerToJson
|
||||
JSS(transactions); // out: LedgerToJson,
|
||||
// in: AccountTx*, Unsubscribe
|
||||
JSS(TRANSACTION_RESULTS); // out: RPC server_definitions
|
||||
// matches definitions.json format
|
||||
JSS(TRANSACTION_TYPES); // out: RPC server_definitions
|
||||
// matches definitions.json format
|
||||
JSS(TYPES); // out: RPC server_definitions
|
||||
// matches definitions.json format
|
||||
JSS(transfer_rate); // out: nft_info (clio)
|
||||
JSS(transitions); // out: NetworkOPs
|
||||
JSS(treenode_cache_size); // out: GetCounts
|
||||
@@ -680,7 +697,7 @@ JSS(txr_not_enabled_cnt); // out: peers with tx reduce-relay disabled count
|
||||
JSS(txr_missing_tx_freq); // out: missing tx frequency average
|
||||
JSS(txs); // out: TxHistory
|
||||
JSS(type); // in: AccountObjects
|
||||
// out: NetworkOPs
|
||||
// out: NetworkOPs, RPC server_definitions
|
||||
// OverlayImpl, Logic
|
||||
JSS(type_hex); // out: STPathSet
|
||||
JSS(unl); // out: UnlList
|
||||
|
||||
@@ -127,6 +127,8 @@ doPeerReservationsList(RPC::JsonContext&);
|
||||
Json::Value
|
||||
doRipplePathFind(RPC::JsonContext&);
|
||||
Json::Value
|
||||
doServerDefinitions(RPC::JsonContext&);
|
||||
Json::Value
|
||||
doServerInfo(RPC::JsonContext&); // for humans
|
||||
Json::Value
|
||||
doServerState(RPC::JsonContext&); // for machines
|
||||
|
||||
@@ -22,13 +22,302 @@
|
||||
#include <ripple/app/reporting/P2pProxy.h>
|
||||
#include <ripple/json/json_value.h>
|
||||
#include <ripple/net/RPCErr.h>
|
||||
#include <ripple/protocol/LedgerFormats.h>
|
||||
#include <ripple/protocol/SField.h>
|
||||
#include <ripple/protocol/TER.h>
|
||||
#include <ripple/protocol/TxFormats.h>
|
||||
#include <ripple/protocol/digest.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <ripple/rpc/Context.h>
|
||||
#include <ripple/rpc/Role.h>
|
||||
#include <ripple/rpc/impl/TransactionSign.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace detail {
|
||||
|
||||
class ServerDefinitions
|
||||
{
|
||||
private:
|
||||
std::string
|
||||
// translate e.g. STI_LEDGERENTRY to LedgerEntry
|
||||
translate(std::string const& inp);
|
||||
|
||||
uint256 defsHash_;
|
||||
Json::Value defs_;
|
||||
|
||||
public:
|
||||
ServerDefinitions();
|
||||
|
||||
bool
|
||||
hashMatches(uint256 hash) const
|
||||
{
|
||||
return defsHash_ == hash;
|
||||
}
|
||||
|
||||
Json::Value const&
|
||||
get() const
|
||||
{
|
||||
return defs_;
|
||||
}
|
||||
};
|
||||
|
||||
std::string
|
||||
ServerDefinitions::translate(std::string const& inp)
|
||||
{
|
||||
auto replace = [&](char const* oldStr, char const* newStr) -> std::string {
|
||||
std::string out = inp;
|
||||
boost::replace_all(out, oldStr, newStr);
|
||||
return out;
|
||||
};
|
||||
|
||||
auto contains = [&](char const* s) -> bool {
|
||||
return inp.find(s) != std::string::npos;
|
||||
};
|
||||
|
||||
if (contains("UINT"))
|
||||
{
|
||||
if (contains("256") || contains("160") || contains("128"))
|
||||
return replace("UINT", "Hash");
|
||||
else
|
||||
return replace("UINT", "UInt");
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> replacements{
|
||||
{"OBJECT", "STObject"},
|
||||
{"ARRAY", "STArray"},
|
||||
{"ACCOUNT", "AccountID"},
|
||||
{"LEDGERENTRY", "LedgerEntry"},
|
||||
{"NOTPRESENT", "NotPresent"},
|
||||
{"PATHSET", "PathSet"},
|
||||
{"VL", "Blob"},
|
||||
{"XCHAIN_BRIDGE", "XChainBridge"},
|
||||
};
|
||||
|
||||
if (auto const& it = replacements.find(inp); it != replacements.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::string out;
|
||||
size_t pos = 0;
|
||||
std::string inpToProcess = inp;
|
||||
|
||||
// convert snake_case to CamelCase
|
||||
for (;;)
|
||||
{
|
||||
pos = inpToProcess.find("_");
|
||||
if (pos == std::string::npos)
|
||||
pos = inpToProcess.size();
|
||||
std::string token = inpToProcess.substr(0, pos);
|
||||
if (token.size() > 1)
|
||||
{
|
||||
boost::algorithm::to_lower(token);
|
||||
token.data()[0] -= ('a' - 'A');
|
||||
out += token;
|
||||
}
|
||||
else
|
||||
out += token;
|
||||
if (pos == inpToProcess.size())
|
||||
break;
|
||||
inpToProcess = inpToProcess.substr(pos + 1);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
ServerDefinitions::ServerDefinitions() : defs_{Json::objectValue}
|
||||
{
|
||||
// populate SerializedTypeID names and values
|
||||
defs_[jss::TYPES] = Json::objectValue;
|
||||
|
||||
defs_[jss::TYPES]["Done"] = -1;
|
||||
std::map<int32_t, std::string> typeMap{{-1, "Done"}};
|
||||
for (auto const& [rawName, typeValue] : sTypeMap)
|
||||
{
|
||||
std::string typeName =
|
||||
translate(std::string(rawName).substr(4) /* remove STI_ */);
|
||||
defs_[jss::TYPES][typeName] = typeValue;
|
||||
typeMap[typeValue] = typeName;
|
||||
}
|
||||
|
||||
// populate LedgerEntryType names and values
|
||||
defs_[jss::LEDGER_ENTRY_TYPES] = Json::objectValue;
|
||||
defs_[jss::LEDGER_ENTRY_TYPES][jss::Invalid] = -1;
|
||||
|
||||
for (auto const& f : LedgerFormats::getInstance())
|
||||
{
|
||||
defs_[jss::LEDGER_ENTRY_TYPES][f.getName()] = f.getType();
|
||||
}
|
||||
|
||||
// populate SField serialization data
|
||||
defs_[jss::FIELDS] = Json::arrayValue;
|
||||
|
||||
uint32_t i = 0;
|
||||
{
|
||||
Json::Value a = Json::arrayValue;
|
||||
a[0U] = "Generic";
|
||||
Json::Value v = Json::objectValue;
|
||||
v[jss::nth] = 0;
|
||||
v[jss::isVLEncoded] = false;
|
||||
v[jss::isSerialized] = false;
|
||||
v[jss::isSigningField] = false;
|
||||
v[jss::type] = "Unknown";
|
||||
a[1U] = v;
|
||||
defs_[jss::FIELDS][i++] = a;
|
||||
}
|
||||
|
||||
{
|
||||
Json::Value a = Json::arrayValue;
|
||||
a[0U] = "Invalid";
|
||||
Json::Value v = Json::objectValue;
|
||||
v[jss::nth] = -1;
|
||||
v[jss::isVLEncoded] = false;
|
||||
v[jss::isSerialized] = false;
|
||||
v[jss::isSigningField] = false;
|
||||
v[jss::type] = "Unknown";
|
||||
a[1U] = v;
|
||||
defs_[jss::FIELDS][i++] = a;
|
||||
}
|
||||
|
||||
{
|
||||
Json::Value a = Json::arrayValue;
|
||||
a[0U] = "ObjectEndMarker";
|
||||
Json::Value v = Json::objectValue;
|
||||
v[jss::nth] = 1;
|
||||
v[jss::isVLEncoded] = false;
|
||||
v[jss::isSerialized] = true;
|
||||
v[jss::isSigningField] = true;
|
||||
v[jss::type] = "STObject";
|
||||
a[1U] = v;
|
||||
defs_[jss::FIELDS][i++] = a;
|
||||
}
|
||||
|
||||
{
|
||||
Json::Value a = Json::arrayValue;
|
||||
a[0U] = "ArrayEndMarker";
|
||||
Json::Value v = Json::objectValue;
|
||||
v[jss::nth] = 1;
|
||||
v[jss::isVLEncoded] = false;
|
||||
v[jss::isSerialized] = true;
|
||||
v[jss::isSigningField] = true;
|
||||
v[jss::type] = "STArray";
|
||||
a[1U] = v;
|
||||
defs_[jss::FIELDS][i++] = a;
|
||||
}
|
||||
|
||||
{
|
||||
Json::Value a = Json::arrayValue;
|
||||
a[0U] = "taker_gets_funded";
|
||||
Json::Value v = Json::objectValue;
|
||||
v[jss::nth] = 258;
|
||||
v[jss::isVLEncoded] = false;
|
||||
v[jss::isSerialized] = false;
|
||||
v[jss::isSigningField] = false;
|
||||
v[jss::type] = "Amount";
|
||||
a[1U] = v;
|
||||
defs_[jss::FIELDS][i++] = a;
|
||||
}
|
||||
|
||||
{
|
||||
Json::Value a = Json::arrayValue;
|
||||
a[0U] = "taker_pays_funded";
|
||||
Json::Value v = Json::objectValue;
|
||||
v[jss::nth] = 259;
|
||||
v[jss::isVLEncoded] = false;
|
||||
v[jss::isSerialized] = false;
|
||||
v[jss::isSigningField] = false;
|
||||
v[jss::type] = "Amount";
|
||||
a[1U] = v;
|
||||
defs_[jss::FIELDS][i++] = a;
|
||||
}
|
||||
|
||||
for (auto const& [code, f] : ripple::SField::getKnownCodeToField())
|
||||
{
|
||||
if (f->fieldName == "")
|
||||
continue;
|
||||
|
||||
Json::Value innerObj = Json::objectValue;
|
||||
|
||||
uint32_t type = f->fieldType;
|
||||
|
||||
innerObj[jss::nth] = f->fieldValue;
|
||||
|
||||
// whether the field is variable-length encoded
|
||||
// this means that the length is included before the content
|
||||
innerObj[jss::isVLEncoded] =
|
||||
(type == 7U /* Blob */ || type == 8U /* AccountID */ ||
|
||||
type == 19U /* Vector256 */);
|
||||
|
||||
// whether the field is included in serialization
|
||||
innerObj[jss::isSerialized] =
|
||||
(type < 10000 && f->fieldName != "hash" &&
|
||||
f->fieldName != "index"); /* hash, index, TRANSACTION,
|
||||
LEDGER_ENTRY, VALIDATION, METADATA */
|
||||
|
||||
// whether the field is included in serialization when signing
|
||||
innerObj[jss::isSigningField] = f->shouldInclude(false);
|
||||
|
||||
innerObj[jss::type] = typeMap[type];
|
||||
|
||||
Json::Value innerArray = Json::arrayValue;
|
||||
innerArray[0U] = f->fieldName;
|
||||
innerArray[1U] = innerObj;
|
||||
|
||||
defs_[jss::FIELDS][i++] = innerArray;
|
||||
}
|
||||
|
||||
// populate TER code names and values
|
||||
defs_[jss::TRANSACTION_RESULTS] = Json::objectValue;
|
||||
|
||||
for (auto const& [code, terInfo] : transResults())
|
||||
{
|
||||
defs_[jss::TRANSACTION_RESULTS][terInfo.first] = code;
|
||||
}
|
||||
|
||||
// populate TxType names and values
|
||||
defs_[jss::TRANSACTION_TYPES] = Json::objectValue;
|
||||
defs_[jss::TRANSACTION_TYPES][jss::Invalid] = -1;
|
||||
for (auto const& f : TxFormats::getInstance())
|
||||
{
|
||||
defs_[jss::TRANSACTION_TYPES][f.getName()] = f.getType();
|
||||
}
|
||||
|
||||
// generate hash
|
||||
{
|
||||
const std::string out = Json::FastWriter().write(defs_);
|
||||
defsHash_ = ripple::sha512Half(ripple::Slice{out.data(), out.size()});
|
||||
defs_[jss::hash] = to_string(defsHash_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
Json::Value
|
||||
doServerDefinitions(RPC::JsonContext& context)
|
||||
{
|
||||
auto& params = context.params;
|
||||
|
||||
uint256 hash;
|
||||
if (params.isMember(jss::hash))
|
||||
{
|
||||
if (!params[jss::hash].isString() ||
|
||||
!hash.parseHex(params[jss::hash].asString()))
|
||||
return RPC::invalid_field_error(jss::hash);
|
||||
}
|
||||
|
||||
static const detail::ServerDefinitions defs{};
|
||||
if (defs.hashMatches(hash))
|
||||
{
|
||||
Json::Value jv = Json::objectValue;
|
||||
jv[jss::hash] = to_string(hash);
|
||||
return jv;
|
||||
}
|
||||
return defs.get();
|
||||
}
|
||||
|
||||
Json::Value
|
||||
doServerInfo(RPC::JsonContext& context)
|
||||
{
|
||||
|
||||
@@ -80,6 +80,7 @@ Handler const handlerArray[]{
|
||||
{"channel_verify", byRef(&doChannelVerify), Role::USER, NO_CONDITION},
|
||||
{"connect", byRef(&doConnect), Role::ADMIN, NO_CONDITION},
|
||||
{"consensus_info", byRef(&doConsensusInfo), Role::ADMIN, NO_CONDITION},
|
||||
{"crawl_shards", byRef(&doCrawlShards), Role::ADMIN, NO_CONDITION},
|
||||
{"deposit_authorized",
|
||||
byRef(&doDepositAuthorized),
|
||||
Role::USER,
|
||||
@@ -139,17 +140,20 @@ Handler const handlerArray[]{
|
||||
Role::ADMIN,
|
||||
NO_CONDITION},
|
||||
{"ripple_path_find", byRef(&doRipplePathFind), Role::USER, NO_CONDITION},
|
||||
{"server_definitions",
|
||||
byRef(&doServerDefinitions),
|
||||
Role::USER,
|
||||
NO_CONDITION},
|
||||
{"server_info", byRef(&doServerInfo), Role::USER, NO_CONDITION},
|
||||
{"server_state", byRef(&doServerState), Role::USER, NO_CONDITION},
|
||||
{"sign", byRef(&doSign), Role::USER, NO_CONDITION},
|
||||
{"sign_for", byRef(&doSignFor), Role::USER, NO_CONDITION},
|
||||
{"stop", byRef(&doStop), Role::ADMIN, NO_CONDITION},
|
||||
{"submit", byRef(&doSubmit), Role::USER, NEEDS_CURRENT_LEDGER},
|
||||
{"submit_multisigned",
|
||||
byRef(&doSubmitMultiSigned),
|
||||
Role::USER,
|
||||
NEEDS_CURRENT_LEDGER},
|
||||
{"server_info", byRef(&doServerInfo), Role::USER, NO_CONDITION},
|
||||
{"server_state", byRef(&doServerState), Role::USER, NO_CONDITION},
|
||||
{"crawl_shards", byRef(&doCrawlShards), Role::ADMIN, NO_CONDITION},
|
||||
{"stop", byRef(&doStop), Role::ADMIN, NO_CONDITION},
|
||||
{"transaction_entry", byRef(&doTransactionEntry), Role::USER, NO_CONDITION},
|
||||
{"tx", byRef(&doTxJson), Role::USER, NEEDS_NETWORK_CONNECTION},
|
||||
{"tx_history", byRef(&doTxHistory), Role::USER, NO_CONDITION},
|
||||
|
||||
@@ -79,6 +79,8 @@ admin = 127.0.0.1
|
||||
void
|
||||
testServerInfo()
|
||||
{
|
||||
testcase("server_info");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
{
|
||||
@@ -148,10 +150,104 @@ admin = 127.0.0.1
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testServerDefinitions()
|
||||
{
|
||||
testcase("server_definitions");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
{
|
||||
Env env(*this);
|
||||
auto const result = env.rpc("server_definitions");
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
|
||||
BEAST_EXPECT(result[jss::result][jss::status] == "success");
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::FIELDS));
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
|
||||
BEAST_EXPECT(
|
||||
result[jss::result].isMember(jss::TRANSACTION_RESULTS));
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_TYPES));
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::TYPES));
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
|
||||
|
||||
// test a random element of each result
|
||||
// (testing the whole output would be difficult to maintain)
|
||||
|
||||
{
|
||||
auto const firstField = result[jss::result][jss::FIELDS][0u];
|
||||
BEAST_EXPECT(firstField[0u].asString() == "Generic");
|
||||
BEAST_EXPECT(
|
||||
firstField[1][jss::isSerialized].asBool() == false);
|
||||
BEAST_EXPECT(
|
||||
firstField[1][jss::isSigningField].asBool() == false);
|
||||
BEAST_EXPECT(firstField[1][jss::isVLEncoded].asBool() == false);
|
||||
BEAST_EXPECT(firstField[1][jss::nth].asUInt() == 0);
|
||||
BEAST_EXPECT(firstField[1][jss::type].asString() == "Unknown");
|
||||
}
|
||||
|
||||
BEAST_EXPECT(
|
||||
result[jss::result][jss::LEDGER_ENTRY_TYPES]["AccountRoot"]
|
||||
.asUInt() == 97);
|
||||
BEAST_EXPECT(
|
||||
result[jss::result][jss::TRANSACTION_RESULTS]["tecDIR_FULL"]
|
||||
.asUInt() == 121);
|
||||
BEAST_EXPECT(
|
||||
result[jss::result][jss::TRANSACTION_TYPES]["Payment"]
|
||||
.asUInt() == 0);
|
||||
BEAST_EXPECT(
|
||||
result[jss::result][jss::TYPES]["AccountID"].asUInt() == 8);
|
||||
}
|
||||
|
||||
// test providing the same hash
|
||||
{
|
||||
Env env(*this);
|
||||
auto const firstResult = env.rpc("server_definitions");
|
||||
auto const hash = firstResult[jss::result][jss::hash].asString();
|
||||
auto const hashParam =
|
||||
std::string("{ ") + "\"hash\": \"" + hash + "\"}";
|
||||
|
||||
auto const result =
|
||||
env.rpc("json", "server_definitions", hashParam);
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
|
||||
BEAST_EXPECT(result[jss::result][jss::status] == "success");
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::FIELDS));
|
||||
BEAST_EXPECT(
|
||||
!result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
|
||||
BEAST_EXPECT(
|
||||
!result[jss::result].isMember(jss::TRANSACTION_RESULTS));
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_TYPES));
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::TYPES));
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
|
||||
}
|
||||
|
||||
// test providing a different hash
|
||||
{
|
||||
Env env(*this);
|
||||
std::string const hash =
|
||||
"54296160385A27154BFA70A239DD8E8FD4CC2DB7BA32D970BA3A5B132CF749"
|
||||
"D1";
|
||||
auto const hashParam =
|
||||
std::string("{ ") + "\"hash\": \"" + hash + "\"}";
|
||||
|
||||
auto const result =
|
||||
env.rpc("json", "server_definitions", hashParam);
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
|
||||
BEAST_EXPECT(result[jss::result][jss::status] == "success");
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::FIELDS));
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
|
||||
BEAST_EXPECT(
|
||||
result[jss::result].isMember(jss::TRANSACTION_RESULTS));
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_TYPES));
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::TYPES));
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testServerInfo();
|
||||
testServerDefinitions();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user