Account tx v1 api support (#874)

* Don't fail on ledger params for v1

* Different error on invalid ledger indexes for v1

* Allow forward and binary to be not bool for v1

* Minor fixes

* Fix tests

* Don't fail if input ledger index is out of range for v1

* Restore deleted test

* Fix comparison of integers with different signedness

* Updated default api version in README and example config
This commit is contained in:
Sergey Kuznetsov
2023-09-28 11:31:35 +01:00
committed by GitHub
parent 963685dd31
commit 6ca777ea96
7 changed files with 375 additions and 144 deletions

View File

@@ -224,16 +224,16 @@ a database in each region, and the Clio nodes in each region use their region's
This is effectively two systems. This is effectively two systems.
Clio supports API versioning as [described here](https://xrpl.org/request-formatting.html#api-versioning). Clio supports API versioning as [described here](https://xrpl.org/request-formatting.html#api-versioning).
It's possible to configure `minimum`, `maximum` and `default` version like so: It's possible to configure `minimum`, `maximum` and `default` version like so:
```json ```json
"api_version": { "api_version": {
"min": 1, "min": 1,
"max": 2, "max": 2,
"default": 2 "default": 1
} }
``` ```
All of the above are optional. All of the above are optional.
Clio will fallback to hardcoded defaults when not specified in the config file or configured values are outside Clio will fallback to hardcoded defaults when not specified in the config file or configured values are outside
of the minimum and maximum supported versions hardcoded in `src/rpc/common/APIVersion.h`. of the minimum and maximum supported versions hardcoded in `src/rpc/common/APIVersion.h`.
> **Note:** See `example-config.json` for more details. > **Note:** See `example-config.json` for more details.

View File

@@ -111,8 +111,8 @@
// "ssl_cert_file" : "/full/path/to/cert.file", // "ssl_cert_file" : "/full/path/to/cert.file",
// "ssl_key_file" : "/full/path/to/key.file" // "ssl_key_file" : "/full/path/to/key.file"
"api_version": { "api_version": {
"min": 2, "min": 1, // Minimum API version supported (could be 1 or 2)
"max": 2, "max": 2, // Maximum API version supported (could be 1 or 2, but >= min)
"default": 2 // Clio only supports API v2 and newer "default": 1 // Clio behaves the same as rippled by default
} }
} }

46
src/rpc/common/JsonBool.h Normal file
View File

@@ -0,0 +1,46 @@
#pragma once
#include <boost/json/value_to.hpp>
namespace rpc {
/**
* @brief A wrapper around bool that allows to convert from any JSON value
*/
struct JsonBool
{
bool value = false;
operator bool() const
{
return value;
}
};
inline JsonBool
tag_invoke(boost::json::value_to_tag<JsonBool> const&, boost::json::value const& jsonValue)
{
switch (jsonValue.kind())
{
case boost::json::kind::null:
return JsonBool{false};
case boost::json::kind::bool_:
return JsonBool{jsonValue.as_bool()};
case boost::json::kind::uint64:
[[fallthrough]];
case boost::json::kind::int64:
return JsonBool{jsonValue.as_int64() != 0};
case boost::json::kind::double_:
return JsonBool{jsonValue.as_double() != 0.0};
case boost::json::kind::string:
// Also should be `jsonValue.as_string() != "false"` but rippled doesn't do that. Anyway for v2 api we have
// bool validation
return JsonBool{!jsonValue.as_string().empty() && jsonValue.as_string()[0] != 0};
case boost::json::kind::array:
return JsonBool{!jsonValue.as_array().empty()};
case boost::json::kind::object:
return JsonBool{!jsonValue.as_object().empty()};
}
throw std::runtime_error("Invalid json value");
}
} // namespace rpc

View File

@@ -76,6 +76,18 @@ struct RpcSpec final
{ {
} }
/**
* @brief Construct a full RPC request specification from another spec and additional fields.
*
* @param other The other spec to copy fields from
* @param additionalFields The additional fields to add to the spec
*/
RpcSpec(const RpcSpec& other, std::initializer_list<FieldSpec> additionalFields) : fields_{other.fields_}
{
for (auto& f : additionalFields)
fields_.push_back(std::move(f));
}
/** /**
* @brief Processos the passed JSON value using the stored field specs. * @brief Processos the passed JSON value using the stored field specs.
* *

View File

@@ -72,27 +72,39 @@ AccountTxHandler::process(AccountTxHandler::Input input, Context const& ctx) con
if (input.ledgerIndexMin) if (input.ledgerIndexMin)
{ {
if (range->maxSequence < input.ledgerIndexMin || range->minSequence > input.ledgerIndexMin) if (ctx.apiVersion > 1u &&
(input.ledgerIndexMin > range->maxSequence || input.ledgerIndexMin < range->minSequence))
{
return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMinOutOfRange"}}; return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMinOutOfRange"}};
}
minIndex = *input.ledgerIndexMin; if (static_cast<std::uint32_t>(*input.ledgerIndexMin) > minIndex)
minIndex = *input.ledgerIndexMin;
} }
if (input.ledgerIndexMax) if (input.ledgerIndexMax)
{ {
if (range->maxSequence < input.ledgerIndexMax || range->minSequence > input.ledgerIndexMax) if (ctx.apiVersion > 1u &&
(input.ledgerIndexMax > range->maxSequence || input.ledgerIndexMax < range->minSequence))
{
return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMaxOutOfRange"}}; return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMaxOutOfRange"}};
}
maxIndex = *input.ledgerIndexMax; if (static_cast<std::uint32_t>(*input.ledgerIndexMax) < maxIndex)
maxIndex = *input.ledgerIndexMax;
} }
if (minIndex > maxIndex) if (minIndex > maxIndex)
{
if (ctx.apiVersion == 1u)
return Error{Status{RippledError::rpcLGR_IDXS_INVALID}};
return Error{Status{RippledError::rpcINVALID_LGR_RANGE}}; return Error{Status{RippledError::rpcINVALID_LGR_RANGE}};
}
if (input.ledgerHash || input.ledgerIndex || input.usingValidatedLedger) if (input.ledgerHash || input.ledgerIndex || input.usingValidatedLedger)
{ {
// rippled does not have this check if (ctx.apiVersion > 1u && (input.ledgerIndexMax || input.ledgerIndexMin))
if (input.ledgerIndexMax || input.ledgerIndexMin)
return Error{Status{RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}}; return Error{Status{RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}};
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
@@ -247,10 +259,10 @@ tag_invoke(boost::json::value_to_tag<AccountTxHandler::Input>, boost::json::valu
} }
if (jsonObject.contains(JS(binary))) if (jsonObject.contains(JS(binary)))
input.binary = jsonObject.at(JS(binary)).as_bool(); input.binary = boost::json::value_to<JsonBool>(jsonObject.at(JS(binary)));
if (jsonObject.contains(JS(forward))) if (jsonObject.contains(JS(forward)))
input.forward = jsonObject.at(JS(forward)).as_bool(); input.forward = boost::json::value_to<JsonBool>(jsonObject.at(JS(forward)));
if (jsonObject.contains(JS(limit))) if (jsonObject.contains(JS(limit)))
input.limit = jsonObject.at(JS(limit)).as_int64(); input.limit = jsonObject.at(JS(limit)).as_int64();

View File

@@ -21,6 +21,7 @@
#include <data/BackendInterface.h> #include <data/BackendInterface.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <rpc/common/JsonBool.h>
#include <rpc/common/MetaProcessors.h> #include <rpc/common/MetaProcessors.h>
#include <rpc/common/Modifiers.h> #include <rpc/common/Modifiers.h>
#include <rpc/common/Types.h> #include <rpc/common/Types.h>
@@ -76,8 +77,8 @@ public:
std::optional<int32_t> ledgerIndexMin; std::optional<int32_t> ledgerIndexMin;
std::optional<int32_t> ledgerIndexMax; std::optional<int32_t> ledgerIndexMax;
bool usingValidatedLedger = false; bool usingValidatedLedger = false;
bool binary = false; JsonBool binary{false};
bool forward = false; JsonBool forward{false};
std::optional<uint32_t> limit; std::optional<uint32_t> limit;
std::optional<Marker> marker; std::optional<Marker> marker;
std::optional<ripple::TxType> transactionType; std::optional<ripple::TxType> transactionType;
@@ -92,14 +93,12 @@ public:
RpcSpecConstRef RpcSpecConstRef
spec([[maybe_unused]] uint32_t apiVersion) const spec([[maybe_unused]] uint32_t apiVersion) const
{ {
static auto const rpcSpec = RpcSpec{ static auto const rpcSpecForV1 = RpcSpec{
{JS(account), validation::Required{}, validation::AccountValidator}, {JS(account), validation::Required{}, validation::AccountValidator},
{JS(ledger_hash), validation::Uint256HexStringValidator}, {JS(ledger_hash), validation::Uint256HexStringValidator},
{JS(ledger_index), validation::LedgerIndexValidator}, {JS(ledger_index), validation::LedgerIndexValidator},
{JS(ledger_index_min), validation::Type<int32_t>{}}, {JS(ledger_index_min), validation::Type<int32_t>{}},
{JS(ledger_index_max), validation::Type<int32_t>{}}, {JS(ledger_index_max), validation::Type<int32_t>{}},
{JS(binary), validation::Type<bool>{}},
{JS(forward), validation::Type<bool>{}},
{JS(limit), {JS(limit),
validation::Type<uint32_t>{}, validation::Type<uint32_t>{},
validation::Min(1u), validation::Min(1u),
@@ -121,7 +120,14 @@ public:
}, },
}; };
return rpcSpec; static auto const rpcSpec = RpcSpec{
rpcSpecForV1,
{
{JS(binary), validation::Type<bool>{}},
{JS(forward), validation::Type<bool>{}},
}};
return apiVersion == 1 ? rpcSpecForV1 : rpcSpec;
} }
Result Result

View File

@@ -45,8 +45,9 @@ struct AccountTxParamTestCaseBundle
{ {
std::string testName; std::string testName;
std::string testJson; std::string testJson;
std::string expectedError; std::optional<std::string> expectedError;
std::string expectedErrorMessage; std::optional<std::string> expectedErrorMessage;
std::uint32_t apiVersion = 2;
}; };
// parameterized test cases for parameters check // parameterized test cases for parameters check
@@ -58,91 +59,102 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest, public WithParam
std::string std::string
operator()(const testing::TestParamInfo<ParamType>& info) const operator()(const testing::TestParamInfo<ParamType>& info) const
{ {
auto bundle = static_cast<AccountTxParamTestCaseBundle>(info.param); return info.param.testName;
return bundle.testName;
} }
}; };
};
static auto static auto
generateTestValuesForParametersTest() generateTestValuesForParametersTest()
{ {
return std::vector<AccountTxParamTestCaseBundle>{ return std::vector<AccountTxParamTestCaseBundle>{
AccountTxParamTestCaseBundle{"MissingAccount", R"({})", "invalidParams", "Required field 'account' missing"}, AccountTxParamTestCaseBundle{
AccountTxParamTestCaseBundle{ "MissingAccount", R"({})", "invalidParams", "Required field 'account' missing"},
"BinaryNotBool", AccountTxParamTestCaseBundle{
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "binary": 1})", "BinaryNotBool",
"invalidParams", R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "binary": 1})",
"Invalid parameters."}, "invalidParams",
AccountTxParamTestCaseBundle{ "Invalid parameters."},
"ForwardNotBool", AccountTxParamTestCaseBundle{
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "forward": 1})", "BinaryNotBool_API_v1",
"invalidParams", R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "binary": 1})",
"Invalid parameters."}, std::nullopt,
AccountTxParamTestCaseBundle{ std::nullopt,
"ledger_index_minNotInt", 1u},
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_min": "x"})", AccountTxParamTestCaseBundle{
"invalidParams", "ForwardNotBool",
"Invalid parameters."}, R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "forward": 1})",
AccountTxParamTestCaseBundle{ "invalidParams",
"ledger_index_maxNotInt", "Invalid parameters."},
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_max": "x"})", AccountTxParamTestCaseBundle{
"invalidParams", "ForwardNotBool_API_v1",
"Invalid parameters."}, R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "forward": 1})",
AccountTxParamTestCaseBundle{ std::nullopt,
"ledger_indexInvalid", std::nullopt,
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index": "x"})", 1u},
"invalidParams", AccountTxParamTestCaseBundle{
"ledgerIndexMalformed"}, "ledger_index_minNotInt",
AccountTxParamTestCaseBundle{ R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_min": "x"})",
"ledger_hashInvalid", "invalidParams",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": "x"})", "Invalid parameters."},
"invalidParams", AccountTxParamTestCaseBundle{
"ledger_hashMalformed"}, "ledger_index_maxNotInt",
AccountTxParamTestCaseBundle{ R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_max": "x"})",
"ledger_hashNotString", "invalidParams",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": 123})", "Invalid parameters."},
"invalidParams", AccountTxParamTestCaseBundle{
"ledger_hashNotString"}, "ledger_indexInvalid",
AccountTxParamTestCaseBundle{ R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index": "x"})",
"limitNotInt", "invalidParams",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": "123"})", "ledgerIndexMalformed"},
"invalidParams", AccountTxParamTestCaseBundle{
"Invalid parameters."}, "ledger_hashInvalid",
AccountTxParamTestCaseBundle{ R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": "x"})",
"limitNegative", "invalidParams",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": -1})", "ledger_hashMalformed"},
"invalidParams", AccountTxParamTestCaseBundle{
"Invalid parameters."}, "ledger_hashNotString",
AccountTxParamTestCaseBundle{ R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": 123})",
"limitZero", "invalidParams",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": 0})", "ledger_hashNotString"},
"invalidParams", AccountTxParamTestCaseBundle{
"Invalid parameters."}, "limitNotInt",
AccountTxParamTestCaseBundle{ R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": "123"})",
"MarkerNotObject", "invalidParams",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "marker": 101})", "Invalid parameters."},
"invalidParams", AccountTxParamTestCaseBundle{
"invalidMarker"}, "limitNegative",
AccountTxParamTestCaseBundle{ R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": -1})",
"MarkerMissingSeq", "invalidParams",
R"({ "Invalid parameters."},
AccountTxParamTestCaseBundle{
"limitZero",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": 0})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"MarkerNotObject",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "marker": 101})",
"invalidParams",
"invalidMarker"},
AccountTxParamTestCaseBundle{
"MarkerMissingSeq",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"marker": {"ledger": 123} "marker": {"ledger": 123}
})", })",
"invalidParams", "invalidParams",
"Required field 'seq' missing"}, "Required field 'seq' missing"},
AccountTxParamTestCaseBundle{ AccountTxParamTestCaseBundle{
"MarkerMissingLedger", "MarkerMissingLedger",
R"({ R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"marker":{"seq": 123} "marker":{"seq": 123}
})", })",
"invalidParams", "invalidParams",
"Required field 'ledger' missing"}, "Required field 'ledger' missing"},
AccountTxParamTestCaseBundle{ AccountTxParamTestCaseBundle{
"MarkerLedgerNotInt", "MarkerLedgerNotInt",
R"({ R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"marker": "marker":
{ {
@@ -150,11 +162,11 @@ generateTestValuesForParametersTest()
"ledger": 1 "ledger": 1
} }
})", })",
"invalidParams", "invalidParams",
"Invalid parameters."}, "Invalid parameters."},
AccountTxParamTestCaseBundle{ AccountTxParamTestCaseBundle{
"MarkerSeqNotInt", "MarkerSeqNotInt",
R"({ R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"marker": "marker":
{ {
@@ -162,77 +174,220 @@ generateTestValuesForParametersTest()
"seq": 1 "seq": 1
} }
})", })",
"invalidParams", "invalidParams",
"Invalid parameters."}, "Invalid parameters."},
AccountTxParamTestCaseBundle{ AccountTxParamTestCaseBundle{
"LedgerIndexMinLessThanMinSeq", "LedgerIndexMinLessThanMinSeq",
R"({ R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_min": 9 "ledger_index_min": 9
})", })",
"lgrIdxMalformed", "lgrIdxMalformed",
"ledgerSeqMinOutOfRange"}, "ledgerSeqMinOutOfRange"},
AccountTxParamTestCaseBundle{ AccountTxParamTestCaseBundle{
"LedgerIndexMaxLargeThanMaxSeq", "LedgerIndexMaxLargeThanMaxSeq",
R"({ R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 31 "ledger_index_max": 31
})", })",
"lgrIdxMalformed", "lgrIdxMalformed",
"ledgerSeqMaxOutOfRange"}, "ledgerSeqMaxOutOfRange"},
AccountTxParamTestCaseBundle{ AccountTxParamTestCaseBundle{
"LedgerIndexMaxLessThanLedgerIndexMin", "LedgerIndexMaxLargeThanMaxSeq_API_v1",
R"({ R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 31
})",
std::nullopt,
std::nullopt,
1u},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxSmallerThanMinSeq",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 9
})",
"lgrIdxMalformed",
"ledgerSeqMaxOutOfRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxSmallerThanMinSeq_API_v1",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 9
})",
"lgrIdxsInvalid",
"Ledger indexes invalid.",
1u},
AccountTxParamTestCaseBundle{
"LedgerIndexMinSmallerThanMinSeq",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_min": 9
})",
"lgrIdxMalformed",
"ledgerSeqMinOutOfRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMinSmallerThanMinSeq_API_v1",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_min": 9
})",
std::nullopt,
std::nullopt,
1u},
AccountTxParamTestCaseBundle{
"LedgerIndexMinLargerThanMaxSeq",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_min": 31
})",
"lgrIdxMalformed",
"ledgerSeqMinOutOfRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMinLargerThanMaxSeq_API_v1",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_min": 31
})",
"lgrIdxsInvalid",
"Ledger indexes invalid.",
1u},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxLessThanLedgerIndexMin",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 11, "ledger_index_max": 11,
"ledger_index_min": 20 "ledger_index_min": 20
})", })",
"invalidLgrRange", "invalidLgrRange",
"Ledger range is invalid."}, "Ledger range is invalid."},
AccountTxParamTestCaseBundle{ AccountTxParamTestCaseBundle{
"LedgerIndexMaxMinAndLedgerIndex", "LedgerIndexMaxLessThanLedgerIndexMin_API_v1",
R"({ R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 11,
"ledger_index_min": 20
})",
"lgrIdxsInvalid",
"Ledger indexes invalid.",
1u},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxMinAndLedgerIndex",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 20, "ledger_index_max": 20,
"ledger_index_min": 11, "ledger_index_min": 11,
"ledger_index": 10 "ledger_index": 10
})", })",
"invalidParams", "invalidParams",
"containsLedgerSpecifierAndRange"}, "containsLedgerSpecifierAndRange"},
AccountTxParamTestCaseBundle{ AccountTxParamTestCaseBundle{
"LedgerIndexMaxMinAndLedgerIndexValidated", "LedgerIndexMaxMinAndLedgerIndexValidated",
R"({ R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 20, "ledger_index_max": 20,
"ledger_index_min": 11, "ledger_index_min": 11,
"ledger_index": "validated" "ledger_index": "validated"
})", })",
"invalidParams", "invalidParams",
"containsLedgerSpecifierAndRange"}, "containsLedgerSpecifierAndRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxMinAndLedgerIndex_API_v1",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 20,
"ledger_index_min": 11,
"ledger_index": 10
})",
std::nullopt,
std::nullopt,
1u},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxMinAndLedgerHash",
fmt::format(
R"({{
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 20,
"ledger_index_min": 11,
"ledger_hash": "{}"
}})",
LEDGERHASH),
"invalidParams",
"containsLedgerSpecifierAndRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxMinAndLedgerHash_API_v1",
fmt::format(
R"({{
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 20,
"ledger_index_min": 11,
"ledger_hash": "{}"
}})",
LEDGERHASH),
std::nullopt,
std::nullopt,
1u},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxMinAndLedgerIndexValidated_API_v1",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 20,
"ledger_index_min": 11,
"ledger_index": "validated"
})",
std::nullopt,
std::nullopt,
1u},
};
}; };
} };
INSTANTIATE_TEST_CASE_P( INSTANTIATE_TEST_CASE_P(
RPCAccountTxGroup1, RPCAccountTxGroup1,
AccountTxParameterTest, AccountTxParameterTest,
ValuesIn(generateTestValuesForParametersTest()), ValuesIn(AccountTxParameterTest::generateTestValuesForParametersTest()),
AccountTxParameterTest::NameGenerator{}); AccountTxParameterTest::NameGenerator{});
TEST_P(AccountTxParameterTest, InvalidParams) TEST_P(AccountTxParameterTest, CheckParams)
{ {
mockBackendPtr->updateRange(MINSEQ); // min mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max mockBackendPtr->updateRange(MAXSEQ); // max
auto const testBundle = GetParam(); auto const testBundle = GetParam();
runSpawn([&, this](auto yield) { auto* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
auto const handler = AnyHandler{AccountTxHandler{mockBackendPtr}}; std::cout << "Before parse" << std::endl;
auto const req = json::parse(testBundle.testJson); auto const req = json::parse(testBundle.testJson);
auto const output = handler.process(req, Context{yield}); std::cout << "After parse" << std::endl;
ASSERT_FALSE(output); if (testBundle.expectedError.has_value())
{
ASSERT_TRUE(testBundle.expectedErrorMessage.has_value());
auto const err = rpc::makeError(output.error()); runSpawn([&, this](auto yield) {
EXPECT_EQ(err.at("error").as_string(), testBundle.expectedError); auto const handler = AnyHandler{AccountTxHandler{mockBackendPtr}};
EXPECT_EQ(err.at("error_message").as_string(), testBundle.expectedErrorMessage); auto const output = handler.process(req, Context{.yield = yield, .apiVersion = testBundle.apiVersion});
}); ASSERT_FALSE(output);
auto const err = rpc::makeError(output.error());
EXPECT_EQ(err.at("error").as_string(), *testBundle.expectedError);
EXPECT_EQ(err.at("error_message").as_string(), *testBundle.expectedErrorMessage);
});
}
else
{
if (req.as_object().contains("ledger_hash"))
{
EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).WillOnce(testing::Return(ripple::LedgerHeader{}));
}
else if (req.as_object().contains("ledger_index"))
{
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).WillOnce(testing::Return(ripple::LedgerHeader{}));
}
EXPECT_CALL(*rawBackendPtr, fetchAccountTransactions);
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{AccountTxHandler{mockBackendPtr}};
auto const output = handler.process(req, Context{.yield = yield, .apiVersion = testBundle.apiVersion});
EXPECT_TRUE(output);
});
}
} }
namespace { namespace {