mirror of
				https://github.com/XRPLF/clio.git
				synced 2025-11-04 11:55:51 +00:00 
			
		
		
		
	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:
		@@ -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.
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
"api_version": {
 | 
			
		||||
    "min": 1,
 | 
			
		||||
    "max": 2,
 | 
			
		||||
    "default": 2 
 | 
			
		||||
    "default": 1
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
All of the above are optional. 
 | 
			
		||||
Clio will fallback to hardcoded defaults when not specified in the config file or configured values are outside 
 | 
			
		||||
All of the above are optional.
 | 
			
		||||
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`.
 | 
			
		||||
> **Note:** See `example-config.json` for more details. 
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -111,8 +111,8 @@
 | 
			
		||||
    // "ssl_cert_file" : "/full/path/to/cert.file",
 | 
			
		||||
    // "ssl_key_file" : "/full/path/to/key.file"
 | 
			
		||||
    "api_version": {
 | 
			
		||||
        "min": 2,
 | 
			
		||||
        "max": 2,
 | 
			
		||||
        "default": 2 // Clio only supports API v2 and newer
 | 
			
		||||
        "min": 1, // Minimum API version supported (could be 1 or 2)
 | 
			
		||||
        "max": 2, // Maximum API version supported (could be 1 or 2, but >= min)
 | 
			
		||||
        "default": 1 // Clio behaves the same as rippled by default
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										46
									
								
								src/rpc/common/JsonBool.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/rpc/common/JsonBool.h
									
									
									
									
									
										Normal 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
 | 
			
		||||
@@ -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.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -72,27 +72,39 @@ AccountTxHandler::process(AccountTxHandler::Input input, Context const& ctx) con
 | 
			
		||||
 | 
			
		||||
    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"}};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        minIndex = *input.ledgerIndexMin;
 | 
			
		||||
        if (static_cast<std::uint32_t>(*input.ledgerIndexMin) > minIndex)
 | 
			
		||||
            minIndex = *input.ledgerIndexMin;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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"}};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        maxIndex = *input.ledgerIndexMax;
 | 
			
		||||
        if (static_cast<std::uint32_t>(*input.ledgerIndexMax) < maxIndex)
 | 
			
		||||
            maxIndex = *input.ledgerIndexMax;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (minIndex > maxIndex)
 | 
			
		||||
    {
 | 
			
		||||
        if (ctx.apiVersion == 1u)
 | 
			
		||||
            return Error{Status{RippledError::rpcLGR_IDXS_INVALID}};
 | 
			
		||||
 | 
			
		||||
        return Error{Status{RippledError::rpcINVALID_LGR_RANGE}};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (input.ledgerHash || input.ledgerIndex || input.usingValidatedLedger)
 | 
			
		||||
    {
 | 
			
		||||
        // rippled does not have this check
 | 
			
		||||
        if (input.ledgerIndexMax || input.ledgerIndexMin)
 | 
			
		||||
        if (ctx.apiVersion > 1u && (input.ledgerIndexMax || input.ledgerIndexMin))
 | 
			
		||||
            return Error{Status{RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}};
 | 
			
		||||
 | 
			
		||||
        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)))
 | 
			
		||||
        input.binary = jsonObject.at(JS(binary)).as_bool();
 | 
			
		||||
        input.binary = boost::json::value_to<JsonBool>(jsonObject.at(JS(binary)));
 | 
			
		||||
 | 
			
		||||
    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)))
 | 
			
		||||
        input.limit = jsonObject.at(JS(limit)).as_int64();
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@
 | 
			
		||||
 | 
			
		||||
#include <data/BackendInterface.h>
 | 
			
		||||
#include <rpc/RPCHelpers.h>
 | 
			
		||||
#include <rpc/common/JsonBool.h>
 | 
			
		||||
#include <rpc/common/MetaProcessors.h>
 | 
			
		||||
#include <rpc/common/Modifiers.h>
 | 
			
		||||
#include <rpc/common/Types.h>
 | 
			
		||||
@@ -76,8 +77,8 @@ public:
 | 
			
		||||
        std::optional<int32_t> ledgerIndexMin;
 | 
			
		||||
        std::optional<int32_t> ledgerIndexMax;
 | 
			
		||||
        bool usingValidatedLedger = false;
 | 
			
		||||
        bool binary = false;
 | 
			
		||||
        bool forward = false;
 | 
			
		||||
        JsonBool binary{false};
 | 
			
		||||
        JsonBool forward{false};
 | 
			
		||||
        std::optional<uint32_t> limit;
 | 
			
		||||
        std::optional<Marker> marker;
 | 
			
		||||
        std::optional<ripple::TxType> transactionType;
 | 
			
		||||
@@ -92,14 +93,12 @@ public:
 | 
			
		||||
    RpcSpecConstRef
 | 
			
		||||
    spec([[maybe_unused]] uint32_t apiVersion) const
 | 
			
		||||
    {
 | 
			
		||||
        static auto const rpcSpec = RpcSpec{
 | 
			
		||||
        static auto const rpcSpecForV1 = RpcSpec{
 | 
			
		||||
            {JS(account), validation::Required{}, validation::AccountValidator},
 | 
			
		||||
            {JS(ledger_hash), validation::Uint256HexStringValidator},
 | 
			
		||||
            {JS(ledger_index), validation::LedgerIndexValidator},
 | 
			
		||||
            {JS(ledger_index_min), validation::Type<int32_t>{}},
 | 
			
		||||
            {JS(ledger_index_max), validation::Type<int32_t>{}},
 | 
			
		||||
            {JS(binary), validation::Type<bool>{}},
 | 
			
		||||
            {JS(forward), validation::Type<bool>{}},
 | 
			
		||||
            {JS(limit),
 | 
			
		||||
             validation::Type<uint32_t>{},
 | 
			
		||||
             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
 | 
			
		||||
 
 | 
			
		||||
@@ -45,8 +45,9 @@ struct AccountTxParamTestCaseBundle
 | 
			
		||||
{
 | 
			
		||||
    std::string testName;
 | 
			
		||||
    std::string testJson;
 | 
			
		||||
    std::string expectedError;
 | 
			
		||||
    std::string expectedErrorMessage;
 | 
			
		||||
    std::optional<std::string> expectedError;
 | 
			
		||||
    std::optional<std::string> expectedErrorMessage;
 | 
			
		||||
    std::uint32_t apiVersion = 2;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// parameterized test cases for parameters check
 | 
			
		||||
@@ -58,91 +59,102 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest, public WithParam
 | 
			
		||||
        std::string
 | 
			
		||||
        operator()(const testing::TestParamInfo<ParamType>& info) const
 | 
			
		||||
        {
 | 
			
		||||
            auto bundle = static_cast<AccountTxParamTestCaseBundle>(info.param);
 | 
			
		||||
            return bundle.testName;
 | 
			
		||||
            return info.param.testName;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static auto
 | 
			
		||||
generateTestValuesForParametersTest()
 | 
			
		||||
{
 | 
			
		||||
    return std::vector<AccountTxParamTestCaseBundle>{
 | 
			
		||||
        AccountTxParamTestCaseBundle{"MissingAccount", R"({})", "invalidParams", "Required field 'account' missing"},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "BinaryNotBool",
 | 
			
		||||
            R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "binary": 1})",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "Invalid parameters."},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "ForwardNotBool",
 | 
			
		||||
            R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "forward": 1})",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "Invalid parameters."},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "ledger_index_minNotInt",
 | 
			
		||||
            R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_min": "x"})",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "Invalid parameters."},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "ledger_index_maxNotInt",
 | 
			
		||||
            R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_max": "x"})",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "Invalid parameters."},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "ledger_indexInvalid",
 | 
			
		||||
            R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index": "x"})",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "ledgerIndexMalformed"},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "ledger_hashInvalid",
 | 
			
		||||
            R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": "x"})",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "ledger_hashMalformed"},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "ledger_hashNotString",
 | 
			
		||||
            R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": 123})",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "ledger_hashNotString"},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "limitNotInt",
 | 
			
		||||
            R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": "123"})",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "Invalid parameters."},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "limitNegative",
 | 
			
		||||
            R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": -1})",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "Invalid parameters."},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "limitZero",
 | 
			
		||||
            R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": 0})",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "Invalid parameters."},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "MarkerNotObject",
 | 
			
		||||
            R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "marker": 101})",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "invalidMarker"},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "MarkerMissingSeq",
 | 
			
		||||
            R"({
 | 
			
		||||
    static auto
 | 
			
		||||
    generateTestValuesForParametersTest()
 | 
			
		||||
    {
 | 
			
		||||
        return std::vector<AccountTxParamTestCaseBundle>{
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "MissingAccount", R"({})", "invalidParams", "Required field 'account' missing"},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "BinaryNotBool",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "binary": 1})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Invalid parameters."},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "BinaryNotBool_API_v1",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "binary": 1})",
 | 
			
		||||
                std::nullopt,
 | 
			
		||||
                std::nullopt,
 | 
			
		||||
                1u},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "ForwardNotBool",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "forward": 1})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Invalid parameters."},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "ForwardNotBool_API_v1",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "forward": 1})",
 | 
			
		||||
                std::nullopt,
 | 
			
		||||
                std::nullopt,
 | 
			
		||||
                1u},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "ledger_index_minNotInt",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_min": "x"})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Invalid parameters."},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "ledger_index_maxNotInt",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_max": "x"})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Invalid parameters."},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "ledger_indexInvalid",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index": "x"})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "ledgerIndexMalformed"},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "ledger_hashInvalid",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": "x"})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "ledger_hashMalformed"},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "ledger_hashNotString",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": 123})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "ledger_hashNotString"},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "limitNotInt",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": "123"})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Invalid parameters."},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "limitNegative",
 | 
			
		||||
                R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": -1})",
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "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",
 | 
			
		||||
                "marker": {"ledger": 123}
 | 
			
		||||
            })",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "Required field 'seq' missing"},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "MarkerMissingLedger",
 | 
			
		||||
            R"({
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Required field 'seq' missing"},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "MarkerMissingLedger",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "marker":{"seq": 123}
 | 
			
		||||
            })",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "Required field 'ledger' missing"},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "MarkerLedgerNotInt",
 | 
			
		||||
            R"({
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Required field 'ledger' missing"},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "MarkerLedgerNotInt",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "marker": 
 | 
			
		||||
                {
 | 
			
		||||
@@ -150,11 +162,11 @@ generateTestValuesForParametersTest()
 | 
			
		||||
                    "ledger": 1
 | 
			
		||||
                }
 | 
			
		||||
            })",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "Invalid parameters."},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "MarkerSeqNotInt",
 | 
			
		||||
            R"({
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Invalid parameters."},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "MarkerSeqNotInt",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "marker": 
 | 
			
		||||
                {
 | 
			
		||||
@@ -162,77 +174,220 @@ generateTestValuesForParametersTest()
 | 
			
		||||
                    "seq": 1
 | 
			
		||||
                }
 | 
			
		||||
            })",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "Invalid parameters."},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "LedgerIndexMinLessThanMinSeq",
 | 
			
		||||
            R"({
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "Invalid parameters."},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMinLessThanMinSeq",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_min": 9
 | 
			
		||||
            })",
 | 
			
		||||
            "lgrIdxMalformed",
 | 
			
		||||
            "ledgerSeqMinOutOfRange"},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "LedgerIndexMaxLargeThanMaxSeq",
 | 
			
		||||
            R"({
 | 
			
		||||
                "lgrIdxMalformed",
 | 
			
		||||
                "ledgerSeqMinOutOfRange"},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMaxLargeThanMaxSeq",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_max": 31
 | 
			
		||||
            })",
 | 
			
		||||
            "lgrIdxMalformed",
 | 
			
		||||
            "ledgerSeqMaxOutOfRange"},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "LedgerIndexMaxLessThanLedgerIndexMin",
 | 
			
		||||
            R"({
 | 
			
		||||
                "lgrIdxMalformed",
 | 
			
		||||
                "ledgerSeqMaxOutOfRange"},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMaxLargeThanMaxSeq_API_v1",
 | 
			
		||||
                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",
 | 
			
		||||
                "ledger_index_max": 11,
 | 
			
		||||
                "ledger_index_min": 20
 | 
			
		||||
            })",
 | 
			
		||||
            "invalidLgrRange",
 | 
			
		||||
            "Ledger range is invalid."},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "LedgerIndexMaxMinAndLedgerIndex",
 | 
			
		||||
            R"({
 | 
			
		||||
                "invalidLgrRange",
 | 
			
		||||
                "Ledger range is invalid."},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMaxLessThanLedgerIndexMin_API_v1",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_max": 11,
 | 
			
		||||
                "ledger_index_min": 20
 | 
			
		||||
            })",
 | 
			
		||||
                "lgrIdxsInvalid",
 | 
			
		||||
                "Ledger indexes invalid.",
 | 
			
		||||
                1u},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMaxMinAndLedgerIndex",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", 
 | 
			
		||||
                "ledger_index_max": 20,
 | 
			
		||||
                "ledger_index_min": 11,
 | 
			
		||||
                "ledger_index": 10
 | 
			
		||||
            })",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "containsLedgerSpecifierAndRange"},
 | 
			
		||||
        AccountTxParamTestCaseBundle{
 | 
			
		||||
            "LedgerIndexMaxMinAndLedgerIndexValidated",
 | 
			
		||||
            R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", 
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "containsLedgerSpecifierAndRange"},
 | 
			
		||||
            AccountTxParamTestCaseBundle{
 | 
			
		||||
                "LedgerIndexMaxMinAndLedgerIndexValidated",
 | 
			
		||||
                R"({
 | 
			
		||||
                "account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
 | 
			
		||||
                "ledger_index_max": 20,
 | 
			
		||||
                "ledger_index_min": 11,
 | 
			
		||||
                "ledger_index": "validated"
 | 
			
		||||
            })",
 | 
			
		||||
            "invalidParams",
 | 
			
		||||
            "containsLedgerSpecifierAndRange"},
 | 
			
		||||
                "invalidParams",
 | 
			
		||||
                "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(
 | 
			
		||||
    RPCAccountTxGroup1,
 | 
			
		||||
    AccountTxParameterTest,
 | 
			
		||||
    ValuesIn(generateTestValuesForParametersTest()),
 | 
			
		||||
    ValuesIn(AccountTxParameterTest::generateTestValuesForParametersTest()),
 | 
			
		||||
    AccountTxParameterTest::NameGenerator{});
 | 
			
		||||
 | 
			
		||||
TEST_P(AccountTxParameterTest, InvalidParams)
 | 
			
		||||
TEST_P(AccountTxParameterTest, CheckParams)
 | 
			
		||||
{
 | 
			
		||||
    mockBackendPtr->updateRange(MINSEQ);  // min
 | 
			
		||||
    mockBackendPtr->updateRange(MAXSEQ);  // max
 | 
			
		||||
    auto const testBundle = GetParam();
 | 
			
		||||
    runSpawn([&, this](auto yield) {
 | 
			
		||||
        auto const handler = AnyHandler{AccountTxHandler{mockBackendPtr}};
 | 
			
		||||
        auto const req = json::parse(testBundle.testJson);
 | 
			
		||||
        auto const output = handler.process(req, Context{yield});
 | 
			
		||||
        ASSERT_FALSE(output);
 | 
			
		||||
    auto* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
 | 
			
		||||
    std::cout << "Before parse" << std::endl;
 | 
			
		||||
    auto const req = json::parse(testBundle.testJson);
 | 
			
		||||
    std::cout << "After parse" << std::endl;
 | 
			
		||||
    if (testBundle.expectedError.has_value())
 | 
			
		||||
    {
 | 
			
		||||
        ASSERT_TRUE(testBundle.expectedErrorMessage.has_value());
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
    });
 | 
			
		||||
        runSpawn([&, this](auto yield) {
 | 
			
		||||
            auto const handler = AnyHandler{AccountTxHandler{mockBackendPtr}};
 | 
			
		||||
            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 {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user