mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-18 18:55:51 +00:00
@@ -61,6 +61,7 @@ target_sources(clio PRIVATE
|
||||
src/rpc/ngHandlers/AccountCurrencies.cpp
|
||||
src/rpc/ngHandlers/Tx.cpp
|
||||
src/rpc/ngHandlers/GatewayBalances.cpp
|
||||
src/rpc/ngHandlers/LedgerEntry.cpp
|
||||
## RPC Methods
|
||||
# Account
|
||||
src/rpc/handlers/AccountChannels.cpp
|
||||
@@ -124,7 +125,8 @@ if(BUILD_TESTS)
|
||||
unittests/rpc/handlers/PingTest.cpp
|
||||
unittests/rpc/handlers/AccountChannelsTest.cpp
|
||||
unittests/rpc/handlers/TxTest.cpp
|
||||
unittests/rpc/handlers/GatewayBalancesTest.cpp)
|
||||
unittests/rpc/handlers/GatewayBalancesTest.cpp
|
||||
unittests/rpc/handlers/LedgerEntryTest.cpp)
|
||||
include(CMake/deps/gtest.cmake)
|
||||
|
||||
# if CODE_COVERAGE enable, add clio_test-ccov
|
||||
|
||||
@@ -36,6 +36,9 @@ Section::verify(boost::json::value const& value, std::string_view key) const
|
||||
// instead
|
||||
|
||||
auto const& res = value.at(key.data());
|
||||
// if it is not a json object, let other validators fail
|
||||
if (!res.is_object())
|
||||
return {};
|
||||
for (auto const& spec : specs)
|
||||
{
|
||||
if (auto const ret = spec.validate(res); not ret)
|
||||
@@ -97,17 +100,19 @@ checkIsU32Numeric(std::string_view sv)
|
||||
return ec == std::errc();
|
||||
}
|
||||
|
||||
CustomValidator LedgerHashValidator = CustomValidator{
|
||||
CustomValidator Uint256HexStringValidator = CustomValidator{
|
||||
[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
if (!value.is_string())
|
||||
{
|
||||
return Error{RPC::Status{
|
||||
RPC::RippledError::rpcINVALID_PARAMS, "ledgerHashNotString"}};
|
||||
RPC::RippledError::rpcINVALID_PARAMS,
|
||||
std::string(key) + "NotString"}};
|
||||
}
|
||||
ripple::uint256 ledgerHash;
|
||||
if (!ledgerHash.parseHex(value.as_string().c_str()))
|
||||
return Error{RPC::Status{
|
||||
RPC::RippledError::rpcINVALID_PARAMS, "ledgerHashMalformed"}};
|
||||
RPC::RippledError::rpcINVALID_PARAMS,
|
||||
std::string(key) + "Malformed"}};
|
||||
return MaybeError{};
|
||||
}};
|
||||
|
||||
@@ -146,6 +151,21 @@ CustomValidator AccountValidator = CustomValidator{
|
||||
return MaybeError{};
|
||||
}};
|
||||
|
||||
CustomValidator AccountBase58Validator = CustomValidator{
|
||||
[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
if (!value.is_string())
|
||||
{
|
||||
return Error{RPC::Status{
|
||||
RPC::RippledError::rpcINVALID_PARAMS,
|
||||
std::string(key) + "NotString"}};
|
||||
}
|
||||
auto const account =
|
||||
ripple::parseBase58<ripple::AccountID>(value.as_string().c_str());
|
||||
if (!account || account->isZero())
|
||||
return Error{RPC::Status{RPC::ClioError::rpcMALFORMED_ADDRESS}};
|
||||
return MaybeError{};
|
||||
}};
|
||||
|
||||
CustomValidator MarkerValidator = CustomValidator{
|
||||
[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
if (!value.is_string())
|
||||
@@ -165,7 +185,7 @@ CustomValidator MarkerValidator = CustomValidator{
|
||||
return MaybeError{};
|
||||
}};
|
||||
|
||||
CustomValidator TxHashValidator = CustomValidator{
|
||||
CustomValidator CurrencyValidator = CustomValidator{
|
||||
[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
if (!value.is_string())
|
||||
{
|
||||
@@ -173,12 +193,10 @@ CustomValidator TxHashValidator = CustomValidator{
|
||||
RPC::RippledError::rpcINVALID_PARAMS,
|
||||
std::string(key) + "NotString"}};
|
||||
}
|
||||
ripple::uint256 txHash;
|
||||
if (!txHash.parseHex(value.as_string().c_str()))
|
||||
{
|
||||
ripple::Currency currency;
|
||||
if (!ripple::to_currency(currency, value.as_string().c_str()))
|
||||
return Error{RPC::Status{
|
||||
RPC::RippledError::rpcINVALID_PARAMS, "malformedTransaction"}};
|
||||
}
|
||||
RPC::ClioError::rpcMALFORMED_CURRENCY, "malformedCurrency"}};
|
||||
return MaybeError{};
|
||||
}};
|
||||
|
||||
|
||||
@@ -415,19 +415,13 @@ public:
|
||||
[[nodiscard]] bool
|
||||
checkIsU32Numeric(std::string_view sv);
|
||||
|
||||
/**
|
||||
* @brief Provide a common used validator for ledger hash
|
||||
* LedgerHash must be a string and hex
|
||||
*/
|
||||
extern CustomValidator LedgerIndexValidator;
|
||||
|
||||
/**
|
||||
* @brief Provide a common used validator for ledger index
|
||||
* LedgerIndex must be a string or int
|
||||
* If the specified LedgerIndex is a string, it's value must be either
|
||||
* "validated" or a valid integer value represented as a string.
|
||||
*/
|
||||
extern CustomValidator LedgerHashValidator;
|
||||
extern CustomValidator LedgerIndexValidator;
|
||||
|
||||
/**
|
||||
* @brief Provide a common used validator for account
|
||||
@@ -435,6 +429,12 @@ extern CustomValidator LedgerHashValidator;
|
||||
*/
|
||||
extern CustomValidator AccountValidator;
|
||||
|
||||
/**
|
||||
* @brief Provide a common used validator for account
|
||||
* Account must be a string and can convert to base58
|
||||
*/
|
||||
extern CustomValidator AccountBase58Validator;
|
||||
|
||||
/**
|
||||
* @brief Provide a common used validator for marker
|
||||
* Marker is composed of a comma separated index and start hint. The
|
||||
@@ -443,9 +443,16 @@ extern CustomValidator AccountValidator;
|
||||
extern CustomValidator MarkerValidator;
|
||||
|
||||
/**
|
||||
* @brief Provide a common used validator for transaction hash
|
||||
* @brief Provide a common used validator for uint256 hex string
|
||||
* It must be a string and hex
|
||||
* Transaction index, ledger hash all use this validator
|
||||
*/
|
||||
extern CustomValidator TxHashValidator;
|
||||
extern CustomValidator Uint256HexStringValidator;
|
||||
|
||||
/**
|
||||
* @brief Provide a common used validator for currency
|
||||
* including standard currency code and token code
|
||||
*/
|
||||
extern CustomValidator CurrencyValidator;
|
||||
|
||||
} // namespace RPCng::validation
|
||||
|
||||
@@ -88,7 +88,7 @@ public:
|
||||
static const RpcSpec rpcSpec = {
|
||||
{"account", validation::Required{}, validation::AccountValidator},
|
||||
{"destination_account", validation::Type<std::string>{},validation::AccountValidator},
|
||||
{"ledger_hash", validation::LedgerHashValidator},
|
||||
{"ledger_hash", validation::Uint256HexStringValidator},
|
||||
{"limit", validation::Type<uint32_t>{},validation::Between{10,400}},
|
||||
{"ledger_index", validation::LedgerIndexValidator},
|
||||
{"marker", validation::MarkerValidator}
|
||||
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
{
|
||||
static const RpcSpec rpcSpec = {
|
||||
{"account", validation::Required{}, validation::AccountValidator},
|
||||
{"ledger_hash", validation::LedgerHashValidator},
|
||||
{"ledger_hash", validation::Uint256HexStringValidator},
|
||||
{"ledger_index", validation::LedgerIndexValidator}};
|
||||
return rpcSpec;
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ public:
|
||||
|
||||
static const RpcSpec rpcSpec = {
|
||||
{"account", validation::Required{}, validation::AccountValidator},
|
||||
{"ledger_hash", validation::LedgerHashValidator},
|
||||
{"ledger_hash", validation::Uint256HexStringValidator},
|
||||
{"ledger_index", validation::LedgerIndexValidator},
|
||||
{"hotwallet", hotWalletValidator}};
|
||||
return rpcSpec;
|
||||
|
||||
282
src/rpc/ngHandlers/LedgerEntry.cpp
Normal file
282
src/rpc/ngHandlers/LedgerEntry.cpp
Normal file
@@ -0,0 +1,282 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2023, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and 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 <rpc/RPCHelpers.h>
|
||||
#include <rpc/ngHandlers/LedgerEntry.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace RPCng {
|
||||
LedgerEntryHandler::Result
|
||||
LedgerEntryHandler::process(
|
||||
LedgerEntryHandler::Input input,
|
||||
boost::asio::yield_context& yield) const
|
||||
{
|
||||
ripple::uint256 key;
|
||||
if (input.index)
|
||||
{
|
||||
key = ripple::uint256{std::string_view(*(input.index))};
|
||||
}
|
||||
else if (input.accountRoot)
|
||||
{
|
||||
key = ripple::keylet::account(
|
||||
*ripple::parseBase58<ripple::AccountID>(*(input.accountRoot)))
|
||||
.key;
|
||||
}
|
||||
else if (input.directory)
|
||||
{
|
||||
auto const keyOrStatus = composeKeyFromDirectory(*input.directory);
|
||||
if (auto const status = std::get_if<RPC::Status>(&keyOrStatus))
|
||||
return Error{*status};
|
||||
key = std::get<ripple::uint256>(keyOrStatus);
|
||||
}
|
||||
else if (input.offer)
|
||||
{
|
||||
auto const id = ripple::parseBase58<ripple::AccountID>(
|
||||
input.offer->at("account").as_string().c_str());
|
||||
key = ripple::keylet::offer(
|
||||
*id,
|
||||
boost::json::value_to<std::uint32_t>(input.offer->at("seq")))
|
||||
.key;
|
||||
}
|
||||
else if (input.rippleStateAccount)
|
||||
{
|
||||
auto const id1 = ripple::parseBase58<ripple::AccountID>(
|
||||
input.rippleStateAccount->at("accounts")
|
||||
.as_array()
|
||||
.at(0)
|
||||
.as_string()
|
||||
.c_str());
|
||||
auto const id2 = ripple::parseBase58<ripple::AccountID>(
|
||||
input.rippleStateAccount->at("accounts")
|
||||
.as_array()
|
||||
.at(1)
|
||||
.as_string()
|
||||
.c_str());
|
||||
auto const currency = ripple::to_currency(
|
||||
input.rippleStateAccount->at("currency").as_string().c_str());
|
||||
key = ripple::keylet::line(*id1, *id2, currency).key;
|
||||
}
|
||||
else if (input.escrow)
|
||||
{
|
||||
auto const id = ripple::parseBase58<ripple::AccountID>(
|
||||
input.escrow->at("owner").as_string().c_str());
|
||||
key =
|
||||
ripple::keylet::escrow(*id, input.escrow->at("seq").as_int64()).key;
|
||||
}
|
||||
else if (input.depositPreauth)
|
||||
{
|
||||
auto const owner = ripple::parseBase58<ripple::AccountID>(
|
||||
input.depositPreauth->at("owner").as_string().c_str());
|
||||
auto const authorized = ripple::parseBase58<ripple::AccountID>(
|
||||
input.depositPreauth->at("authorized").as_string().c_str());
|
||||
key = ripple::keylet::depositPreauth(*owner, *authorized).key;
|
||||
}
|
||||
else if (input.ticket)
|
||||
{
|
||||
auto const id = ripple::parseBase58<ripple::AccountID>(
|
||||
input.ticket->at("account").as_string().c_str());
|
||||
key = ripple::getTicketIndex(
|
||||
*id, input.ticket->at("ticket_seq").as_int64());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Must specify 1 of the following fields to indicate what type
|
||||
return Error{
|
||||
RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "unknownOption"}};
|
||||
}
|
||||
|
||||
// check ledger exists
|
||||
auto const range = sharedPtrBackend_->fetchLedgerRange();
|
||||
auto const lgrInfoOrStatus = RPC::getLedgerInfoFromHashOrSeq(
|
||||
*sharedPtrBackend_,
|
||||
yield,
|
||||
input.ledgerHash,
|
||||
input.ledgerIndex,
|
||||
range->maxSequence);
|
||||
|
||||
if (auto const status = std::get_if<RPC::Status>(&lgrInfoOrStatus))
|
||||
return Error{*status};
|
||||
|
||||
auto const lgrInfo = std::get<ripple::LedgerInfo>(lgrInfoOrStatus);
|
||||
auto const ledgerObject =
|
||||
sharedPtrBackend_->fetchLedgerObject(key, lgrInfo.seq, yield);
|
||||
if (!ledgerObject || ledgerObject->size() == 0)
|
||||
return Error{RPC::Status{"entryNotFound"}};
|
||||
|
||||
ripple::STLedgerEntry const sle{
|
||||
ripple::SerialIter{ledgerObject->data(), ledgerObject->size()}, key};
|
||||
if (input.expectedType != ripple::ltANY &&
|
||||
sle.getType() != input.expectedType)
|
||||
return Error{RPC::Status{"unexpectedLedgerType"}};
|
||||
|
||||
LedgerEntryHandler::Output output;
|
||||
output.index = ripple::strHex(key);
|
||||
output.ledgerIndex = lgrInfo.seq;
|
||||
output.ledgerHash = ripple::strHex(lgrInfo.hash);
|
||||
if (input.binary)
|
||||
{
|
||||
output.nodeBinary = ripple::strHex(*ledgerObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
output.node = RPC::toJson(sle);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
std::variant<ripple::uint256, RPC::Status>
|
||||
LedgerEntryHandler::composeKeyFromDirectory(
|
||||
boost::json::object const& directory) const noexcept
|
||||
{
|
||||
// can not specify both dir_root and owner.
|
||||
if (directory.contains("dir_root") && directory.contains("owner"))
|
||||
return RPC::Status{
|
||||
RPC::RippledError::rpcINVALID_PARAMS,
|
||||
"mayNotSpecifyBothDirRootAndOwner"};
|
||||
// at least one should availiable
|
||||
if (!(directory.contains("dir_root") || directory.contains("owner")))
|
||||
return RPC::Status{
|
||||
RPC::RippledError::rpcINVALID_PARAMS, "missingOwnerOrDirRoot"};
|
||||
|
||||
uint64_t const subIndex = directory.contains("sub_index")
|
||||
? boost::json::value_to<uint64_t>(directory.at("sub_index"))
|
||||
: 0;
|
||||
|
||||
if (directory.contains("dir_root"))
|
||||
{
|
||||
ripple::uint256 const uDirRoot{
|
||||
directory.at("dir_root").as_string().c_str()};
|
||||
return ripple::keylet::page(uDirRoot, subIndex).key;
|
||||
}
|
||||
|
||||
auto const ownerID = ripple::parseBase58<ripple::AccountID>(
|
||||
directory.at("owner").as_string().c_str());
|
||||
return ripple::keylet::page(ripple::keylet::ownerDir(*ownerID), subIndex)
|
||||
.key;
|
||||
}
|
||||
|
||||
void
|
||||
tag_invoke(
|
||||
boost::json::value_from_tag,
|
||||
boost::json::value& jv,
|
||||
LedgerEntryHandler::Output const& output)
|
||||
{
|
||||
auto object = boost::json::object{
|
||||
{"ledger_hash", output.ledgerHash},
|
||||
{"ledger_index", output.ledgerIndex},
|
||||
{"validated", output.validated},
|
||||
{"index", output.index}};
|
||||
if (output.nodeBinary)
|
||||
{
|
||||
object["node_binary"] = *(output.nodeBinary);
|
||||
}
|
||||
else
|
||||
{
|
||||
object["node"] = *(output.node);
|
||||
}
|
||||
jv = std::move(object);
|
||||
}
|
||||
|
||||
LedgerEntryHandler::Input
|
||||
tag_invoke(
|
||||
boost::json::value_to_tag<LedgerEntryHandler::Input>,
|
||||
boost::json::value const& jv)
|
||||
{
|
||||
auto const& jsonObject = jv.as_object();
|
||||
LedgerEntryHandler::Input input;
|
||||
if (jsonObject.contains("ledger_hash"))
|
||||
{
|
||||
input.ledgerHash = jv.at("ledger_hash").as_string().c_str();
|
||||
}
|
||||
if (jsonObject.contains("ledger_index"))
|
||||
{
|
||||
if (!jsonObject.at("ledger_index").is_string())
|
||||
{
|
||||
input.ledgerIndex = jv.at("ledger_index").as_int64();
|
||||
}
|
||||
else if (jsonObject.at("ledger_index").as_string() != "validated")
|
||||
{
|
||||
input.ledgerIndex =
|
||||
std::stoi(jv.at("ledger_index").as_string().c_str());
|
||||
}
|
||||
}
|
||||
if (jsonObject.contains("binary"))
|
||||
{
|
||||
input.binary = jv.at("binary").as_bool();
|
||||
}
|
||||
// check all the protential index
|
||||
static auto const indexFieldTypeMap =
|
||||
std::unordered_map<std::string, ripple::LedgerEntryType>{
|
||||
{"index", ripple::ltANY},
|
||||
{"directory", ripple::ltDIR_NODE},
|
||||
{"offer", ripple::ltOFFER},
|
||||
{"check", ripple::ltCHECK},
|
||||
{"escrow", ripple::ltESCROW},
|
||||
{"payment_channel", ripple::ltPAYCHAN},
|
||||
{"deposit_preauth", ripple::ltDEPOSIT_PREAUTH},
|
||||
{"ticket", ripple::ltTICKET}};
|
||||
|
||||
auto const indexFieldType = std::find_if(
|
||||
indexFieldTypeMap.begin(),
|
||||
indexFieldTypeMap.end(),
|
||||
[&jsonObject](auto const& pair) {
|
||||
auto const& [field, _] = pair;
|
||||
return jsonObject.contains(field) &&
|
||||
jsonObject.at(field).is_string();
|
||||
});
|
||||
if (indexFieldType != indexFieldTypeMap.end())
|
||||
{
|
||||
input.index = jv.at(indexFieldType->first).as_string().c_str();
|
||||
input.expectedType = indexFieldType->second;
|
||||
}
|
||||
// check if request for account root
|
||||
else if (jsonObject.contains("account_root"))
|
||||
{
|
||||
input.accountRoot = jv.at("account_root").as_string().c_str();
|
||||
}
|
||||
// no need to check if_object again, validator only allows string or object
|
||||
else if (jsonObject.contains("directory"))
|
||||
{
|
||||
input.directory = jv.at("directory").as_object();
|
||||
}
|
||||
else if (jsonObject.contains("offer"))
|
||||
{
|
||||
input.offer = jv.at("offer").as_object();
|
||||
}
|
||||
else if (jsonObject.contains("ripple_state"))
|
||||
{
|
||||
input.rippleStateAccount = jv.at("ripple_state").as_object();
|
||||
}
|
||||
else if (jsonObject.contains("escrow"))
|
||||
{
|
||||
input.escrow = jv.at("escrow").as_object();
|
||||
}
|
||||
else if (jsonObject.contains("deposit_preauth"))
|
||||
{
|
||||
input.depositPreauth = jv.at("deposit_preauth").as_object();
|
||||
}
|
||||
else if (jsonObject.contains("ticket"))
|
||||
{
|
||||
input.ticket = jv.at("ticket").as_object();
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
} // namespace RPCng
|
||||
209
src/rpc/ngHandlers/LedgerEntry.h
Normal file
209
src/rpc/ngHandlers/LedgerEntry.h
Normal file
@@ -0,0 +1,209 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2023, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and 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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <rpc/common/Types.h>
|
||||
#include <rpc/common/Validators.h>
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
|
||||
namespace RPCng {
|
||||
|
||||
class LedgerEntryHandler
|
||||
{
|
||||
std::shared_ptr<BackendInterface> sharedPtrBackend_;
|
||||
|
||||
public:
|
||||
struct Output
|
||||
{
|
||||
std::string index;
|
||||
uint32_t ledgerIndex;
|
||||
std::string ledgerHash;
|
||||
std::optional<boost::json::object> node;
|
||||
std::optional<std::string> nodeBinary;
|
||||
bool validated = true;
|
||||
};
|
||||
|
||||
// TODO: nft_page has not been implemented
|
||||
struct Input
|
||||
{
|
||||
std::optional<std::string> ledgerHash;
|
||||
std::optional<uint32_t> ledgerIndex;
|
||||
bool binary = false;
|
||||
// id of this ledger entry: 256 bits hex string
|
||||
std::optional<std::string> index;
|
||||
// index can be extracted from payment_channel, check, escrow, offer
|
||||
// etc, expectedType is used to save the type of index
|
||||
ripple::LedgerEntryType expectedType = ripple::ltANY;
|
||||
// account id to address account root object
|
||||
std::optional<std::string> accountRoot;
|
||||
// TODO: extract into custom objects, remove json from Input
|
||||
std::optional<boost::json::object> directory;
|
||||
std::optional<boost::json::object> offer;
|
||||
std::optional<boost::json::object> rippleStateAccount;
|
||||
std::optional<boost::json::object> escrow;
|
||||
std::optional<boost::json::object> depositPreauth;
|
||||
std::optional<boost::json::object> ticket;
|
||||
};
|
||||
|
||||
using Result = RPCng::HandlerReturnType<Output>;
|
||||
|
||||
LedgerEntryHandler(
|
||||
std::shared_ptr<BackendInterface> const& sharedPtrBackend)
|
||||
: sharedPtrBackend_(sharedPtrBackend)
|
||||
{
|
||||
}
|
||||
|
||||
RpcSpecConstRef
|
||||
spec() const
|
||||
{
|
||||
// Validator only works in this handler
|
||||
// The accounts array must have two different elements
|
||||
// Each element must be a valid address
|
||||
static auto const rippleStateAccountsCheck =
|
||||
validation::CustomValidator{
|
||||
[](boost::json::value const& value,
|
||||
std::string_view key) -> MaybeError {
|
||||
if (!value.is_array() || value.as_array().size() != 2 ||
|
||||
!value.as_array()[0].is_string() ||
|
||||
!value.as_array()[1].is_string() ||
|
||||
value.as_array()[0].as_string() ==
|
||||
value.as_array()[1].as_string())
|
||||
return Error{RPC::Status{
|
||||
RPC::RippledError::rpcINVALID_PARAMS,
|
||||
"malformedAccounts"}};
|
||||
auto const id1 = ripple::parseBase58<ripple::AccountID>(
|
||||
value.as_array()[0].as_string().c_str());
|
||||
auto const id2 = ripple::parseBase58<ripple::AccountID>(
|
||||
value.as_array()[1].as_string().c_str());
|
||||
if (!id1 || !id2)
|
||||
return Error{RPC::Status{
|
||||
RPC::ClioError::rpcMALFORMED_ADDRESS,
|
||||
"malformedAddresses"}};
|
||||
return MaybeError{};
|
||||
}};
|
||||
|
||||
static const RpcSpec rpcSpec = {
|
||||
{"binary", validation::Type<bool>{}},
|
||||
{"ledger_hash", validation::Uint256HexStringValidator},
|
||||
{"ledger_index", validation::LedgerIndexValidator},
|
||||
{"index", validation::Uint256HexStringValidator},
|
||||
{"account_root", validation::AccountBase58Validator},
|
||||
{"check", validation::Uint256HexStringValidator},
|
||||
{"deposit_preauth",
|
||||
validation::Type<std::string, boost::json::object>{},
|
||||
validation::IfType<std::string>{
|
||||
validation::Uint256HexStringValidator},
|
||||
validation::IfType<boost::json::object>{
|
||||
validation::Section{
|
||||
{"owner",
|
||||
validation::Required{},
|
||||
validation::AccountBase58Validator},
|
||||
{"authorized",
|
||||
validation::Required{},
|
||||
validation::AccountBase58Validator},
|
||||
},
|
||||
}},
|
||||
{"directory",
|
||||
validation::Type<std::string, boost::json::object>{},
|
||||
validation::IfType<std::string>{
|
||||
validation::Uint256HexStringValidator},
|
||||
validation::IfType<boost::json::object>{validation::Section{
|
||||
{"owner", validation::AccountBase58Validator},
|
||||
{"dir_root", validation::Uint256HexStringValidator},
|
||||
{"sub_index", validation::Type<uint32_t>{}}}}},
|
||||
{"escrow",
|
||||
validation::Type<std::string, boost::json::object>{},
|
||||
validation::IfType<std::string>{
|
||||
validation::Uint256HexStringValidator},
|
||||
validation::IfType<boost::json::object>{
|
||||
validation::Section{
|
||||
{"owner",
|
||||
validation::Required{},
|
||||
validation::AccountBase58Validator},
|
||||
{"seq",
|
||||
validation::Required{},
|
||||
validation::Type<uint32_t>{}},
|
||||
},
|
||||
}},
|
||||
{"offer",
|
||||
validation::Type<std::string, boost::json::object>{},
|
||||
validation::IfType<std::string>{
|
||||
validation::Uint256HexStringValidator},
|
||||
validation::IfType<boost::json::object>{
|
||||
validation::Section{
|
||||
{"account",
|
||||
validation::Required{},
|
||||
validation::AccountBase58Validator},
|
||||
{"seq",
|
||||
validation::Required{},
|
||||
validation::Type<uint32_t>{}},
|
||||
},
|
||||
}},
|
||||
{"payment_channel", validation::Uint256HexStringValidator},
|
||||
{"ripple_state",
|
||||
validation::Type<boost::json::object>{},
|
||||
validation::Section{
|
||||
{"accounts", validation::Required{}, rippleStateAccountsCheck},
|
||||
{"currency",
|
||||
validation::Required{},
|
||||
validation::CurrencyValidator},
|
||||
}},
|
||||
{"ticket",
|
||||
validation::Type<std::string, boost::json::object>{},
|
||||
validation::IfType<std::string>{
|
||||
validation::Uint256HexStringValidator},
|
||||
validation::IfType<boost::json::object>{
|
||||
validation::Section{
|
||||
{"account",
|
||||
validation::Required{},
|
||||
validation::AccountBase58Validator},
|
||||
{"ticket_seq",
|
||||
validation::Required{},
|
||||
validation::Type<uint32_t>{}},
|
||||
},
|
||||
}},
|
||||
};
|
||||
return rpcSpec;
|
||||
}
|
||||
|
||||
Result
|
||||
process(Input input, boost::asio::yield_context& yield) const;
|
||||
|
||||
private:
|
||||
// dir_root and owner can not be both empty or filled at the same time
|
||||
// This function will return an error if this is the case
|
||||
std::variant<ripple::uint256, RPC::Status>
|
||||
composeKeyFromDirectory(
|
||||
boost::json::object const& directory) const noexcept;
|
||||
};
|
||||
|
||||
void
|
||||
tag_invoke(
|
||||
boost::json::value_from_tag,
|
||||
boost::json::value& jv,
|
||||
LedgerEntryHandler::Output const& output);
|
||||
|
||||
LedgerEntryHandler::Input
|
||||
tag_invoke(
|
||||
boost::json::value_to_tag<LedgerEntryHandler::Input>,
|
||||
boost::json::value const& jv);
|
||||
} // namespace RPCng
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
static const RpcSpec rpcSpec = {
|
||||
{"transaction",
|
||||
validation::Required{},
|
||||
validation::TxHashValidator},
|
||||
validation::Uint256HexStringValidator},
|
||||
{"binary", validation::Type<bool>{}},
|
||||
{"min_ledger", validation::Type<uint32_t>{}},
|
||||
{"max_ledger", validation::Type<uint32_t>{}},
|
||||
|
||||
@@ -230,7 +230,7 @@ TEST_F(RPCBaseTest, IfTypeValidator)
|
||||
Section{{ "limit", Required{}, Type<uint32_t>{}, Between<uint32_t>{0, 100}}},
|
||||
Section{{ "limit2", Required{}, Type<uint32_t>{}, Between<uint32_t>{0, 100}}}
|
||||
},
|
||||
IfType<std::string>{LedgerHashValidator,}
|
||||
IfType<std::string>{Uint256HexStringValidator,}
|
||||
}};
|
||||
// clang-format on
|
||||
// if json object pass
|
||||
@@ -281,24 +281,6 @@ TEST_F(RPCBaseTest, CustomValidator)
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, LedgerHashValidator)
|
||||
{
|
||||
auto spec = RpcSpec{
|
||||
{"ledgerHash", LedgerHashValidator},
|
||||
};
|
||||
auto passingInput = json::parse(
|
||||
R"({ "ledgerHash": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC" })");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
|
||||
auto failingInput = json::parse(R"({ "ledgerHash": "wrongformat" })");
|
||||
ASSERT_FALSE(spec.validate(failingInput));
|
||||
|
||||
failingInput = json::parse(R"({ "ledgerHash": 256 })");
|
||||
auto err = spec.validate(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "ledgerHashNotString");
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, LedgerIndexValidator)
|
||||
{
|
||||
auto spec = RpcSpec{
|
||||
@@ -362,9 +344,9 @@ TEST_F(RPCBaseTest, MarkerValidator)
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, TxHashValidator)
|
||||
TEST_F(RPCBaseTest, Uint256HexStringValidator)
|
||||
{
|
||||
auto const spec = RpcSpec{{"transaction", TxHashValidator}};
|
||||
auto const spec = RpcSpec{{"transaction", Uint256HexStringValidator}};
|
||||
auto const passingInput = json::parse(
|
||||
R"({ "transaction": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC"})");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
@@ -378,5 +360,26 @@ TEST_F(RPCBaseTest, TxHashValidator)
|
||||
R"({ "transaction": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC"})");
|
||||
err = spec.validate(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "malformedTransaction");
|
||||
ASSERT_EQ(err.error().message, "transactionMalformed");
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, CurrencyValidator)
|
||||
{
|
||||
auto const spec = RpcSpec{{"currency", CurrencyValidator}};
|
||||
auto passingInput = json::parse(R"({ "currency": "GBP"})");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
|
||||
passingInput = json::parse(
|
||||
R"({ "currency": "0158415500000000C1F76FF6ECB0BAC600000000"})");
|
||||
ASSERT_TRUE(spec.validate(passingInput));
|
||||
|
||||
auto failingInput = json::parse(R"({ "currency": 256})");
|
||||
auto err = spec.validate(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "currencyNotString");
|
||||
|
||||
failingInput = json::parse(R"({ "currency": "12314"})");
|
||||
err = spec.validate(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "malformedCurrency");
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ TEST_F(RPCAccountHandlerTest, NonHexLedgerHash)
|
||||
|
||||
auto const err = RPC::makeError(output.error());
|
||||
EXPECT_EQ(err.at("error").as_string(), "invalidParams");
|
||||
EXPECT_EQ(err.at("error_message").as_string(), "ledgerHashMalformed");
|
||||
EXPECT_EQ(err.at("error_message").as_string(), "ledger_hashMalformed");
|
||||
});
|
||||
ctx.run();
|
||||
}
|
||||
@@ -81,7 +81,7 @@ TEST_F(RPCAccountHandlerTest, NonStringLedgerHash)
|
||||
|
||||
auto const err = RPC::makeError(output.error());
|
||||
EXPECT_EQ(err.at("error").as_string(), "invalidParams");
|
||||
EXPECT_EQ(err.at("error_message").as_string(), "ledgerHashNotString");
|
||||
EXPECT_EQ(err.at("error_message").as_string(), "ledger_hashNotString");
|
||||
});
|
||||
ctx.run();
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ generateParameterTestBundles()
|
||||
}})",
|
||||
ACCOUNT),
|
||||
"invalidParams",
|
||||
"ledgerHashMalformed"},
|
||||
"ledger_hashMalformed"},
|
||||
ParameterTestBundle{
|
||||
"LedgerHashNotString",
|
||||
fmt::format(
|
||||
@@ -137,7 +137,7 @@ generateParameterTestBundles()
|
||||
}})",
|
||||
ACCOUNT),
|
||||
"invalidParams",
|
||||
"ledgerHashNotString"},
|
||||
"ledger_hashNotString"},
|
||||
ParameterTestBundle{
|
||||
"WalletsNotStringOrArray",
|
||||
fmt::format(
|
||||
|
||||
1041
unittests/rpc/handlers/LedgerEntryTest.cpp
Normal file
1041
unittests/rpc/handlers/LedgerEntryTest.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -321,3 +321,94 @@ CreateRippleStateLedgerObject(
|
||||
line.setFieldU32(ripple::sfPreviousTxnLgrSeq, previousTxnSeq);
|
||||
return line;
|
||||
}
|
||||
|
||||
ripple::STObject
|
||||
CreateOfferLedgerObject(
|
||||
std::string_view account,
|
||||
int takerGets,
|
||||
int takerPays,
|
||||
std::string_view currency,
|
||||
std::string_view issueId)
|
||||
{
|
||||
ripple::STObject offer(ripple::sfLedgerEntry);
|
||||
offer.setFieldU16(ripple::sfLedgerEntryType, ripple::ltOFFER);
|
||||
offer.setAccountID(ripple::sfAccount, GetAccountIDWithString(account));
|
||||
offer.setFieldU32(ripple::sfSequence, 0);
|
||||
offer.setFieldU32(ripple::sfFlags, 0);
|
||||
ripple::Issue issue1 = GetIssue(currency, issueId);
|
||||
offer.setFieldAmount(
|
||||
ripple::sfTakerGets, ripple::STAmount(issue1, takerGets));
|
||||
offer.setFieldAmount(
|
||||
ripple::sfTakerPays, ripple::STAmount(takerPays, false));
|
||||
offer.setFieldH256(ripple::sfBookDirectory, ripple::uint256{});
|
||||
offer.setFieldU64(ripple::sfBookNode, 0);
|
||||
offer.setFieldU64(ripple::sfOwnerNode, 0);
|
||||
offer.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{});
|
||||
offer.setFieldU32(ripple::sfPreviousTxnLgrSeq, 0);
|
||||
return offer;
|
||||
}
|
||||
|
||||
ripple::STObject
|
||||
CreateTicketLedgerObject(std::string_view account, uint32_t sequence)
|
||||
{
|
||||
ripple::STObject ticket(ripple::sfLedgerEntry);
|
||||
ticket.setFieldU16(ripple::sfLedgerEntryType, ripple::ltTICKET);
|
||||
ticket.setAccountID(ripple::sfAccount, GetAccountIDWithString(account));
|
||||
ticket.setFieldU32(ripple::sfFlags, 0);
|
||||
ticket.setFieldU64(ripple::sfOwnerNode, 0);
|
||||
ticket.setFieldU32(ripple::sfTicketSequence, sequence);
|
||||
ticket.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{});
|
||||
ticket.setFieldU32(ripple::sfPreviousTxnLgrSeq, 0);
|
||||
return ticket;
|
||||
}
|
||||
|
||||
ripple::STObject
|
||||
CreateEscrowLedgerObject(std::string_view account, std::string_view dest)
|
||||
{
|
||||
ripple::STObject escrow(ripple::sfLedgerEntry);
|
||||
escrow.setFieldU16(ripple::sfLedgerEntryType, ripple::ltESCROW);
|
||||
escrow.setAccountID(ripple::sfAccount, GetAccountIDWithString(account));
|
||||
escrow.setAccountID(ripple::sfDestination, GetAccountIDWithString(dest));
|
||||
escrow.setFieldAmount(ripple::sfAmount, ripple::STAmount(0, false));
|
||||
escrow.setFieldU64(ripple::sfOwnerNode, 0);
|
||||
escrow.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{});
|
||||
escrow.setFieldU32(ripple::sfPreviousTxnLgrSeq, 0);
|
||||
escrow.setFieldU32(ripple::sfFlags, 0);
|
||||
return escrow;
|
||||
}
|
||||
|
||||
ripple::STObject
|
||||
CreateCheckLedgerObject(std::string_view account, std::string_view dest)
|
||||
{
|
||||
ripple::STObject check(ripple::sfLedgerEntry);
|
||||
check.setFieldU16(ripple::sfLedgerEntryType, ripple::ltCHECK);
|
||||
check.setAccountID(ripple::sfAccount, GetAccountIDWithString(account));
|
||||
check.setAccountID(ripple::sfDestination, GetAccountIDWithString(dest));
|
||||
check.setFieldU32(ripple::sfFlags, 0);
|
||||
check.setFieldU64(ripple::sfOwnerNode, 0);
|
||||
check.setFieldU64(ripple::sfDestinationNode, 0);
|
||||
check.setFieldAmount(ripple::sfSendMax, ripple::STAmount(0, false));
|
||||
check.setFieldU32(ripple::sfSequence, 0);
|
||||
check.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{});
|
||||
check.setFieldU32(ripple::sfPreviousTxnLgrSeq, 0);
|
||||
return check;
|
||||
}
|
||||
|
||||
ripple::STObject
|
||||
CreateDepositPreauthLedgerObject(
|
||||
std::string_view account,
|
||||
std::string_view auth)
|
||||
{
|
||||
ripple::STObject depositPreauth(ripple::sfLedgerEntry);
|
||||
depositPreauth.setFieldU16(
|
||||
ripple::sfLedgerEntryType, ripple::ltDEPOSIT_PREAUTH);
|
||||
depositPreauth.setAccountID(
|
||||
ripple::sfAccount, GetAccountIDWithString(account));
|
||||
depositPreauth.setAccountID(
|
||||
ripple::sfAuthorize, GetAccountIDWithString(auth));
|
||||
depositPreauth.setFieldU32(ripple::sfFlags, 0);
|
||||
depositPreauth.setFieldU64(ripple::sfOwnerNode, 0);
|
||||
depositPreauth.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{});
|
||||
depositPreauth.setFieldU32(ripple::sfPreviousTxnLgrSeq, 0);
|
||||
return depositPreauth;
|
||||
}
|
||||
|
||||
@@ -169,3 +169,25 @@ CreateRippleStateLedgerObject(
|
||||
int highLimit,
|
||||
std::string_view previousTxnId,
|
||||
uint32_t previousTxnSeq);
|
||||
|
||||
ripple::STObject
|
||||
CreateOfferLedgerObject(
|
||||
std::string_view account,
|
||||
int takerGets,
|
||||
int takerPays,
|
||||
std::string_view currency,
|
||||
std::string_view issueId);
|
||||
|
||||
ripple::STObject
|
||||
CreateTicketLedgerObject(std::string_view rootIndex, uint32_t sequence);
|
||||
|
||||
ripple::STObject
|
||||
CreateEscrowLedgerObject(std::string_view account, std::string_view dest);
|
||||
|
||||
ripple::STObject
|
||||
CreateCheckLedgerObject(std::string_view account, std::string_view dest);
|
||||
|
||||
ripple::STObject
|
||||
CreateDepositPreauthLedgerObject(
|
||||
std::string_view account,
|
||||
std::string_view auth);
|
||||
|
||||
Reference in New Issue
Block a user