Files
rippled/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp
2026-05-05 17:18:26 +00:00

407 lines
12 KiB
C++

#include <xrpld/rpc/handlers/server_info/ServerDefinitions.h>
#include <xrpld/rpc/Context.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/json_writer.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/digest.h>
#include <xrpl/protocol/jss.h>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <cstddef>
#include <cstdint>
#include <map>
#include <set>
#include <string>
#include <string_view>
#include <unordered_map>
namespace xrpl {
namespace detail {
class ServerDefinitions
{
private:
static std::string
// translate e.g. STI_LEDGERENTRY to LedgerEntry
translate(std::string const& inp);
uint256 defsHash_;
json::Value defs_;
public:
ServerDefinitions();
[[nodiscard]] bool
hashMatches(uint256 hash) const
{
return defsHash_ == hash;
}
[[nodiscard]] json::Value const&
get() const
{
return defs_;
}
};
std::string
ServerDefinitions::translate(std::string const& inp)
{
auto replace = [&](std::string_view oldStr, std::string_view newStr) -> std::string {
std::string out = inp;
boost::replace_all(out, oldStr, newStr);
return out;
};
// TODO: use string::contains with C++23
auto contains = [&](std::string_view s) -> bool { return inp.find(s) != std::string::npos; };
if (contains("UINT"))
{
if (contains("512") || contains("384") || contains("256") || contains("192") ||
contains("160") || contains("128"))
{
return replace("UINT", "Hash");
}
return replace("UINT", "UInt");
}
static std::unordered_map<std::string_view, std::string_view> const kREPLACEMENTS{
{"OBJECT", "STObject"},
{"ARRAY", "STArray"},
{"ACCOUNT", "AccountID"},
{"LEDGERENTRY", "LedgerEntry"},
{"NOTPRESENT", "NotPresent"},
{"PATHSET", "PathSet"},
{"VL", "Blob"},
{"XCHAIN_BRIDGE", "XChainBridge"},
};
if (auto const& it = kREPLACEMENTS.find(inp); it != kREPLACEMENTS.end())
{
return std::string(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] : kS_TYPE_MAP)
{
std::string const 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] = "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;
}
// copy into a sorted map to ensure deterministic output order (sorted by fieldCode)
static std::map<int, SField const*> const kSORTED_FIELDS(
xrpl::SField::getKnownCodeToField().begin(), xrpl::SField::getKnownCodeToField().end());
for (auto const& [code, field] : kSORTED_FIELDS)
{
if (field->fieldName.empty())
continue;
json::Value innerObj = json::ObjectValue;
int32_t const type = field->fieldType;
innerObj[jss::nth] = field->fieldValue;
// whether the field is variable-length encoded this means that the length is included
// before the content
innerObj[jss::isVLEncoded] =
(type == STI_VL || type == STI_ACCOUNT || type == STI_VECTOR256);
static_assert(
STI_VL == 7U && STI_ACCOUNT == 8U && STI_VECTOR256 == 19U,
"STI_VL, STI_ACCOUNT, STI_VECTOR256 must be 7, 8, 19 respectively");
// whether the field is included in serialization
innerObj[jss::isSerialized] =
(type < 10000 && field->fieldName != "hash" &&
field->fieldName !=
"index"); // hash, index, TRANSACTION, LEDGER_ENTRY, VALIDATION, METADATA
// whether the field is included in serialization when signing
innerObj[jss::isSigningField] = field->shouldInclude(false);
innerObj[jss::type] = typeMap[type];
json::Value innerArray = json::ArrayValue;
innerArray[0U] = field->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();
}
// populate TxFormats
defs_[jss::TRANSACTION_FORMATS] = json::ObjectValue;
defs_[jss::TRANSACTION_FORMATS][jss::common] = json::ArrayValue;
auto txCommonFields = std::set<std::string>();
for (auto const& element : TxFormats::getCommonFields())
{
json::Value elementObj = json::ObjectValue;
elementObj[jss::name] = element.sField().getName();
elementObj[jss::optionality] = element.style();
defs_[jss::TRANSACTION_FORMATS][jss::common].append(elementObj);
txCommonFields.insert(element.sField().getName());
}
for (auto const& format : TxFormats::getInstance())
{
auto const& soTemplate = format.getSOTemplate();
json::Value templateArray = json::ArrayValue;
for (auto const& element : soTemplate)
{
if (txCommonFields.contains(element.sField().getName()))
continue; // skip common fields, already added
json::Value elementObj = json::ObjectValue;
elementObj[jss::name] = element.sField().getName();
elementObj[jss::optionality] = element.style();
templateArray.append(elementObj);
}
defs_[jss::TRANSACTION_FORMATS][format.getName()] = templateArray;
}
// populate LedgerFormats
defs_[jss::LEDGER_ENTRY_FORMATS] = json::ObjectValue;
defs_[jss::LEDGER_ENTRY_FORMATS][jss::common] = json::ArrayValue;
auto ledgerCommonFields = std::set<std::string>();
for (auto const& element : LedgerFormats::getCommonFields())
{
json::Value elementObj = json::ObjectValue;
elementObj[jss::name] = element.sField().getName();
elementObj[jss::optionality] = element.style();
defs_[jss::LEDGER_ENTRY_FORMATS][jss::common].append(elementObj);
ledgerCommonFields.insert(element.sField().getName());
}
for (auto const& format : LedgerFormats::getInstance())
{
auto const& soTemplate = format.getSOTemplate();
json::Value templateArray = json::ArrayValue;
for (auto const& element : soTemplate)
{
if (ledgerCommonFields.contains(element.sField().getName()))
continue; // skip common fields, already added
json::Value elementObj = json::ObjectValue;
elementObj[jss::name] = element.sField().getName();
elementObj[jss::optionality] = element.style();
templateArray.append(elementObj);
}
defs_[jss::LEDGER_ENTRY_FORMATS][format.getName()] = templateArray;
}
defs_[jss::TRANSACTION_FLAGS] = json::ObjectValue;
for (auto const& [name, value] : getAllTxFlags())
{
json::Value txObj = json::ObjectValue;
for (auto const& [flagName, flagValue] : value)
{
txObj[flagName] = flagValue;
}
defs_[jss::TRANSACTION_FLAGS][name] = txObj;
}
defs_[jss::LEDGER_ENTRY_FLAGS] = json::ObjectValue;
for (auto const& [name, value] : getAllLedgerFlags())
{
json::Value ledgerObj = json::ObjectValue;
for (auto const& [flagName, flagValue] : value)
{
ledgerObj[flagName] = flagValue;
}
defs_[jss::LEDGER_ENTRY_FLAGS][name] = ledgerObj;
}
defs_[jss::ACCOUNT_SET_FLAGS] = json::ObjectValue;
for (auto const& [name, value] : getAsfFlagMap())
{
defs_[jss::ACCOUNT_SET_FLAGS][name] = value;
}
// generate hash
{
std::string const out = json::FastWriter().write(defs_);
defsHash_ = xrpl::sha512Half(xrpl::Slice{out.data(), out.size()});
defs_[jss::hash] = to_string(defsHash_);
}
}
ServerDefinitions const&
getDefinitions()
{
static ServerDefinitions const kDEFS{};
return kDEFS;
}
} // namespace detail
json::Value const&
getServerDefinitionsJson()
{
return detail::getDefinitions().get();
}
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::invalidFieldError(jss::hash);
}
auto const& defs = detail::getDefinitions();
if (defs.hashMatches(hash))
{
json::Value jv = json::ObjectValue;
jv[jss::hash] = to_string(hash);
return jv;
}
return defs.get();
}
} // namespace xrpl