Ledger entry in new RPC framework (#534)

Fixes #539
This commit is contained in:
cyan317
2023-03-15 13:01:40 +00:00
committed by GitHub
parent b99a68e55f
commit bc438ce58a
15 changed files with 1724 additions and 49 deletions

View File

@@ -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

View File

@@ -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{};
}};

View File

@@ -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

View File

@@ -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}

View File

@@ -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;
}

View File

@@ -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;

View 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

View 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

View File

@@ -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>{}},

View File

@@ -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");
}

View File

@@ -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();
}

View File

@@ -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(

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}

View File

@@ -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);