Compare commits

...

24 Commits

Author SHA1 Message Date
Sergey Kuznetsov
7b18e28c47 fix: Fix extra brackets in warnings (#1519)
Fixes #1518
2024-07-05 12:05:14 +01:00
cyan317
4940d463dc Fix empty currency (#1481) 2024-06-21 13:01:14 +01:00
Peter Chen
c795cf371a Fix base_asset value in getAggregatePrice (#1467)
Fixes #1372
2024-06-18 09:04:33 -04:00
Peter Chen
e135aa49d5 Create generate free port class to avoid conflicting ports (#1439)
Fixes #1317
2024-06-18 11:29:05 +01:00
cyan317
5ba08b1d26 Improve etl check (#1465)
Fix #1444
2024-06-17 11:52:39 +01:00
github-actions[bot]
37cd79ceb0 [CI] clang-tidy auto fixes (#1464)
Fixes #1463. Please review and commit clang-tidy fixes.

Co-authored-by: kuznetsss <kuznetsss@users.noreply.github.com>
2024-06-17 09:18:35 +01:00
Sergey Kuznetsov
1334bd05d9 Add forwarding timeout option (#1462)
Fixes #1454.
2024-06-14 16:53:08 +01:00
Peter Chen
437ea7bf98 Fix quoteAsset value in getAggregatePrice (#1449)
Fixes #1373
2024-06-12 11:16:11 -04:00
github-actions[bot]
f9f3bc928e [CI] clang-tidy auto fixes (#1459)
Fixes #1458. Please review and commit clang-tidy fixes.

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Sergey Kuznetsov <skuznetsov@ripple.com>
2024-06-12 11:25:16 +01:00
cyan317
aa1f3efda2 Add trouble shooting md (#1455)
Fix #1284
2024-06-12 10:34:49 +01:00
github-actions[bot]
a6d21c1a02 [CI] clang-tidy auto fixes (#1457)
Fixes #1456. Please review and commit clang-tidy fixes.

Co-authored-by: kuznetsss <kuznetsss@users.noreply.github.com>
2024-06-12 10:34:13 +01:00
cyan317
49b80c7ad8 Support string type integer for oracle_document_id (#1448)
Fix #1420
2024-06-12 10:31:32 +01:00
Sergey Kuznetsov
56ab943be5 Add option to set X-User header value for forwarded requests (#1425)
Fixes #1422.
2024-06-11 17:59:10 +01:00
Sergey Kuznetsov
9d3b4f0313 Add assertion to process method (#1453)
Also adjust clangd so it doesn't bother us with header files about `expected`.
2024-06-11 13:10:08 +01:00
Alex Kremer
42c970a2a3 Forward feature RPC (#1440)
Fixes #1436 

This is a temporary implementation of the `feature` RPC that will always
return `noPermission` iff `vetoed` is set.
If `vetoed` isn't specified, Clio will always forward the request to
`rippled` instead.

In the future, #1131 will implement a Clio-native `feature` RPC. This
requires specific support from `libxrpl` side and that is not going to
be available till at least 2.2.1, hence the temporary forwarding.

It would be great to review the error message and code so that we pick
the right one from the start.

Co-authored-by: Sergey Kuznetsov <skuznetsov@ripple.com>
2024-06-11 10:51:03 +01:00
Peter Chen
1125b09611 Allow tlsv13 in Clio (#1447)
Fixes #1419

Co-authored-by: Sergey Kuznetsov <skuznetsov@ripple.com>
2024-06-10 11:52:53 -04:00
Sergey Kuznetsov
ce94f0f513 Update libxrpl to 2.2.0 (#1446) 2024-06-10 14:52:35 +01:00
Peter Chen
d39fb20022 Update build documentation to address encountered errors (#1442) 2024-06-04 10:05:27 -04:00
Peter Chen
967b85ca33 Change ledgerInfo and replace with ledgerHeader (#1426)
Fixes [#1396](https://github.com/XRPLF/clio/issues/1396)
2024-06-03 13:11:31 -04:00
Peter Chen
55b8134e6d Comment out precommit hook for Doc (#1432)
Temporary Fix for #1431
2024-05-30 20:17:18 +01:00
Alex Kremer
66e8a65732 Flow to check new libXRPL version (#1433) 2024-05-29 17:31:15 +01:00
Peter Chen
067dd72aed Move NameGenerator to util (#1428)
Fixes for #876
2024-05-29 09:08:27 -04:00
Peter Chen
da5bf5c441 Fix invalid syntax in example-config.json (#1423)
remove "." fixes syntax for example-config; runs with ./clio_server
2024-05-24 09:22:08 -04:00
Sergey Kuznetsov
ff4bc5b0aa Load tool (#1421)
Simple tool to put a specific load on clio.
2024-05-24 13:01:36 +01:00
144 changed files with 2815 additions and 1259 deletions

View File

@@ -5,4 +5,6 @@ Diagnostics:
UnusedIncludes: Strict
MissingIncludes: Strict
Includes:
IgnoreHeader: ".*/(detail|impl)/.*"
IgnoreHeader:
- ".*/(detail|impl)/.*"
- ".*expected.*"

View File

@@ -3,4 +3,6 @@
# This script is intended to be run from the root of the repository.
source .githooks/check-format
source .githooks/check-docs
#source .githooks/check-docs
# TODO: Fix Doxygen issue with reference links. See https://github.com/XRPLF/clio/issues/1431

View File

@@ -7,6 +7,14 @@ inputs:
body:
description: Issue body
required: true
labels:
description: Comma-separated list of labels
required: true
default: 'bug'
assignees:
description: Comma-separated list of assignees
required: true
default: 'cindyyan317,godexsoft,kuznetsss'
outputs:
created_issue_id:
description: Created issue id
@@ -19,7 +27,7 @@ runs:
shell: bash
run: |
echo -e '${{ inputs.body }}' > issue.md
gh issue create --assignee 'cindyyan317,godexsoft,kuznetsss' --label bug --title '${{ inputs.title }}' --body-file ./issue.md > create_issue.log
gh issue create --assignee '${{ inputs.assignees }}' --label '${{ inputs.labels }}' --title '${{ inputs.title }}' --body-file ./issue.md > create_issue.log
created_issue=$(cat create_issue.log | sed 's|.*/||')
echo "created_issue=$created_issue" >> $GITHUB_OUTPUT
rm create_issue.log issue.md

28
.github/scripts/update-libxrpl-version vendored Executable file
View File

@@ -0,0 +1,28 @@
#!/bin/bash
# Note: This script is intended to be run from the root of the repository.
#
# This script modifies conanfile.py such that the specified version of libXRPL is used.
if [[ -z "$1" ]]; then
cat <<EOF
ERROR
-----------------------------------------------------------------------------
Version should be passed as first argument to the script.
-----------------------------------------------------------------------------
EOF
exit 1
fi
VERSION=$1
GNU_SED=$(sed --version 2>&1 | grep -q 'GNU' && echo true || echo false)
echo "+ Updating required libXRPL version to $VERSION"
if [[ "$GNU_SED" == "false" ]]; then
sed -i '' -E "s|'xrpl/[a-zA-Z0-9\\.\\-]+'|'xrpl/$VERSION'|g" conanfile.py
else
sed -i -E "s|'xrpl/[a-zA-Z0-9\\.\\-]+'|'xrpl/$VERSION'|g" conanfile.py
fi

91
.github/workflows/check_libxrpl.yml vendored Normal file
View File

@@ -0,0 +1,91 @@
name: Check new libXRPL
on:
repository_dispatch:
types: [check_libxrpl]
jobs:
build:
name: Build Clio / `libXRPL ${{ github.event.client_payload.version }}`
runs-on: [self-hosted, heavy]
container:
image: rippleci/clio_ci:latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Update libXRPL version requirement
shell: bash
run: |
./.github/scripts/update-libxrpl-version ${{ github.event.client_payload.version }}
- name: Prepare runner
uses: ./.github/actions/prepare_runner
with:
disable_ccache: true
- name: Setup conan
uses: ./.github/actions/setup_conan
id: conan
with:
conan_profile: gcc
- name: Run conan and cmake
uses: ./.github/actions/generate
with:
conan_profile: ${{ steps.conan.outputs.conan_profile }}
conan_cache_hit: ${{ steps.restore_cache.outputs.conan_cache_hit }}
build_type: Release
- name: Build Clio
uses: ./.github/actions/build_clio
- name: Strip tests
run: strip build/clio_tests
- name: Upload clio_tests
uses: actions/upload-artifact@v4
with:
name: clio_tests_libxrpl-${{ github.event.client_payload.version }}
path: build/clio_tests
run_tests:
name: Run tests
needs: build
runs-on: [self-hosted, heavy]
container:
image: rippleci/clio_ci:latest
steps:
- uses: actions/download-artifact@v4
with:
name: clio_tests_libxrpl-${{ github.event.client_payload.version }}
- name: Run clio_tests
run: |
chmod +x ./clio_tests
./clio_tests
create_issue_on_failure:
name: Create an issue on failure
needs: [build, run_tests]
if: ${{ always() && contains(needs.*.result, 'failure') }}
runs-on: ubuntu-20.04
permissions:
contents: write
issues: write
steps:
- uses: actions/checkout@v4
- name: Create an issue
uses: ./.github/actions/create_issue
env:
GH_TOKEN: ${{ github.token }}
with:
labels: 'compatibility,bug'
title: 'Proposed libXRPL check failed'
body: >
Clio build or tests failed against `libXRPL ${{ github.event.client_payload.version }}`.
Workflow: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/

View File

@@ -36,6 +36,7 @@ Below are some useful docs to learn more about Clio.
- [How to configure Clio and rippled](./docs/configure-clio.md)
- [How to run Clio](./docs/run-clio.md)
- [Logging](./docs/logging.md)
- [Troubleshooting guide](./docs/trouble_shooting.md)
**General reference material:**

View File

@@ -28,7 +28,7 @@ class Clio(ConanFile):
'protobuf/3.21.9',
'grpc/1.50.1',
'openssl/1.1.1u',
'xrpl/2.2.0-rc3',
'xrpl/2.2.0',
'libbacktrace/cci.20210118'
]

View File

@@ -36,6 +36,8 @@ compiler.version=15
compiler.libcxx=libc++
build_type=Release
compiler.cppstd=20
[conf]
tools.build:cxxflags+=["-DBOOST_ASIO_DISABLE_CONCEPTS"]
```
> Linux example:
@@ -91,6 +93,9 @@ If successful, `conan install` will find the required packages and `cmake` will
> [!TIP]
> To generate a Code Coverage report, include `-o coverage=True` in the `conan install` command above, along with `-o tests=True` to enable tests. After running the `cmake` commands, execute `make clio_tests-ccov`. The coverage report will be found at `clio_tests-llvm-cov/index.html`.
> [!NOTE]
> If you've built Clio before and the build is now failing, it's likely due to updated dependencies. Try deleting the build folder and then rerunning the Conan and CMake commands mentioned above.
### Generating API docs for Clio
The API documentation for Clio is generated by [Doxygen](https://www.doxygen.nl/index.html). If you want to generate the API documentation when building Clio, make sure to install Doxygen on your system.

View File

@@ -27,7 +27,7 @@ If you're running Clio and `rippled` on separate machines, in addition to uncomm
2. Open a public, unencrypted WebSocket port on your `rippled` server.
3. In the `rippled` config, change the IP specified for `secure_gateway`, under the `port_grpc` section, to the IP of your Clio server. This entry can take the form of a comma-separated list if you are running multiple Clio nodes.
3. In the `rippled` config, change the IP specified for `secure_gateway`, under the `port_grpc` and websocket server sections, to the IP of your Clio server. This entry can take the form of a comma-separated list if you are running multiple Clio nodes.
## Ledger sequence

View File

@@ -35,7 +35,10 @@
"grpc_port": "50051"
}
],
"forwarding_cache_timeout": 0.250, // in seconds, could be 0, which means no cache
"forwarding": {
"cache_timeout": 0.250, // in seconds, could be 0, which means no cache
"request_timeout": 10.0 // time for Clio to wait for rippled to reply on a forwarded request (default is 10 seconds)
},
"dos_guard": {
// Comma-separated list of IPs to exclude from rate limiting
"whitelist": [
@@ -64,10 +67,10 @@
"admin_password": "xrp",
// If local_admin is true, Clio will consider requests come from 127.0.0.1 as admin requests
// It's true by default unless admin_password is set,'local_admin' : true and 'admin_password' can not be set at the same time
"local_amdin": false
"local_admin": false
},
// Time in seconds for graceful shutdown. Defaults to 10 seconds. Not fully implemented yet.
"graceful_period": 10.,
"graceful_period": 10.0,
// Overrides log level on a per logging channel.
// Defaults to global "log_level" for each unspecified channel.
"log_channels": [

47
docs/trouble_shooting.md Normal file
View File

@@ -0,0 +1,47 @@
# Troubleshooting Guide
This guide will help you troubleshoot common issues of Clio.
## Can't connect to DB
If you see the error log message `Could not connect to Cassandra: No hosts available`, this means that Clio can't connect to the database. Check the following:
- Make sure the database is running at the specified address and port.
- Make sure the database is accessible from the machine where Clio is running.
You can use [cqlsh](https://pypi.org/project/cqlsh/) to check the connection to the database.
If you would like to run a local ScyllaDB, you can call:
```sh
docker run --rm -p 9042:9042 --name clio-scylla -d scylladb/scylla
```
## Check the server status of Clio
To check if Clio is syncing with rippled:
```sh
curl -v -d '{"method":"server_info", "params":[{}]}' 127.0.0.1:51233|python3 -m json.tool|grep seq
```
If Clio is syncing with rippled, the `seq` value will be increasing.
## Clio fails to start
If you see the error log message `Failed to fetch ETL state from...`, this means the configured rippled node is not reachable. Check the following:
- Make sure the rippled node is running at the specified address and port.
- Make sure the rippled node is accessible from the machine where Clio is running.
If you would like to run Clio without an avaliable rippled node, you can add below setting to Clio's configuration file:
```
"allow_no_etl": true
```
## Clio is not added to secure_gateway in rippled's config
If you see the warning message `AsyncCallData is_unlimited is false.`, this means that Clio is not added to the `secure_gateway` of `port_grpc` session in the rippled configuration file. It will slow down the sync process. Please add Clio's IP to the `secure_gateway` in the rippled configuration file for both grpc and ws port.
## Clio is slow
To speed up the response time, Clio has a cache inside. However, cache can take time to warm up. If you see slow response time, you can firstly check if cache is still loading.
You can check the cache status by calling:
```sh
curl -v -d '{"method":"server_info", "params":[{}]}' 127.0.0.1:51233|python3 -m json.tool|grep is_full
curl -v -d '{"method":"server_info", "params":[{}]}' 127.0.0.1:51233|python3 -m json.tool|grep is_enabled
```
If `is_full` is false, it means the cache is still loading. Normally, the Clio can respond quicker if cache finishs loading. If `is_enabled` is false, it means the cache is disabled in the configuration file or there is data corruption in the database.
## Receive error message `Too many requests`
If client sees the error message `Too many requests`, this means that the client is blocked by Clio's DosGuard protection. You may want to add the client's IP to the whitelist in the configuration file, Or update other your DosGuard settings.

View File

@@ -206,13 +206,13 @@ public:
}
void
writeLedger(ripple::LedgerHeader const& ledgerInfo, std::string&& blob) override
writeLedger(ripple::LedgerHeader const& ledgerHeader, std::string&& blob) override
{
executor_.write(schema_->insertLedgerHeader, ledgerInfo.seq, std::move(blob));
executor_.write(schema_->insertLedgerHeader, ledgerHeader.seq, std::move(blob));
executor_.write(schema_->insertLedgerHash, ledgerInfo.hash, ledgerInfo.seq);
executor_.write(schema_->insertLedgerHash, ledgerHeader.hash, ledgerHeader.seq);
ledgerSequence_ = ledgerInfo.seq;
ledgerSequence_ = ledgerHeader.seq;
}
std::optional<std::uint32_t>

View File

@@ -47,7 +47,7 @@ struct ETLState {
fetchETLStateFromSource(Forward& source) noexcept
{
auto const serverInfoRippled = data::synchronous([&source](auto yield) {
return source.forwardToRippled({{"command", "server_info"}}, std::nullopt, yield);
return source.forwardToRippled({{"command", "server_info"}}, std::nullopt, {}, yield);
});
if (serverInfoRippled)

View File

@@ -25,7 +25,6 @@
#include "etl/Source.hpp"
#include "feed/SubscriptionManagerInterface.hpp"
#include "util/Assert.hpp"
#include "util/Constants.hpp"
#include "util/Random.hpp"
#include "util/log/Logger.hpp"
@@ -38,7 +37,6 @@
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <memory>
@@ -77,11 +75,9 @@ LoadBalancer::LoadBalancer(
SourceFactory sourceFactory
)
{
auto const forwardingCacheTimeout = config.valueOr<float>("forwarding_cache_timeout", 0.f);
auto const forwardingCacheTimeout = config.valueOr<float>("forwarding.cache_timeout", 0.f);
if (forwardingCacheTimeout > 0.f) {
forwardingCache_ = impl::ForwardingCache{std::chrono::milliseconds{
std::lroundf(forwardingCacheTimeout * static_cast<float>(util::MILLISECONDS_PER_SECOND))
}};
forwardingCache_ = impl::ForwardingCache{Config::toMilliseconds(forwardingCacheTimeout)};
}
static constexpr std::uint32_t MAX_DOWNLOAD = 256;
@@ -103,6 +99,7 @@ LoadBalancer::LoadBalancer(
}
};
auto const forwardingTimeout = Config::toMilliseconds(config.valueOr<float>("forwarding.request_timeout", 10.));
for (auto const& entry : config.array("etl_sources")) {
auto source = sourceFactory(
entry,
@@ -110,22 +107,24 @@ LoadBalancer::LoadBalancer(
backend,
subscriptions,
validatedLedgers,
forwardingTimeout,
[this]() {
if (not hasForwardingSource_)
chooseForwardingSource();
},
[this]() { chooseForwardingSource(); },
[this]() { forwardingCache_->invalidate(); }
[this]() {
if (forwardingCache_.has_value())
forwardingCache_->invalidate();
}
);
// checking etl node validity
auto const stateOpt = ETLState::fetchETLStateFromSource(*source);
if (!stateOpt) {
checkOnETLFailure(fmt::format(
"Failed to fetch ETL state from source = {} Please check the configuration and network",
source->toString()
));
LOG(log_.warn()) << "Failed to fetch ETL state from source = " << source->toString()
<< " Please check the configuration and network";
} else if (etlState_ && etlState_->networkID && stateOpt->networkID &&
etlState_->networkID != stateOpt->networkID) {
checkOnETLFailure(fmt::format(
@@ -141,6 +140,9 @@ LoadBalancer::LoadBalancer(
LOG(log_.info()) << "Added etl source - " << sources_.back()->toString();
}
if (!etlState_)
checkOnETLFailure("Failed to fetch ETL state from any source. Please check the configuration and network");
if (sources_.empty())
checkOnETLFailure("No ETL sources configured. Please check the configuration");
@@ -213,6 +215,7 @@ std::optional<boost::json::object>
LoadBalancer::forwardToRippled(
boost::json::object const& request,
std::optional<std::string> const& clientIp,
bool isAdmin,
boost::asio::yield_context yield
)
{
@@ -227,9 +230,11 @@ LoadBalancer::forwardToRippled(
auto numAttempts = 0u;
auto xUserValue = isAdmin ? ADMIN_FORWARDING_X_USER_VALUE : USER_FORWARDING_X_USER_VALUE;
std::optional<boost::json::object> response;
while (numAttempts < sources_.size()) {
if (auto res = sources_[sourceIdx]->forwardToRippled(request, clientIp, yield)) {
if (auto res = sources_[sourceIdx]->forwardToRippled(request, clientIp, xUserValue, yield)) {
response = std::move(res);
break;
}

View File

@@ -20,8 +20,8 @@
#pragma once
#include "data/BackendInterface.hpp"
#include "etl/ETLHelpers.hpp"
#include "etl/ETLState.hpp"
#include "etl/NetworkValidatedLedgersInterface.hpp"
#include "etl/Source.hpp"
#include "etl/impl/ForwardingCache.hpp"
#include "feed/SubscriptionManagerInterface.hpp"
@@ -44,7 +44,7 @@
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <string_view>
#include <vector>
namespace etl {
@@ -68,6 +68,8 @@ private:
util::Logger log_{"ETL"};
// Forwarding cache must be destroyed after sources because sources have a callback to invalidate cache
std::optional<impl::ForwardingCache> forwardingCache_;
std::optional<std::string> forwardingXUserValue_;
std::vector<SourcePtr> sources_;
std::optional<ETLState> etlState_;
std::uint32_t downloadRanges_ =
@@ -75,6 +77,16 @@ private:
std::atomic_bool hasForwardingSource_{false};
public:
/**
* @brief Value for the X-User header when forwarding admin requests
*/
static constexpr std::string_view ADMIN_FORWARDING_X_USER_VALUE = "clio_admin";
/**
* @brief Value for the X-User header when forwarding user requests
*/
static constexpr std::string_view USER_FORWARDING_X_USER_VALUE = "clio_user";
/**
* @brief Create an instance of the load balancer.
*
@@ -167,6 +179,7 @@ public:
*
* @param request JSON-RPC request to forward
* @param clientIp The IP address of the peer, if known
* @param isAdmin Whether the request is from an admin
* @param yield The coroutine context
* @return Response received from rippled node as JSON object on success; nullopt on failure
*/
@@ -174,6 +187,7 @@ public:
forwardToRippled(
boost::json::object const& request,
std::optional<std::string> const& clientIp,
bool isAdmin,
boost::asio::yield_context yield
);

View File

@@ -30,6 +30,7 @@
#include <boost/asio/io_context.hpp>
#include <chrono>
#include <memory>
#include <string>
#include <utility>
@@ -43,6 +44,7 @@ make_Source(
std::shared_ptr<BackendInterface> backend,
std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions,
std::shared_ptr<NetworkValidatedLedgersInterface> validatedLedgers,
std::chrono::steady_clock::duration forwardingTimeout,
SourceBase::OnConnectHook onConnect,
SourceBase::OnDisconnectHook onDisconnect,
SourceBase::OnLedgerClosedHook onLedgerClosed
@@ -52,7 +54,7 @@ make_Source(
auto const wsPort = config.valueOr<std::string>("ws_port", {});
auto const grpcPort = config.valueOr<std::string>("grpc_port", {});
impl::ForwardingSource forwardingSource{ip, wsPort};
impl::ForwardingSource forwardingSource{ip, wsPort, forwardingTimeout};
impl::GrpcSource grpcSource{ip, grpcPort, std::move(backend)};
auto subscriptionSource = std::make_unique<impl::SubscriptionSource>(
ioc,

View File

@@ -32,11 +32,13 @@
#include <grpcpp/support/status.h>
#include <org/xrpl/rpc/v1/get_ledger.pb.h>
#include <chrono>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
@@ -127,6 +129,7 @@ public:
*
* @param request The request to forward
* @param forwardToRippledClientIp IP of the client forwarding this request if known
* @param xUserValue Value of the X-User header
* @param yield The coroutine context
* @return Response wrapped in an optional on success; nullopt otherwise
*/
@@ -134,6 +137,7 @@ public:
forwardToRippled(
boost::json::object const& request,
std::optional<std::string> const& forwardToRippledClientIp,
std::string_view xUserValue,
boost::asio::yield_context yield
) const = 0;
};
@@ -146,6 +150,7 @@ using SourceFactory = std::function<SourcePtr(
std::shared_ptr<BackendInterface> backend,
std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions,
std::shared_ptr<NetworkValidatedLedgersInterface> validatedLedgers,
std::chrono::steady_clock::duration forwardingTimeout,
SourceBase::OnConnectHook onConnect,
SourceBase::OnDisconnectHook onDisconnect,
SourceBase::OnLedgerClosedHook onLedgerClosed
@@ -159,6 +164,7 @@ using SourceFactory = std::function<SourcePtr(
* @param backend BackendInterface implementation
* @param subscriptions Subscription manager
* @param validatedLedgers The network validated ledgers data structure
* @param forwardingTimeout The timeout for forwarding to rippled
* @param onConnect The hook to call on connect
* @param onDisconnect The hook to call on disconnect
* @param onLedgerClosed The hook to call on ledger closed. This is called when a ledger is closed and the source is set
@@ -172,6 +178,7 @@ make_Source(
std::shared_ptr<BackendInterface> backend,
std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions,
std::shared_ptr<NetworkValidatedLedgersInterface> validatedLedgers,
std::chrono::steady_clock::duration forwardingTimeout,
SourceBase::OnConnectHook onConnect,
SourceBase::OnDisconnectHook onDisconnect,
SourceBase::OnLedgerClosedHook onLedgerClosed

View File

@@ -34,6 +34,7 @@
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
namespace etl::impl {
@@ -41,9 +42,12 @@ namespace etl::impl {
ForwardingSource::ForwardingSource(
std::string ip,
std::string wsPort,
std::chrono::steady_clock::duration forwardingTimeout,
std::chrono::steady_clock::duration connectionTimeout
)
: log_(fmt::format("ForwardingSource[{}:{}]", ip, wsPort)), connectionBuilder_(std::move(ip), std::move(wsPort))
: log_(fmt::format("ForwardingSource[{}:{}]", ip, wsPort))
, connectionBuilder_(std::move(ip), std::move(wsPort))
, forwardingTimeout_{forwardingTimeout}
{
connectionBuilder_.setConnectionTimeout(connectionTimeout)
.addHeader(
@@ -55,6 +59,7 @@ std::optional<boost::json::object>
ForwardingSource::forwardToRippled(
boost::json::object const& request,
std::optional<std::string> const& forwardToRippledClientIp,
std::string_view xUserValue,
boost::asio::yield_context yield
) const
{
@@ -64,18 +69,21 @@ ForwardingSource::forwardToRippled(
{boost::beast::http::field::forwarded, fmt::format("for={}", *forwardToRippledClientIp)}
);
}
connectionBuilder.addHeader({"X-User", std::string{xUserValue}});
auto expectedConnection = connectionBuilder.connect(yield);
if (not expectedConnection) {
return std::nullopt;
}
auto& connection = expectedConnection.value();
auto writeError = connection->write(boost::json::serialize(request), yield);
auto writeError = connection->write(boost::json::serialize(request), yield, forwardingTimeout_);
if (writeError) {
return std::nullopt;
}
auto response = connection->read(yield);
auto response = connection->read(yield, forwardingTimeout_);
if (not response) {
return std::nullopt;
}

View File

@@ -28,19 +28,22 @@
#include <chrono>
#include <optional>
#include <string>
#include <string_view>
namespace etl::impl {
class ForwardingSource {
util::Logger log_;
util::requests::WsConnectionBuilder connectionBuilder_;
std::chrono::steady_clock::duration forwardingTimeout_;
static constexpr std::chrono::seconds CONNECTION_TIMEOUT{3};
public:
ForwardingSource(
std::string ip_,
std::string wsPort_,
std::string ip,
std::string wsPort,
std::chrono::steady_clock::duration forwardingTimeout,
std::chrono::steady_clock::duration connectionTimeout = CONNECTION_TIMEOUT
);
@@ -49,6 +52,7 @@ public:
*
* @param request The request to forward
* @param forwardToRippledClientIp IP of the client forwarding this request if known
* @param xUserValue Optional value for X-User header
* @param yield The coroutine context
* @return Response wrapped in an optional on success; nullopt otherwise
*/
@@ -56,6 +60,7 @@ public:
forwardToRippled(
boost::json::object const& request,
std::optional<std::string> const& forwardToRippledClientIp,
std::string_view xUserValue,
boost::asio::yield_context yield
) const;
};

View File

@@ -34,6 +34,7 @@
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace etl::impl {
@@ -202,6 +203,7 @@ public:
*
* @param request The request to forward
* @param forwardToRippledClientIp IP of the client forwarding this request if known
* @param xUserValue Optional value of the X-User header
* @param yield The coroutine context
* @return Response wrapped in an optional on success; nullopt otherwise
*/
@@ -209,10 +211,11 @@ public:
forwardToRippled(
boost::json::object const& request,
std::optional<std::string> const& forwardToRippledClientIp,
std::string_view xUserValue,
boost::asio::yield_context yield
) const final
{
return forwardingSource_.forwardToRippled(request, forwardToRippledClientIp, yield);
return forwardingSource_.forwardToRippled(request, forwardToRippledClientIp, xUserValue, yield);
}
};

View File

@@ -136,7 +136,7 @@ parseCerts(Config const& config)
readKey.close();
std::string key = contents.str();
ssl::context ctx{ssl::context::tlsv12};
ssl::context ctx{ssl::context::tls_server};
ctx.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2);
ctx.use_certificate_chain(buffer(cert.data(), cert.size()));
ctx.use_private_key(buffer(key.data(), key.size()), ssl::context::file_format::pem);

View File

@@ -25,6 +25,7 @@ target_sources(
handlers/BookChanges.cpp
handlers/BookOffers.cpp
handlers/DepositAuthorized.cpp
handlers/Feature.cpp
handlers/GatewayBalances.cpp
handlers/GetAggregatePrice.cpp
handlers/Ledger.cpp

View File

@@ -358,7 +358,7 @@ toJson(ripple::LedgerHeader const& lgrInfo, bool const binary, std::uint32_t con
{
boost::json::object header;
if (binary) {
header[JS(ledger_data)] = ripple::strHex(ledgerInfoToBlob(lgrInfo));
header[JS(ledger_data)] = ripple::strHex(ledgerHeaderToBlob(lgrInfo));
} else {
header[JS(account_hash)] = ripple::strHex(lgrInfo.accountHash);
header[JS(close_flags)] = lgrInfo.closeFlags;
@@ -396,7 +396,7 @@ parseStringAsUInt(std::string const& value)
}
std::variant<Status, ripple::LedgerHeader>
ledgerInfoFromRequest(std::shared_ptr<data::BackendInterface const> const& backend, web::Context const& ctx)
ledgerHeaderFromRequest(std::shared_ptr<data::BackendInterface const> const& backend, web::Context const& ctx)
{
auto hashValue = ctx.params.contains("ledger_hash") ? ctx.params.at("ledger_hash") : nullptr;
@@ -444,9 +444,9 @@ ledgerInfoFromRequest(std::shared_ptr<data::BackendInterface const> const& backe
return *lgrInfo;
}
// extract ledgerInfoFromRequest's parameter from context
// extract ledgerHeaderFromRequest's parameter from context
std::variant<Status, ripple::LedgerHeader>
getLedgerInfoFromHashOrSeq(
getLedgerHeaderFromHashOrSeq(
BackendInterface const& backend,
boost::asio::yield_context yield,
std::optional<std::string> ledgerHash,
@@ -479,7 +479,7 @@ getLedgerInfoFromHashOrSeq(
}
std::vector<unsigned char>
ledgerInfoToBlob(ripple::LedgerHeader const& info, bool includeHash)
ledgerHeaderToBlob(ripple::LedgerHeader const& info, bool includeHash)
{
ripple::Serializer s;
s.add32(info.seq);

View File

@@ -265,7 +265,7 @@ generatePubLedgerMessage(
* @return The ledger info or an error status
*/
std::variant<Status, ripple::LedgerHeader>
ledgerInfoFromRequest(std::shared_ptr<data::BackendInterface const> const& backend, web::Context const& ctx);
ledgerHeaderFromRequest(std::shared_ptr<data::BackendInterface const> const& backend, web::Context const& ctx);
/**
* @brief Get ledger info from hash or sequence
@@ -278,7 +278,7 @@ ledgerInfoFromRequest(std::shared_ptr<data::BackendInterface const> const& backe
* @return The ledger info or an error status
*/
std::variant<Status, ripple::LedgerHeader>
getLedgerInfoFromHashOrSeq(
getLedgerHeaderFromHashOrSeq(
BackendInterface const& backend,
boost::asio::yield_context yield,
std::optional<std::string> ledgerHash,
@@ -372,7 +372,7 @@ getAccountsFromTransaction(boost::json::object const& transaction);
* @return The blob
*/
std::vector<unsigned char>
ledgerInfoToBlob(ripple::LedgerHeader const& info, bool includeHash = false);
ledgerHeaderToBlob(ripple::LedgerHeader const& info, bool includeHash = false);
/**
* @brief Whether global frozen is set

View File

@@ -20,10 +20,8 @@
#pragma once
#include "rpc/Errors.hpp"
#include "rpc/common/Concepts.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include <boost/json/value.hpp>
#include <fmt/core.h>
@@ -146,10 +144,10 @@ public:
[[nodiscard]] MaybeError
verify(boost::json::value& value, std::string_view key) const
{
if (not value.is_object() or not value.as_object().contains(key.data()))
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
if (not rpc::validation::checkType<Type>(value.as_object().at(key.data())))
if (not rpc::validation::checkType<Type>(value.as_object().at(key)))
return {}; // ignore if type does not match
return processor_(value, key);
@@ -162,9 +160,10 @@ private:
/**
* @brief A meta-processor that wraps a validator and produces a custom error in case the wrapped validator fails.
*/
template <typename SomeRequirement>
template <typename RequirementOrModifierType>
requires SomeRequirement<RequirementOrModifierType> or SomeModifier<RequirementOrModifierType>
class WithCustomError final {
SomeRequirement requirement;
RequirementOrModifierType reqOrModifier;
Status error;
public:
@@ -172,10 +171,11 @@ public:
* @brief Constructs a validator that calls the given validator `req` and returns a custom error `err` in case `req`
* fails.
*
* @param req The requirement to validate against
* @param reqOrModifier The requirement to validate against
* @param err The custom error to return in case `req` fails
*/
WithCustomError(SomeRequirement req, Status err) : requirement{std::move(req)}, error{std::move(err)}
WithCustomError(RequirementOrModifierType reqOrModifier, Status err)
: reqOrModifier{std::move(reqOrModifier)}, error{std::move(err)}
{
}
@@ -188,8 +188,9 @@ public:
*/
[[nodiscard]] MaybeError
verify(boost::json::value const& value, std::string_view key) const
requires SomeRequirement<RequirementOrModifierType>
{
if (auto const res = requirement.verify(value, key); not res)
if (auto const res = reqOrModifier.verify(value, key); not res)
return Error{error};
return {};
@@ -205,12 +206,30 @@ public:
*/
[[nodiscard]] MaybeError
verify(boost::json::value& value, std::string_view key) const
requires SomeRequirement<RequirementOrModifierType>
{
if (auto const res = requirement.verify(value, key); not res)
if (auto const res = reqOrModifier.verify(value, key); not res)
return Error{error};
return {};
}
/**
* @brief Runs the stored modifier and produces a custom error if the wrapped modifier fails.
*
* @param value The JSON value representing the outer object. This value can be modified by the modifier.
* @param key The key used to retrieve the element from the outer object
* @return Possibly an error
*/
MaybeError
modify(boost::json::value& value, std::string_view key) const
requires SomeModifier<RequirementOrModifierType>
{
if (auto const res = reqOrModifier.modify(value, key); not res)
return Error{error};
return {};
}
};
} // namespace rpc::meta

View File

@@ -19,12 +19,16 @@
#pragma once
#include "rpc/Errors.hpp"
#include "rpc/common/Types.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <ripple/protocol/ErrorCodes.h>
#include <exception>
#include <functional>
#include <string>
#include <string_view>
@@ -100,4 +104,75 @@ struct ToLower final {
}
};
/**
* @brief Convert input string to integer.
*
* Note: the conversion is only performed if the input value is a string.
*/
struct ToNumber final {
/**
* @brief Update the input string to integer if it can be converted to integer by stoi.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the modified value from the outer object
* @return Possibly an error
*/
[[nodiscard]] static MaybeError
modify(boost::json::value& value, std::string_view key)
{
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
if (not value.as_object().at(key).is_string())
return {}; // ignore for non-string types
auto const strInt = boost::json::value_to<std::string>(value.as_object().at(key));
if (strInt.find('.') != std::string::npos)
return Error{Status{RippledError::rpcINVALID_PARAMS}}; // maybe a float
try {
value.as_object()[key.data()] = std::stoi(strInt);
} catch (std::exception& e) {
return Error{Status{RippledError::rpcINVALID_PARAMS}};
}
return {};
}
};
/**
* @brief Customised modifier allowing user define how to modify input in provided callable.
*/
class CustomModifier final {
std::function<MaybeError(boost::json::value&, std::string_view)> modifier_;
public:
/**
* @brief Constructs a custom modifier from any supported callable.
*
* @tparam Fn The type of callable
* @param fn The callable/function object
*/
template <typename Fn>
requires std::invocable<Fn, boost::json::value&, std::string_view>
explicit CustomModifier(Fn&& fn) : modifier_{std::forward<Fn>(fn)}
{
}
/**
* @brief Modify the JSON value according to the custom modifier function stored.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the tested value from the outer object
* @return Any compatible user-provided error if modify/verify failed; otherwise no error is returned
*/
[[nodiscard]] MaybeError
modify(boost::json::value& value, std::string_view key) const
{
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
return modifier_(value.as_object().at(key.data()), key);
};
};
} // namespace rpc::modifiers

View File

@@ -94,7 +94,7 @@ struct ReturnType {
* @param warnings The warnings generated by the RPC call
*/
ReturnType(std::expected<boost::json::value, Status> result, boost::json::array warnings = {})
: result{std::move(result)}, warnings{std::move(warnings)}
: result{std::move(result)}, warnings(std::move(warnings))
{
}

View File

@@ -29,6 +29,7 @@
#include <fmt/core.h>
#include <ripple/basics/base_uint.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/UintTypes.h>
#include <ripple/protocol/tokens.h>
@@ -45,7 +46,7 @@ namespace rpc::validation {
[[nodiscard]] MaybeError
Required::verify(boost::json::value const& value, std::string_view key)
{
if (not value.is_object() or not value.as_object().contains(key.data()))
if (not value.is_object() or not value.as_object().contains(key))
return Error{Status{RippledError::rpcINVALID_PARAMS, "Required field '" + std::string{key} + "' missing"}};
return {};
@@ -54,10 +55,10 @@ Required::verify(boost::json::value const& value, std::string_view key)
[[nodiscard]] MaybeError
CustomValidator::verify(boost::json::value const& value, std::string_view key) const
{
if (not value.is_object() or not value.as_object().contains(key.data()))
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
return validator_(value.as_object().at(key.data()), key);
return validator_(value.as_object().at(key), key);
}
[[nodiscard]] bool
@@ -140,8 +141,12 @@ CustomValidator CurrencyValidator =
if (!value.is_string())
return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}};
auto const currencyStr = boost::json::value_to<std::string>(value);
if (currencyStr.empty())
return Error{Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "IsEmpty"}};
ripple::Currency currency;
if (!ripple::to_currency(currency, boost::json::value_to<std::string>(value)))
if (!ripple::to_currency(currency, currencyStr))
return Error{Status{ClioError::rpcMALFORMED_CURRENCY, "malformedCurrency"}};
return MaybeError{};

View File

@@ -402,6 +402,7 @@ public:
* @param fn The callable/function object
*/
template <typename Fn>
requires std::invocable<Fn, boost::json::value const&, std::string_view>
explicit CustomValidator(Fn&& fn) : validator_{std::forward<Fn>(fn)}
{
}

View File

@@ -55,17 +55,22 @@ public:
bool
shouldForward(web::Context const& ctx) const
{
auto const& request = ctx.params;
if (ctx.method == "subscribe" || ctx.method == "unsubscribe")
return false;
// TODO https://github.com/XRPLF/clio/issues/1131 - remove once clio-native feature is
// implemented fully. For now we disallow forwarding of the admin api, only user api is allowed.
if (ctx.method == "feature" and not request.contains("vetoed"))
return true;
if (handlerProvider_->isClioOnly(ctx.method))
return false;
if (isProxied(ctx.method))
return true;
auto const& request = ctx.params;
if (specifiesCurrentOrClosedLedger(request))
return true;
@@ -88,7 +93,7 @@ public:
auto toForward = ctx.params;
toForward["command"] = ctx.method;
auto res = balancer_->forwardToRippled(toForward, ctx.clientIp, ctx.yield);
auto res = balancer_->forwardToRippled(toForward, ctx.clientIp, ctx.isAdmin, ctx.yield);
if (not res) {
notifyFailedToForward(ctx.method);
return Result{Status{RippledError::rpcFAILED_TO_FORWARD}};

View File

@@ -36,6 +36,7 @@
#include "rpc/handlers/BookChanges.hpp"
#include "rpc/handlers/BookOffers.hpp"
#include "rpc/handlers/DepositAuthorized.hpp"
#include "rpc/handlers/Feature.hpp"
#include "rpc/handlers/GatewayBalances.hpp"
#include "rpc/handlers/GetAggregatePrice.hpp"
#include "rpc/handlers/Ledger.hpp"
@@ -85,6 +86,7 @@ ProductionHandlerProvider::ProductionHandlerProvider(
{"book_changes", {BookChangesHandler{backend}}},
{"book_offers", {BookOffersHandler{backend}}},
{"deposit_authorized", {DepositAuthorizedHandler{backend}}},
{"feature", {FeatureHandler{}}},
{"gateway_balances", {GatewayBalancesHandler{backend}}},
{"get_aggregate_price", {GetAggregatePriceHandler{backend}}},
{"ledger", {LedgerHandler{backend}}},

View File

@@ -94,14 +94,14 @@ AMMInfoHandler::process(AMMInfoHandler::Input input, Context const& ctx) const
return Error{Status{RippledError::rpcINVALID_PARAMS}};
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);
if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<LedgerInfo>(lgrInfoOrStatus);
auto const lgrInfo = std::get<LedgerHeader>(lgrInfoOrStatus);
if (input.accountID) {
auto keylet = keylet::account(*input.accountID);

View File

@@ -84,7 +84,7 @@ AccountChannelsHandler::Result
AccountChannelsHandler::process(AccountChannelsHandler::Input input, Context const& ctx) const
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -46,7 +46,7 @@ AccountCurrenciesHandler::Result
AccountCurrenciesHandler::process(AccountCurrenciesHandler::Input input, Context const& ctx) const
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -56,7 +56,7 @@ AccountInfoHandler::process(AccountInfoHandler::Input input, Context const& ctx)
return Error{Status{RippledError::rpcINVALID_PARAMS, ripple::RPC::missing_field_message(JS(account))}};
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -120,7 +120,7 @@ AccountLinesHandler::Result
AccountLinesHandler::process(AccountLinesHandler::Input input, Context const& ctx) const
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -52,7 +52,7 @@ AccountNFTsHandler::Result
AccountNFTsHandler::process(AccountNFTsHandler::Input input, Context const& ctx) const
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -52,7 +52,7 @@ AccountObjectsHandler::Result
AccountObjectsHandler::process(AccountObjectsHandler::Input input, Context const& ctx) const
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -68,7 +68,7 @@ AccountOffersHandler::Result
AccountOffersHandler::process(AccountOffersHandler::Input input, Context const& ctx) const
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -91,7 +91,7 @@ AccountTxHandler::process(AccountTxHandler::Input input, Context const& ctx) con
if (!input.ledgerIndexMax && !input.ledgerIndexMin) {
// mimic rippled, when both range and index specified, respect the range.
// take ledger from ledgerHash or ledgerIndex only when range is not specified
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);
@@ -169,11 +169,11 @@ AccountTxHandler::process(AccountTxHandler::Input input, Context const& ctx) con
obj[JS(hash)] = obj[txKey].as_object()[JS(hash)];
obj[txKey].as_object().erase(JS(hash));
}
if (auto const ledgerInfo =
if (auto const ledgerHeader =
sharedPtrBackend_->fetchLedgerBySequence(txnPlusMeta.ledgerSequence, ctx.yield);
ledgerInfo) {
obj[JS(ledger_hash)] = ripple::strHex(ledgerInfo->hash);
obj[JS(close_time_iso)] = ripple::to_string_iso(ledgerInfo->closeTime);
ledgerHeader) {
obj[JS(ledger_hash)] = ripple::strHex(ledgerHeader->hash);
obj[JS(close_time_iso)] = ripple::to_string_iso(ledgerHeader->closeTime);
}
}
obj[JS(validated)] = true;

View File

@@ -45,7 +45,7 @@ BookChangesHandler::Result
BookChangesHandler::process(BookChangesHandler::Input input, Context const& ctx) const
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -51,7 +51,7 @@ BookOffersHandler::process(Input input, Context const& ctx) const
// check ledger
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -46,7 +46,7 @@ DepositAuthorizedHandler::Result
DepositAuthorizedHandler::process(DepositAuthorizedHandler::Input input, Context const& ctx) const
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -0,0 +1,84 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, 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/handlers/Feature.hpp"
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/MetaProcessors.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include "util/Assert.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <ripple/protocol/jss.h>
#include <cstdint>
#include <string>
namespace rpc {
FeatureHandler::Result
FeatureHandler::process([[maybe_unused]] FeatureHandler::Input input, [[maybe_unused]] Context const& ctx)
{
// For now this handler only fires when "vetoed" is set in the request.
// This always leads to a `notSupported` error as we don't want anyone to be able to
ASSERT(false, "FeatureHandler::process is not implemented.");
return Output{};
}
RpcSpecConstRef
FeatureHandler::spec([[maybe_unused]] uint32_t apiVersion)
{
static RpcSpec const rpcSpec = {
{JS(feature), validation::Type<std::string>{}},
{JS(vetoed),
meta::WithCustomError{
validation::NotSupported{},
Status(RippledError::rpcNO_PERMISSION, "The admin portion of feature API is not available through Clio.")
}},
};
return rpcSpec;
}
void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, FeatureHandler::Output const& output)
{
using boost::json::value_from;
jv = {
{JS(validated), output.validated},
};
}
FeatureHandler::Input
tag_invoke(boost::json::value_to_tag<FeatureHandler::Input>, boost::json::value const& jv)
{
auto input = FeatureHandler::Input{};
auto const jsonObject = jv.as_object();
if (jsonObject.contains(JS(feature)))
input.feature = jv.at(JS(feature)).as_string();
return input;
}
} // namespace rpc

View File

@@ -0,0 +1,95 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, 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 "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <ripple/protocol/jss.h>
#include <cstdint>
#include <string>
namespace rpc {
/**
* @brief Contains common functionality for handling the `server_info` command
*/
class FeatureHandler {
public:
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::string feature;
};
/**
* @brief A struct to hold the output data of the command
*/
struct Output {
// validated should be sent via framework
bool validated = true;
};
using Result = HandlerReturnType<Output>;
/**
* @brief Returns the API specification for the command
*
* @param apiVersion The api version to return the spec for
* @return The spec for the given apiVersion
*/
static RpcSpecConstRef
spec([[maybe_unused]] uint32_t apiVersion);
/**
* @brief Process the Feature command
*
* @param input The input data for the command
* @param ctx The context of the request
* @return The result of the operation
*/
static Result
process(Input input, Context const& ctx); // NOLINT(readability-convert-member-functions-to-static)
private:
/**
* @brief Convert the Output to a JSON object
*
* @param [out] jv The JSON object to convert to
* @param output The output to convert
*/
friend void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output);
/**
* @brief Convert a JSON object to Input type
*
* @param jv The JSON object to convert
* @return Input parsed from the JSON object
*/
friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -59,7 +59,7 @@ GatewayBalancesHandler::process(GatewayBalancesHandler::Input input, Context con
{
// check ledger
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -61,7 +61,7 @@ GetAggregatePriceHandler::Result
GetAggregatePriceHandler::process(GetAggregatePriceHandler::Input input, Context const& ctx) const
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -22,6 +22,8 @@
#include "data/BackendInterface.hpp"
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/MetaProcessors.hpp"
#include "rpc/common/Modifiers.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
@@ -31,7 +33,6 @@
#include <boost/json/conversion.hpp>
#include <ripple/basics/Number.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/STAmount.h>
#include <ripple/protocol/STObject.h>
#include <ripple/protocol/jss.h>
@@ -44,6 +45,7 @@
#include <string_view>
#include <utility>
#include <vector>
namespace rpc {
/**
@@ -131,23 +133,26 @@ public:
static auto constexpr ORACLES_MAX = 200;
static auto const oraclesValidator =
validation::CustomValidator{[](boost::json::value const& value, std::string_view) -> MaybeError {
modifiers::CustomModifier{[](boost::json::value& value, std::string_view) -> MaybeError {
if (!value.is_array() or value.as_array().empty() or value.as_array().size() > ORACLES_MAX)
return Error{Status{RippledError::rpcORACLE_MALFORMED}};
for (auto oracle : value.as_array()) {
for (auto& oracle : value.as_array()) {
if (!oracle.is_object() or !oracle.as_object().contains(JS(oracle_document_id)) or
!oracle.as_object().contains(JS(account)))
return Error{Status{RippledError::rpcORACLE_MALFORMED}};
auto maybeError =
validation::Type<std::uint32_t>{}.verify(oracle.as_object(), JS(oracle_document_id));
auto maybeError = validation::Type<std::uint32_t, std::string>{}.verify(
oracle.as_object(), JS(oracle_document_id)
);
if (!maybeError)
return maybeError;
maybeError = modifiers::ToNumber::modify(oracle, JS(oracle_document_id));
if (!maybeError)
return maybeError;
maybeError = validation::AccountBase58Validator.verify(oracle.as_object(), JS(account));
if (!maybeError)
return Error{Status{RippledError::rpcINVALID_PARAMS}};
};
@@ -158,11 +163,15 @@ public:
static auto const rpcSpec = RpcSpec{
{JS(ledger_hash), validation::Uint256HexStringValidator},
{JS(ledger_index), validation::LedgerIndexValidator},
// note: Rippled's base_asset and quote_asset can be non-string. It will eventually return
// "rpcOBJECT_NOT_FOUND". Clio will return "rpcINVALID_PARAMS" if the base_asset or quote_asset is not a
// string. User can clearly know there is a mistake in the input.
{JS(base_asset), validation::Required{}, validation::Type<std::string>{}},
{JS(quote_asset), validation::Required{}, validation::Type<std::string>{}},
// validate quoteAsset and base_asset in accordance to the currency code found in XRPL doc:
// https://xrpl.org/docs/references/protocol/data-types/currency-formats#currency-codes
// usually Clio returns rpcMALFORMED_CURRENCY , return InvalidParam here just to mimic rippled
{JS(base_asset),
validation::Required{},
meta::WithCustomError{validation::CurrencyValidator, Status(RippledError::rpcINVALID_PARAMS)}},
{JS(quote_asset),
validation::Required{},
meta::WithCustomError{validation::CurrencyValidator, Status(RippledError::rpcINVALID_PARAMS)}},
{JS(oracles), validation::Required{}, oraclesValidator},
// note: Unlike `rippled`, Clio only supports UInt as input, no string, no `null`, etc.
{JS(time_threshold), validation::Type<std::uint32_t>{}},

View File

@@ -52,7 +52,7 @@ LedgerHandler::Result
LedgerHandler::process(LedgerHandler::Input input, Context const& ctx) const
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -61,7 +61,7 @@ LedgerDataHandler::process(Input input, Context const& ctx) const
return Error{Status{RippledError::rpcINVALID_PARAMS, "markerNotString"}};
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -149,7 +149,7 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx)
// check ledger exists
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -24,6 +24,7 @@
#include "rpc/JS.hpp"
#include "rpc/common/Checkers.hpp"
#include "rpc/common/MetaProcessors.hpp"
#include "rpc/common/Modifiers.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
@@ -296,8 +297,9 @@ public:
{JS(oracle_document_id),
meta::WithCustomError{validation::Required{}, Status(ClioError::rpcMALFORMED_REQUEST)},
meta::WithCustomError{
validation::Type<uint32_t>{}, Status(ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID)
}},
validation::Type<uint32_t, std::string>{}, Status(ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID)
},
meta::WithCustomError{modifiers::ToNumber{}, Status(ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID)}},
}}},
{JS(ledger), check::Deprecated{}},
};

View File

@@ -77,7 +77,7 @@ NFTHistoryHandler::process(NFTHistoryHandler::Input input, Context const& ctx) c
if (input.ledgerIndexMax || input.ledgerIndexMin)
return Error{Status{RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}};
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -48,7 +48,7 @@ NFTInfoHandler::process(NFTInfoHandler::Input input, Context const& ctx) const
{
auto const tokenID = ripple::uint256{input.nftID.c_str()};
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -90,8 +90,9 @@ NFTOffersHandlerBase::iterateOfferDirectory(
) const
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus =
getLedgerInfoFromHashOrSeq(*sharedPtrBackend_, yield, input.ledgerHash, input.ledgerIndex, range->maxSequence);
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);
if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*status};

View File

@@ -48,13 +48,13 @@ NFTsByIssuerHandler::Result
NFTsByIssuerHandler::process(NFTsByIssuerHandler::Input input, Context const& ctx) const
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);
if (auto const status = std::get_if<Status>(&lgrInfoOrStatus))
return Error{*status};
auto const lgrInfo = std::get<LedgerInfo>(lgrInfoOrStatus);
auto const lgrInfo = std::get<LedgerHeader>(lgrInfoOrStatus);
auto const limit = input.limit.value_or(NFTsByIssuerHandler::LIMIT_DEFAULT);

View File

@@ -58,7 +58,7 @@ NoRippleCheckHandler::Result
NoRippleCheckHandler::process(NoRippleCheckHandler::Input input, Context const& ctx) const
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -222,7 +222,7 @@ public:
}
auto const serverInfoRippled =
balancer_->forwardToRippled({{"command", "server_info"}}, ctx.clientIp, ctx.yield);
balancer_->forwardToRippled({{"command", "server_info"}}, ctx.clientIp, ctx.isAdmin, ctx.yield);
if (serverInfoRippled && !serverInfoRippled->contains(JS(error))) {
if (serverInfoRippled->contains(JS(result)) &&

View File

@@ -43,7 +43,7 @@ TransactionEntryHandler::Result
TransactionEntryHandler::process(TransactionEntryHandler::Input input, Context const& ctx) const
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
auto const lgrInfoOrStatus = getLedgerHeaderFromHashOrSeq(
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
);

View File

@@ -20,12 +20,10 @@
#include "util/SignalsHandler.hpp"
#include "util/Assert.hpp"
#include "util/Constants.hpp"
#include "util/config/Config.hpp"
#include "util/log/Logger.hpp"
#include <chrono>
#include <cmath>
#include <csignal>
#include <cstddef>
#include <functional>
@@ -101,10 +99,8 @@ SignalsHandler::SignalsHandler(Config const& config, std::function<void()> force
{
impl::SignalsHandlerStatic::registerHandler(*this);
auto const gracefulPeriod =
std::round(config.valueOr("graceful_period", 10.f) * static_cast<float>(util::MILLISECONDS_PER_SECOND));
ASSERT(gracefulPeriod >= 0.f, "Graceful period must be non-negative");
gracefulPeriod_ = std::chrono::milliseconds{static_cast<size_t>(gracefulPeriod)};
gracefulPeriod_ = Config::toMilliseconds(config.valueOr("graceful_period", 10.f));
ASSERT(gracefulPeriod_.count() >= 0, "Graceful period must be non-negative");
setHandler(impl::SignalsHandlerStatic::handleSignal);
}

View File

@@ -19,6 +19,8 @@
#include "util/config/Config.hpp"
#include "util/Assert.hpp"
#include "util/Constants.hpp"
#include "util/config/impl/Helpers.hpp"
#include "util/log/Logger.hpp"
@@ -28,6 +30,8 @@
#include <boost/json/value.hpp>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <exception>
#include <filesystem>
#include <fstream>
@@ -178,6 +182,13 @@ Config::array() const
return out;
}
std::chrono::milliseconds
Config::toMilliseconds(float value)
{
ASSERT(value >= 0.0f, "Floating point value of seconds must be non-negative, got: {}", value);
return std::chrono::milliseconds{std::lroundf(value * static_cast<float>(util::MILLISECONDS_PER_SECOND))};
}
Config
ConfigReader::open(std::filesystem::path path)
{

View File

@@ -26,6 +26,7 @@
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <chrono>
#include <cstdint>
#include <exception>
#include <filesystem>
@@ -362,6 +363,15 @@ public:
[[nodiscard]] ArrayType
array() const;
/**
* @brief Method to convert a float seconds value to milliseconds.
*
* @param value The value to convert
* @return The value in milliseconds
*/
static std::chrono::milliseconds
toMilliseconds(float value);
private:
template <typename Return>
[[nodiscard]] Return

View File

@@ -53,20 +53,29 @@ public:
* @brief Read a message from the WebSocket
*
* @param yield yield context
* @param timeout timeout for the operation
* @return Message or error
*/
virtual std::expected<std::string, RequestError>
read(boost::asio::yield_context yield) = 0;
read(
boost::asio::yield_context yield,
std::optional<std::chrono::steady_clock::duration> timeout = std::nullopt
) = 0;
/**
* @brief Write a message to the WebSocket
*
* @param message message to write
* @param yield yield context
* @param timeout timeout for the operation
* @return Error if any
*/
virtual std::optional<RequestError>
write(std::string const& message, boost::asio::yield_context yield) = 0;
write(
std::string const& message,
boost::asio::yield_context yield,
std::optional<std::chrono::steady_clock::duration> timeout = std::nullopt
) = 0;
/**
* @brief Close the WebSocket

View File

@@ -22,8 +22,13 @@
#include "util/requests/Types.hpp"
#include "util/requests/WsConnection.hpp"
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/cancellation_signal.hpp>
#include <boost/asio/cancellation_type.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/beast/core/buffers_to_string.hpp>
#include <boost/beast/core/error.hpp>
#include <boost/beast/core/flat_buffer.hpp>
@@ -32,6 +37,7 @@
#include <boost/beast/websocket/rfc6455.hpp>
#include <boost/beast/websocket/stream.hpp>
#include <boost/beast/websocket/stream_base.hpp>
#include <boost/system/errc.hpp>
#include <chrono>
#include <expected>
@@ -51,27 +57,46 @@ public:
}
std::expected<std::string, RequestError>
read(boost::asio::yield_context yield) override
read(boost::asio::yield_context yield, std::optional<std::chrono::steady_clock::duration> timeout = std::nullopt)
override
{
boost::beast::error_code errorCode;
boost::beast::flat_buffer buffer;
ws_.async_read(buffer, yield[errorCode]);
auto operation = [&](auto&& token) { ws_.async_read(buffer, token); };
if (timeout) {
withTimeout(operation, yield[errorCode], *timeout);
} else {
operation(yield[errorCode]);
}
if (errorCode)
if (errorCode) {
errorCode = mapError(errorCode);
return std::unexpected{RequestError{"Read error", errorCode}};
}
return boost::beast::buffers_to_string(std::move(buffer).data());
}
std::optional<RequestError>
write(std::string const& message, boost::asio::yield_context yield) override
write(
std::string const& message,
boost::asio::yield_context yield,
std::optional<std::chrono::steady_clock::duration> timeout = std::nullopt
) override
{
boost::beast::error_code errorCode;
ws_.async_write(boost::asio::buffer(message), yield[errorCode]);
auto operation = [&](auto&& token) { ws_.async_write(boost::asio::buffer(message), token); };
if (timeout) {
withTimeout(operation, yield[errorCode], *timeout);
} else {
operation(yield[errorCode]);
}
if (errorCode)
if (errorCode) {
errorCode = mapError(errorCode);
return RequestError{"Write error", errorCode};
}
return std::nullopt;
}
@@ -92,6 +117,31 @@ public:
return RequestError{"Close error", errorCode};
return std::nullopt;
}
private:
template <typename Operation>
static void
withTimeout(Operation&& operation, boost::asio::yield_context yield, std::chrono::steady_clock::duration timeout)
{
boost::asio::cancellation_signal cancellationSignal;
auto cyield = boost::asio::bind_cancellation_slot(cancellationSignal.slot(), yield);
boost::asio::steady_timer timer{boost::asio::get_associated_executor(cyield), timeout};
timer.async_wait([&cancellationSignal](boost::system::error_code errorCode) {
if (!errorCode)
cancellationSignal.emit(boost::asio::cancellation_type::terminal);
});
operation(cyield);
}
static boost::system::error_code
mapError(boost::system::error_code const ec)
{
if (ec == boost::system::errc::operation_canceled) {
return boost::system::errc::make_error_code(boost::system::errc::timed_out);
}
return ec;
}
};
using PlainWsConnection = WsConnectionImpl<boost::beast::websocket::stream<boost::beast::tcp_stream>>;

View File

@@ -2,6 +2,7 @@ add_library(clio_testing_common)
target_sources(
clio_testing_common PRIVATE util/StringUtils.cpp util/TestHttpServer.cpp util/TestWsServer.cpp util/TestObject.cpp
util/AssignRandomPort.cpp
)
include(deps/gtest)

View File

@@ -0,0 +1,45 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, 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 "util/AssignRandomPort.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <cstdint>
using tcp = boost::asio::ip::tcp;
namespace tests::util {
uint32_t
generateFreePort()
{
boost::asio::io_context io_context;
tcp::acceptor acceptor(io_context);
tcp::endpoint const endpoint(tcp::v4(), 0);
acceptor.open(endpoint.protocol());
acceptor.set_option(tcp::acceptor::reuse_address(true));
acceptor.bind(endpoint);
return acceptor.local_endpoint().port();
}
} // namespace tests::util

View File

@@ -0,0 +1,29 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, 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 <cstdint>
namespace tests::util {
uint32_t
generateFreePort();
} // namespace tests::util

View File

@@ -44,14 +44,14 @@ struct MockBackend : public BackendInterface {
}
MOCK_METHOD(
std::optional<ripple::LedgerInfo>,
std::optional<ripple::LedgerHeader>,
fetchLedgerBySequence,
(std::uint32_t const, boost::asio::yield_context),
(const, override)
);
MOCK_METHOD(
std::optional<ripple::LedgerInfo>,
std::optional<ripple::LedgerHeader>,
fetchLedgerByHash,
(ripple::uint256 const&, boost::asio::yield_context),
(const, override)
@@ -170,7 +170,7 @@ struct MockBackend : public BackendInterface {
MOCK_METHOD(std::optional<LedgerRange>, hardFetchLedgerRange, (boost::asio::yield_context), (const, override));
MOCK_METHOD(void, writeLedger, (ripple::LedgerInfo const&, std::string&&), (override));
MOCK_METHOD(void, writeLedger, (ripple::LedgerHeader const&, std::string&&), (override));
MOCK_METHOD(void, writeLedgerObject, (std::string&&, std::uint32_t const, std::string&&), (override));

View File

@@ -35,8 +35,8 @@ struct MockLedgerLoader {
MOCK_METHOD(
FormattedTransactionsData,
insertTransactions,
(ripple::LedgerInfo const&, GetLedgerResponseType& data),
(ripple::LedgerHeader const&, GetLedgerResponseType& data),
()
);
MOCK_METHOD(std::optional<ripple::LedgerInfo>, loadInitialLedger, (uint32_t sequence), ());
MOCK_METHOD(std::optional<ripple::LedgerHeader>, loadInitialLedger, (uint32_t sequence), ());
};

View File

@@ -28,7 +28,7 @@
struct MockLedgerPublisher {
MOCK_METHOD(bool, publish, (uint32_t, std::optional<uint32_t>), ());
MOCK_METHOD(void, publish, (ripple::LedgerInfo const&), ());
MOCK_METHOD(void, publish, (ripple::LedgerHeader const&), ());
MOCK_METHOD(std::uint32_t, lastPublishAgeSeconds, (), (const));
MOCK_METHOD(std::chrono::time_point<std::chrono::system_clock>, getLastPublish, (), (const));
MOCK_METHOD(std::uint32_t, lastCloseAgeSeconds, (), (const));

View File

@@ -40,7 +40,7 @@ struct MockLoadBalancer {
MOCK_METHOD(
std::optional<boost::json::object>,
forwardToRippled,
(boost::json::object const&, std::optional<std::string> const&, boost::asio::yield_context),
(boost::json::object const&, std::optional<std::string> const&, bool, boost::asio::yield_context),
(const)
);
};

View File

@@ -20,6 +20,7 @@
#include "data/BackendInterface.hpp"
#include "etl/ETLHelpers.hpp"
#include "etl/NetworkValidatedLedgersInterface.hpp"
#include "etl/Source.hpp"
#include "feed/SubscriptionManagerInterface.hpp"
#include "util/config/Config.hpp"
@@ -34,12 +35,13 @@
#include <org/xrpl/rpc/v1/get_ledger.pb.h>
#include <algorithm>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <string_view>
#include <utility>
#include <vector>
@@ -60,7 +62,7 @@ struct MockSource : etl::SourceBase {
MOCK_METHOD(
std::optional<boost::json::object>,
forwardToRippled,
(boost::json::object const&, std::optional<std::string> const&, boost::asio::yield_context),
(boost::json::object const&, std::optional<std::string> const&, std::string_view, boost::asio::yield_context),
(const, override)
);
};
@@ -129,10 +131,11 @@ public:
forwardToRippled(
boost::json::object const& request,
std::optional<std::string> const& forwardToRippledClientIp,
std::string_view xUserValue,
boost::asio::yield_context yield
) const override
{
return mock_->forwardToRippled(request, forwardToRippledClientIp, yield);
return mock_->forwardToRippled(request, forwardToRippledClientIp, xUserValue, yield);
}
};
@@ -155,29 +158,58 @@ class MockSourceFactoryImpl {
public:
MockSourceFactoryImpl(size_t numSources)
{
setSourcesNumber(numSources);
ON_CALL(*this, makeSource)
.WillByDefault([this](
util::Config const&,
boost::asio::io_context&,
std::shared_ptr<BackendInterface>,
std::shared_ptr<feed::SubscriptionManagerInterface>,
std::shared_ptr<etl::NetworkValidatedLedgersInterface>,
std::chrono::steady_clock::duration,
etl::SourceBase::OnConnectHook onConnect,
etl::SourceBase::OnDisconnectHook onDisconnect,
etl::SourceBase::OnLedgerClosedHook onLedgerClosed
) {
auto it = std::ranges::find_if(mockData_, [](auto const& d) { return not d.callbacks.has_value(); });
[&]() { ASSERT_NE(it, mockData_.end()) << "Make source called more than expected"; }();
it->callbacks =
MockSourceCallbacks{std::move(onDisconnect), std::move(onConnect), std::move(onLedgerClosed)};
return std::make_unique<MockSourceWrapper<MockType>>(it->source);
});
}
void
setSourcesNumber(size_t numSources)
{
mockData_.clear();
mockData_.reserve(numSources);
std::ranges::generate_n(std::back_inserter(mockData_), numSources, [] { return MockSourceData<MockType>{}; });
}
template <typename... Args>
etl::SourcePtr
makeSourceMock(
util::Config const&,
boost::asio::io_context&,
std::shared_ptr<BackendInterface>,
std::shared_ptr<feed::SubscriptionManagerInterface>,
std::shared_ptr<etl::NetworkValidatedLedgersInterface>,
etl::SourceBase::OnConnectHook onConnect,
etl::SourceBase::OnDisconnectHook onDisconnect,
etl::SourceBase::OnLedgerClosedHook onLedgerClosed
)
operator()(Args&&... args)
{
auto it = std::ranges::find_if(mockData_, [](auto const& d) { return not d.callbacks.has_value(); });
[&]() { ASSERT_NE(it, mockData_.end()) << "Make source called more than expected"; }();
it->callbacks = MockSourceCallbacks{std::move(onDisconnect), std::move(onConnect), std::move(onLedgerClosed)};
return std::make_unique<MockSourceWrapper<MockType>>(it->source);
return makeSource(std::forward<Args>(args)...);
}
MOCK_METHOD(
etl::SourcePtr,
makeSource,
(util::Config const&,
boost::asio::io_context&,
std::shared_ptr<BackendInterface>,
std::shared_ptr<feed::SubscriptionManagerInterface>,
std::shared_ptr<etl::NetworkValidatedLedgersInterface>,
std::chrono::steady_clock::duration,
etl::SourceBase::OnConnectHook,
etl::SourceBase::OnDisconnectHook,
etl::SourceBase::OnLedgerClosedHook)
);
MockType<MockSource>&
sourceAt(size_t index)
{
@@ -193,5 +225,5 @@ public:
}
};
using MockSourceFactory = MockSourceFactoryImpl<>;
using StrictMockSourceFactory = MockSourceFactoryImpl<testing::StrictMock>;
using MockSourceFactory = testing::NiceMock<MockSourceFactoryImpl<>>;
using StrictMockSourceFactory = testing::StrictMock<MockSourceFactoryImpl<testing::StrictMock>>;

View File

@@ -55,7 +55,7 @@ struct MockSubscriptionManager : feed::SubscriptionManagerInterface {
MOCK_METHOD(
void,
pubBookChanges,
(ripple::LedgerInfo const&, std::vector<data::TransactionAndMetadata> const&),
(ripple::LedgerHeader const&, std::vector<data::TransactionAndMetadata> const&),
(const, override)
);
@@ -65,7 +65,7 @@ struct MockSubscriptionManager : feed::SubscriptionManagerInterface {
MOCK_METHOD(void, unsubTransactions, (feed::SubscriberSharedPtr const&), (override));
MOCK_METHOD(void, pubTransaction, (data::TransactionAndMetadata const&, ripple::LedgerInfo const&), (override));
MOCK_METHOD(void, pubTransaction, (data::TransactionAndMetadata const&, ripple::LedgerHeader const&), (override));
MOCK_METHOD(void, subAccount, (ripple::AccountID const&, feed::SubscriberSharedPtr const&), (override));

View File

@@ -82,7 +82,7 @@ struct WithMockXrpLedgerAPIService : virtual ::testing::Test {
WithMockXrpLedgerAPIService(std::string serverAddress)
{
grpc::ServerBuilder builder;
builder.AddListeningPort(serverAddress, grpc::InsecureServerCredentials());
builder.AddListeningPort(serverAddress, grpc::InsecureServerCredentials(), &port_);
builder.RegisterService(&mockXrpLedgerAPIService);
server_ = builder.BuildAndStart();
serverThread_ = std::thread([this] { server_->Wait(); });
@@ -94,11 +94,17 @@ struct WithMockXrpLedgerAPIService : virtual ::testing::Test {
serverThread_.join();
}
int
getXRPLMockPort() const
{
return port_;
}
MockXrpLedgerAPIService mockXrpLedgerAPIService;
private:
std::unique_ptr<grpc::Server> server_;
std::thread serverThread_;
int port_{};
};
} // namespace tests::util

View File

@@ -0,0 +1,28 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, 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 <string>
namespace tests::util {
static auto const NameGenerator = [](auto const& info) { return info.param.testName; };
} // namespace tests::util

View File

@@ -46,9 +46,9 @@ binaryStringToUint256(std::string const& bin)
}
std::string
ledgerInfoToBinaryString(ripple::LedgerInfo const& info)
ledgerHeaderToBinaryString(ripple::LedgerHeader const& info)
{
auto const blob = rpc::ledgerInfoToBlob(info, true);
auto const blob = rpc::ledgerHeaderToBlob(info, true);
std::string strBlob;
for (auto c : blob)
strBlob += c;

View File

@@ -32,4 +32,4 @@ ripple::uint256
binaryStringToUint256(std::string const& bin);
std::string
ledgerInfoToBinaryString(ripple::LedgerInfo const& info);
ledgerHeaderToBinaryString(ripple::LedgerHeader const& info);

View File

@@ -105,9 +105,9 @@ doSession(
} // namespace
TestHttpServer::TestHttpServer(boost::asio::io_context& context, std::string host, int const port) : acceptor_(context)
TestHttpServer::TestHttpServer(boost::asio::io_context& context, std::string host) : acceptor_(context)
{
boost::asio::ip::tcp::endpoint const endpoint(boost::asio::ip::make_address(host), port);
boost::asio::ip::tcp::endpoint const endpoint(boost::asio::ip::make_address(host), 0);
acceptor_.open(endpoint.protocol());
acceptor_.set_option(asio::socket_base::reuse_address(true));
acceptor_.bind(endpoint);
@@ -134,3 +134,9 @@ TestHttpServer::handleRequest(TestHttpServer::RequestHandler handler, bool const
boost::asio::detached
);
}
std::string
TestHttpServer::port() const
{
return std::to_string(acceptor_.local_endpoint().port());
}

View File

@@ -41,9 +41,8 @@ public:
*
* @param context boost::asio::io_context to use for networking
* @param host host to bind to
* @param port port to bind to
*/
TestHttpServer(boost::asio::io_context& context, std::string host, int port);
TestHttpServer(boost::asio::io_context& context, std::string host);
/**
* @brief Start the server
@@ -56,6 +55,14 @@ public:
void
handleRequest(RequestHandler handler, bool allowToFail = false);
/**
* @brief Return the port HTTP server is connected to
*
* @return string port number
*/
std::string
port() const;
private:
boost::asio::ip::tcp::acceptor acceptor_;
};

View File

@@ -75,22 +75,22 @@ GetAccountKey(ripple::AccountID const& acc)
return ripple::keylet::account(acc).key;
}
ripple::LedgerInfo
CreateLedgerInfo(std::string_view ledgerHash, ripple::LedgerIndex seq, std::optional<uint32_t> age)
ripple::LedgerHeader
CreateLedgerHeader(std::string_view ledgerHash, ripple::LedgerIndex seq, std::optional<uint32_t> age)
{
using namespace std::chrono;
auto ledgerinfo = ripple::LedgerInfo();
ledgerinfo.hash = ripple::uint256{ledgerHash};
ledgerinfo.seq = seq;
auto ledgerHeader = ripple::LedgerHeader();
ledgerHeader.hash = ripple::uint256{ledgerHash};
ledgerHeader.seq = seq;
if (age) {
auto const now = duration_cast<seconds>(system_clock::now().time_since_epoch());
auto const closeTime = (now - seconds{age.value()}).count() - rippleEpochStart;
ledgerinfo.closeTime = ripple::NetClock::time_point{seconds{closeTime}};
ledgerHeader.closeTime = ripple::NetClock::time_point{seconds{closeTime}};
}
return ledgerinfo;
return ledgerHeader;
}
ripple::STObject

View File

@@ -60,10 +60,10 @@ GetAccountKey(std::string_view id);
GetAccountKey(ripple::AccountID const& acc);
/*
* Create a simple ledgerInfo object with only hash and seq
* Create a simple ledgerHeader object with only hash and seq
*/
[[nodiscard]] ripple::LedgerInfo
CreateLedgerInfo(std::string_view ledgerHash, ripple::LedgerIndex seq, std::optional<uint32_t> age = std::nullopt);
[[nodiscard]] ripple::LedgerHeader
CreateLedgerHeader(std::string_view ledgerHash, ripple::LedgerIndex seq, std::optional<uint32_t> age = std::nullopt);
/*
* Create a Legacy (pre XRPFees amendment) FeeSetting ledger object

View File

@@ -32,20 +32,36 @@
#include <boost/beast/core/flat_buffer.hpp>
#include <boost/beast/core/role.hpp>
#include <boost/beast/core/tcp_stream.hpp>
#include <boost/beast/http/field.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/beast/websocket/error.hpp>
#include <boost/beast/websocket/rfc6455.hpp>
#include <boost/beast/websocket/stream.hpp>
#include <boost/beast/websocket/stream_base.hpp>
#include <gtest/gtest.h>
#include <algorithm>
#include <expected>
#include <iterator>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace asio = boost::asio;
namespace websocket = boost::beast::websocket;
TestWsConnection::TestWsConnection(websocket::stream<boost::beast::tcp_stream> wsStream) : ws_(std::move(wsStream))
TestWsConnection::TestWsConnection(
websocket::stream<boost::beast::tcp_stream> wsStream,
std::vector<util::requests::HttpHeader> headers
)
: ws_(std::move(wsStream)), headers_(std::move(headers))
{
}
TestWsConnection::TestWsConnection(TestWsConnection&& other)
: ws_(std::move(other.ws_)), headers_(std::move(other.headers_))
{
}
@@ -83,14 +99,26 @@ TestWsConnection::close(boost::asio::yield_context yield)
return std::nullopt;
}
TestWsServer::TestWsServer(asio::io_context& context, std::string const& host, int port) : acceptor_(context)
std::vector<util::requests::HttpHeader> const&
TestWsConnection::headers() const
{
auto endpoint = asio::ip::tcp::endpoint(boost::asio::ip::make_address(host), port);
return headers_;
}
TestWsServer::TestWsServer(asio::io_context& context, std::string const& host) : acceptor_(context)
{
auto endpoint = asio::ip::tcp::endpoint(boost::asio::ip::make_address(host), 0);
acceptor_.open(endpoint.protocol());
acceptor_.set_option(asio::socket_base::reuse_address(true));
acceptor_.bind(endpoint);
}
std::string
TestWsServer::port() const
{
return std::to_string(this->acceptor_.local_endpoint().port());
}
std::expected<TestWsConnection, util::requests::RequestError>
TestWsServer::acceptConnection(asio::yield_context yield)
{
@@ -102,13 +130,28 @@ TestWsServer::acceptConnection(asio::yield_context yield)
if (errorCode)
return std::unexpected{util::requests::RequestError{"Accept error", errorCode}};
boost::beast::flat_buffer buffer;
boost::beast::http::request<boost::beast::http::string_body> request;
boost::beast::http::async_read(socket, buffer, request, yield[errorCode]);
if (errorCode)
return std::unexpected{util::requests::RequestError{"Read error", errorCode}};
std::vector<util::requests::HttpHeader> headers;
std::transform(request.begin(), request.end(), std::back_inserter(headers), [](auto const& header) {
if (header.name() == boost::beast::http::field::unknown)
return util::requests::HttpHeader{header.name_string(), header.value()};
return util::requests::HttpHeader{header.name(), header.value()};
});
if (not boost::beast::websocket::is_upgrade(request))
return std::unexpected{util::requests::RequestError{"Not a websocket request"}};
boost::beast::websocket::stream<boost::beast::tcp_stream> ws(std::move(socket));
ws.set_option(websocket::stream_base::timeout::suggested(boost::beast::role_type::server));
ws.async_accept(yield[errorCode]);
ws.async_accept(request, yield[errorCode]);
if (errorCode)
return std::unexpected{util::requests::RequestError{"Handshake error", errorCode}};
return TestWsConnection(std::move(ws));
return TestWsConnection(std::move(ws), std::move(headers));
}
void

View File

@@ -29,17 +29,26 @@
#include <expected>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
class TestWsConnection {
boost::beast::websocket::stream<boost::beast::tcp_stream> ws_;
std::vector<util::requests::HttpHeader> headers_;
public:
using SendCallback = std::function<void()>;
using ReceiveCallback = std::function<void(std::string)>;
TestWsConnection(boost::beast::websocket::stream<boost::beast::tcp_stream> wsStream);
TestWsConnection(
boost::beast::websocket::stream<boost::beast::tcp_stream> wsStream,
std::vector<util::requests::HttpHeader> headers
);
TestWsConnection(TestWsConnection&& other);
// returns error message if error occurs
std::optional<std::string>
@@ -51,13 +60,20 @@ public:
std::optional<std::string>
close(boost::asio::yield_context yield);
std::vector<util::requests::HttpHeader> const&
headers() const;
};
using TestWsConnectionPtr = std::unique_ptr<TestWsConnection>;
class TestWsServer {
boost::asio::ip::tcp::acceptor acceptor_;
public:
TestWsServer(boost::asio::io_context& context, std::string const& host, int port);
TestWsServer(boost::asio::io_context& context, std::string const& host);
std::string
port() const;
std::expected<TestWsConnection, util::requests::RequestError>
acceptConnection(boost::asio::yield_context yield);

View File

@@ -122,7 +122,7 @@ TEST_F(BackendCassandraTest, Basic)
"CE5AA29652EFFD80AC59CD91416E4E13DBBE";
std::string rawHeaderBlob = hexStringToBinaryString(rawHeader);
ripple::LedgerInfo const lgrInfo = util::deserializeHeader(ripple::makeSlice(rawHeaderBlob));
ripple::LedgerHeader const lgrInfo = util::deserializeHeader(ripple::makeSlice(rawHeaderBlob));
backend->writeLedger(lgrInfo, std::move(rawHeaderBlob));
backend->writeSuccessor(uint256ToString(data::firstKey), lgrInfo.seq, uint256ToString(data::lastKey));
@@ -142,7 +142,7 @@ TEST_F(BackendCassandraTest, Basic)
auto retLgr = backend->fetchLedgerBySequence(lgrInfo.seq, yield);
ASSERT_TRUE(retLgr.has_value());
EXPECT_EQ(retLgr->seq, lgrInfo.seq);
EXPECT_EQ(ledgerInfoToBlob(lgrInfo), ledgerInfoToBlob(*retLgr));
EXPECT_EQ(ledgerHeaderToBlob(lgrInfo), ledgerHeaderToBlob(*retLgr));
}
EXPECT_FALSE(backend->fetchLedgerBySequence(lgrInfo.seq + 1, yield).has_value());
@@ -154,7 +154,7 @@ TEST_F(BackendCassandraTest, Basic)
lgrInfoNext.hash++;
lgrInfoNext.accountHash = ~lgrInfo.accountHash;
{
std::string infoBlob = ledgerInfoToBinaryString(lgrInfoNext);
std::string infoBlob = ledgerHeaderToBinaryString(lgrInfoNext);
backend->writeLedger(lgrInfoNext, std::move(infoBlob));
ASSERT_TRUE(backend->finishWrites(lgrInfoNext.seq));
@@ -173,11 +173,11 @@ TEST_F(BackendCassandraTest, Basic)
auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield);
EXPECT_TRUE(retLgr.has_value());
EXPECT_EQ(retLgr->seq, lgrInfoNext.seq);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext));
EXPECT_NE(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoOld));
EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
EXPECT_NE(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoOld));
retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 1, yield);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoOld));
EXPECT_NE(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext));
EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoOld));
EXPECT_NE(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield);
EXPECT_FALSE(backend->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield).has_value());
@@ -404,7 +404,7 @@ TEST_F(BackendCassandraTest, Basic)
std::vector<NFTsData> nftData;
nftData.push_back(*parsedNFT);
backend->writeLedger(lgrInfoNext, ledgerInfoToBinaryString(lgrInfoNext));
backend->writeLedger(lgrInfoNext, ledgerHeaderToBinaryString(lgrInfoNext));
backend->writeTransaction(
std::string{hashBlob},
lgrInfoNext.seq,
@@ -430,7 +430,7 @@ TEST_F(BackendCassandraTest, Basic)
EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq);
auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield);
EXPECT_TRUE(retLgr);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext));
EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
auto allTransactions = backend->fetchAllTransactionsInLedger(lgrInfoNext.seq, yield);
ASSERT_EQ(allTransactions.size(), 1);
EXPECT_STREQ(
@@ -477,7 +477,7 @@ TEST_F(BackendCassandraTest, Basic)
lgrInfoNext.txHash = lgrInfoNext.txHash ^ lgrInfoNext.accountHash;
lgrInfoNext.accountHash = ~(lgrInfoNext.accountHash ^ lgrInfoNext.txHash);
backend->writeLedger(lgrInfoNext, ledgerInfoToBinaryString(lgrInfoNext));
backend->writeLedger(lgrInfoNext, ledgerHeaderToBinaryString(lgrInfoNext));
std::shuffle(accountBlob.begin(), accountBlob.end(), randomEngine);
backend->writeLedgerObject(std::string{accountIndexBlob}, lgrInfoNext.seq, std::string{accountBlob});
@@ -490,7 +490,7 @@ TEST_F(BackendCassandraTest, Basic)
EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq);
auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield);
EXPECT_TRUE(retLgr);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext));
EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
auto txns = backend->fetchAllTransactionsInLedger(lgrInfoNext.seq, yield);
EXPECT_EQ(txns.size(), 0);
@@ -515,7 +515,7 @@ TEST_F(BackendCassandraTest, Basic)
lgrInfoNext.txHash = lgrInfoNext.txHash ^ lgrInfoNext.accountHash;
lgrInfoNext.accountHash = ~(lgrInfoNext.accountHash ^ lgrInfoNext.txHash);
backend->writeLedger(lgrInfoNext, ledgerInfoToBinaryString(lgrInfoNext));
backend->writeLedger(lgrInfoNext, ledgerHeaderToBinaryString(lgrInfoNext));
backend->writeLedgerObject(std::string{accountIndexBlob}, lgrInfoNext.seq, std::string{});
backend->writeSuccessor(uint256ToString(data::firstKey), lgrInfoNext.seq, uint256ToString(data::lastKey));
@@ -528,7 +528,7 @@ TEST_F(BackendCassandraTest, Basic)
EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq);
auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield);
EXPECT_TRUE(retLgr);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext));
EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
auto txns = backend->fetchAllTransactionsInLedger(lgrInfoNext.seq, yield);
EXPECT_EQ(txns.size(), 0);
@@ -615,7 +615,7 @@ TEST_F(BackendCassandraTest, Basic)
auto writeLedger = [&](auto lgrInfo, auto txns, auto objs, auto accountTx, auto state) {
backend->startWrites();
backend->writeLedger(lgrInfo, ledgerInfoToBinaryString(lgrInfo));
backend->writeLedger(lgrInfo, ledgerHeaderToBinaryString(lgrInfo));
for (auto [hash, txn, meta] : txns) {
backend->writeTransaction(
std::move(hash),
@@ -664,7 +664,7 @@ TEST_F(BackendCassandraTest, Basic)
EXPECT_GE(rng->maxSequence, seq);
auto retLgr = backend->fetchLedgerBySequence(seq, yield);
EXPECT_TRUE(retLgr);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfo));
EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfo));
auto retTxns = backend->fetchAllTransactionsInLedger(seq, yield);
for (auto [hash, txn, meta] : txns) {
bool found = false;
@@ -758,7 +758,7 @@ TEST_F(BackendCassandraTest, Basic)
std::map<uint32_t, std::vector<std::tuple<std::string, std::string, std::string>>> allTxns;
std::unordered_map<std::string, std::pair<std::string, std::string>> allTxnsMap;
std::map<uint32_t, std::map<ripple::AccountID, std::vector<std::string>>> allAccountTx;
std::map<uint32_t, ripple::LedgerInfo> lgrInfos;
std::map<uint32_t, ripple::LedgerHeader> lgrInfos;
for (size_t i = 0; i < 10; ++i) {
lgrInfoNext = generateNextLedger(lgrInfoNext);
auto objs = generateObjects(25, lgrInfoNext.seq);
@@ -893,7 +893,7 @@ TEST_F(BackendCassandraTest, CacheIntegration)
std::string rawHeaderBlob = hexStringToBinaryString(rawHeader);
std::string accountBlob = hexStringToBinaryString(accountHex);
std::string const accountIndexBlob = hexStringToBinaryString(accountIndexHex);
ripple::LedgerInfo const lgrInfo = util::deserializeHeader(ripple::makeSlice(rawHeaderBlob));
ripple::LedgerHeader const lgrInfo = util::deserializeHeader(ripple::makeSlice(rawHeaderBlob));
backend->startWrites();
backend->writeLedger(lgrInfo, std::move(rawHeaderBlob));
@@ -914,7 +914,7 @@ TEST_F(BackendCassandraTest, CacheIntegration)
auto retLgr = backend->fetchLedgerBySequence(lgrInfo.seq, yield);
ASSERT_TRUE(retLgr.has_value());
EXPECT_EQ(retLgr->seq, lgrInfo.seq);
EXPECT_EQ(ledgerInfoToBlob(lgrInfo), ledgerInfoToBlob(*retLgr));
EXPECT_EQ(ledgerHeaderToBlob(lgrInfo), ledgerHeaderToBlob(*retLgr));
}
EXPECT_FALSE(backend->fetchLedgerBySequence(lgrInfo.seq + 1, yield).has_value());
auto lgrInfoOld = lgrInfo;
@@ -925,7 +925,7 @@ TEST_F(BackendCassandraTest, CacheIntegration)
lgrInfoNext.hash++;
lgrInfoNext.accountHash = ~lgrInfo.accountHash;
{
std::string infoBlob = ledgerInfoToBinaryString(lgrInfoNext);
std::string infoBlob = ledgerHeaderToBinaryString(lgrInfoNext);
backend->startWrites();
backend->writeLedger(lgrInfoNext, std::move(infoBlob));
@@ -945,12 +945,12 @@ TEST_F(BackendCassandraTest, CacheIntegration)
auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield);
EXPECT_TRUE(retLgr.has_value());
EXPECT_EQ(retLgr->seq, lgrInfoNext.seq);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext));
EXPECT_NE(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoOld));
EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
EXPECT_NE(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoOld));
retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 1, yield);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoOld));
EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoOld));
EXPECT_NE(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext));
EXPECT_NE(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield);
EXPECT_FALSE(backend->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield).has_value());
@@ -967,7 +967,7 @@ TEST_F(BackendCassandraTest, CacheIntegration)
lgrInfoNext.parentHash = lgrInfoNext.hash;
lgrInfoNext.hash++;
backend->writeLedger(lgrInfoNext, ledgerInfoToBinaryString(lgrInfoNext));
backend->writeLedger(lgrInfoNext, ledgerHeaderToBinaryString(lgrInfoNext));
backend->writeLedgerObject(std::string{accountIndexBlob}, lgrInfoNext.seq, std::string{accountBlob});
auto key = ripple::uint256::fromVoidChecked(accountIndexBlob);
backend->cache().update({{*key, {accountBlob.begin(), accountBlob.end()}}}, lgrInfoNext.seq);
@@ -983,7 +983,7 @@ TEST_F(BackendCassandraTest, CacheIntegration)
EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq);
auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield);
EXPECT_TRUE(retLgr);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfoNext));
EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfoNext));
ripple::uint256 key256;
EXPECT_TRUE(key256.parseHex(accountIndexHex));
auto obj = backend->fetchLedgerObject(key256, lgrInfoNext.seq, yield);
@@ -1004,7 +1004,7 @@ TEST_F(BackendCassandraTest, CacheIntegration)
lgrInfoNext.txHash = lgrInfoNext.txHash ^ lgrInfoNext.accountHash;
lgrInfoNext.accountHash = ~(lgrInfoNext.accountHash ^ lgrInfoNext.txHash);
backend->writeLedger(lgrInfoNext, ledgerInfoToBinaryString(lgrInfoNext));
backend->writeLedger(lgrInfoNext, ledgerHeaderToBinaryString(lgrInfoNext));
std::shuffle(accountBlob.begin(), accountBlob.end(), randomEngine);
auto key = ripple::uint256::fromVoidChecked(accountIndexBlob);
backend->cache().update({{*key, {accountBlob.begin(), accountBlob.end()}}}, lgrInfoNext.seq);
@@ -1042,7 +1042,7 @@ TEST_F(BackendCassandraTest, CacheIntegration)
lgrInfoNext.txHash = lgrInfoNext.txHash ^ lgrInfoNext.accountHash;
lgrInfoNext.accountHash = ~(lgrInfoNext.accountHash ^ lgrInfoNext.txHash);
backend->writeLedger(lgrInfoNext, ledgerInfoToBinaryString(lgrInfoNext));
backend->writeLedger(lgrInfoNext, ledgerHeaderToBinaryString(lgrInfoNext));
auto key = ripple::uint256::fromVoidChecked(accountIndexBlob);
backend->cache().update({{*key, {}}}, lgrInfoNext.seq);
backend->writeLedgerObject(std::string{accountIndexBlob}, lgrInfoNext.seq, std::string{});
@@ -1102,7 +1102,7 @@ TEST_F(BackendCassandraTest, CacheIntegration)
auto writeLedger = [&](auto lgrInfo, auto objs, auto state) {
backend->startWrites();
backend->writeLedger(lgrInfo, std::move(ledgerInfoToBinaryString(lgrInfo)));
backend->writeLedger(lgrInfo, std::move(ledgerHeaderToBinaryString(lgrInfo)));
std::vector<data::LedgerObject> cacheUpdates;
for (auto [key, obj] : objs) {
backend->writeLedgerObject(std::string{key}, lgrInfo.seq, std::string{obj});
@@ -1145,10 +1145,10 @@ TEST_F(BackendCassandraTest, CacheIntegration)
EXPECT_GE(rng->maxSequence, seq);
auto retLgr = backend->fetchLedgerBySequence(seq, yield);
EXPECT_TRUE(retLgr);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfo));
EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfo));
retLgr = backend->fetchLedgerByHash(lgrInfo.hash, yield);
EXPECT_TRUE(retLgr);
EXPECT_EQ(ledgerInfoToBlob(*retLgr), ledgerInfoToBlob(lgrInfo))
EXPECT_EQ(ledgerHeaderToBlob(*retLgr), ledgerHeaderToBlob(lgrInfo))
<< "retLgr seq:" << retLgr->seq << "; lgrInfo seq:" << lgrInfo.seq << "; retLgr hash:" << retLgr->hash
<< "; lgrInfo hash:" << lgrInfo.hash << "; retLgr parentHash:" << retLgr->parentHash
<< "; lgr Info parentHash:" << lgrInfo.parentHash;
@@ -1203,7 +1203,7 @@ TEST_F(BackendCassandraTest, CacheIntegration)
};
std::map<uint32_t, std::vector<std::pair<std::string, std::string>>> state;
std::map<uint32_t, ripple::LedgerInfo> lgrInfos;
std::map<uint32_t, ripple::LedgerHeader> lgrInfos;
for (size_t i = 0; i < 10; ++i) {
lgrInfoNext = generateNextLedger(lgrInfoNext);
auto objs = generateObjects(25, lgrInfoNext.seq);

View File

@@ -67,6 +67,7 @@ target_sources(
rpc/handlers/BookOffersTests.cpp
rpc/handlers/DefaultProcessorTests.cpp
rpc/handlers/DepositAuthorizedTests.cpp
rpc/handlers/FeatureTests.cpp
rpc/handlers/GatewayBalancesTests.cpp
rpc/handlers/GetAggregatePriceTests.cpp
rpc/handlers/LedgerDataTests.cpp

View File

@@ -195,6 +195,13 @@ TEST_F(ConfigTest, Array)
ASSERT_TRUE(exp.empty());
}
TEST_F(ConfigTest, toMilliseconds)
{
EXPECT_EQ(Config::toMilliseconds(0.0f).count(), 0);
EXPECT_EQ(Config::toMilliseconds(0.123f).count(), 123);
EXPECT_EQ(Config::toMilliseconds(3.45f).count(), 3450);
}
/**
* @brief Simple custom data type with json parsing support
*/

View File

@@ -27,7 +27,9 @@
#include <boost/json/serialize.hpp>
#include <gtest/gtest.h>
#include <algorithm>
#include <chrono>
#include <memory>
#include <optional>
#include <string>
#include <utility>
@@ -35,14 +37,19 @@
using namespace etl::impl;
struct ForwardingSourceTests : SyncAsioContextTest {
TestWsServer server_{ctx, "0.0.0.0", 11114};
ForwardingSource forwardingSource{"127.0.0.1", "11114", std::chrono::milliseconds{1}};
TestWsServer server_{ctx, "0.0.0.0"};
ForwardingSource forwardingSource{
"127.0.0.1",
server_.port(),
std::chrono::milliseconds{1},
std::chrono::milliseconds{1}
};
};
TEST_F(ForwardingSourceTests, ConnectionFailed)
{
runSpawn([&](boost::asio::yield_context yield) {
auto result = forwardingSource.forwardToRippled({}, {}, yield);
auto result = forwardingSource.forwardToRippled({}, {}, {}, yield);
EXPECT_FALSE(result);
});
}
@@ -59,11 +66,34 @@ struct ForwardingSourceOperationsTests : ForwardingSourceTests {
[&]() { ASSERT_FALSE(failedConnection); }();
auto connection = server_.acceptConnection(yield);
[&]() { ASSERT_TRUE(connection); }();
[&]() { ASSERT_TRUE(connection) << connection.error().message(); }();
return std::move(connection).value();
}
};
TEST_F(ForwardingSourceOperationsTests, XUserHeader)
{
std::string const xUserValue = "some_user";
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
auto connection = serverConnection(yield);
auto headers = connection.headers();
ASSERT_FALSE(headers.empty());
auto it = std::find_if(headers.begin(), headers.end(), [](auto const& header) {
return std::holds_alternative<std::string>(header.name) && std::get<std::string>(header.name) == "X-User";
});
ASSERT_FALSE(it == headers.end());
EXPECT_EQ(std::get<std::string>(it->name), "X-User");
EXPECT_EQ(it->value, xUserValue);
connection.close(yield);
});
runSpawn([&](boost::asio::yield_context yield) {
auto result =
forwardingSource.forwardToRippled(boost::json::parse(message_).as_object(), {}, xUserValue, yield);
EXPECT_FALSE(result);
});
}
TEST_F(ForwardingSourceOperationsTests, ReadFailed)
{
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
@@ -72,7 +102,20 @@ TEST_F(ForwardingSourceOperationsTests, ReadFailed)
});
runSpawn([&](boost::asio::yield_context yield) {
auto result = forwardingSource.forwardToRippled(boost::json::parse(message_).as_object(), {}, yield);
auto result = forwardingSource.forwardToRippled(boost::json::parse(message_).as_object(), {}, {}, yield);
EXPECT_FALSE(result);
});
}
TEST_F(ForwardingSourceOperationsTests, ReadTimeout)
{
TestWsConnectionPtr connection;
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
connection = std::make_unique<TestWsConnection>(serverConnection(yield));
});
runSpawn([&](boost::asio::yield_context yield) {
auto result = forwardingSource.forwardToRippled(boost::json::parse(message_).as_object(), {}, {}, yield);
EXPECT_FALSE(result);
});
}
@@ -93,7 +136,7 @@ TEST_F(ForwardingSourceOperationsTests, ParseFailed)
});
runSpawn([&](boost::asio::yield_context yield) {
auto result = forwardingSource.forwardToRippled(boost::json::parse(message_).as_object(), {}, yield);
auto result = forwardingSource.forwardToRippled(boost::json::parse(message_).as_object(), {}, {}, yield);
EXPECT_FALSE(result);
});
}
@@ -115,7 +158,7 @@ TEST_F(ForwardingSourceOperationsTests, GotNotAnObject)
});
runSpawn([&](boost::asio::yield_context yield) {
auto result = forwardingSource.forwardToRippled(boost::json::parse(message_).as_object(), {}, yield);
auto result = forwardingSource.forwardToRippled(boost::json::parse(message_).as_object(), {}, {}, yield);
EXPECT_FALSE(result);
});
}
@@ -134,7 +177,7 @@ TEST_F(ForwardingSourceOperationsTests, Success)
});
runSpawn([&](boost::asio::yield_context yield) {
auto result = forwardingSource.forwardToRippled(boost::json::parse(message_).as_object(), "some_ip", yield);
auto result = forwardingSource.forwardToRippled(boost::json::parse(message_).as_object(), "some_ip", {}, yield);
[&]() { ASSERT_TRUE(result); }();
auto expectedReply = reply_;
expectedReply["forwarded"] = true;

View File

@@ -42,9 +42,9 @@ using namespace etl::impl;
struct GrpcSourceTests : NoLoggerFixture, util::prometheus::WithPrometheus, tests::util::WithMockXrpLedgerAPIService {
GrpcSourceTests()
: WithMockXrpLedgerAPIService("localhost:55051")
: WithMockXrpLedgerAPIService("localhost:0")
, mockBackend_(std::make_shared<testing::StrictMock<MockBackend>>(util::Config{}))
, grpcSource_("127.0.0.1", "55051", mockBackend_)
, grpcSource_("127.0.0.1", std::to_string(getXRPLMockPort()), mockBackend_)
{
}

View File

@@ -68,13 +68,13 @@ struct ETLLedgerPublisherTest : util::prometheus::WithPrometheus, MockBackendTes
StrictMockSubscriptionManagerSharedPtr mockSubscriptionManagerPtr;
};
TEST_F(ETLLedgerPublisherTest, PublishLedgerInfoIsWritingFalse)
TEST_F(ETLLedgerPublisherTest, PublishLedgerHeaderIsWritingFalse)
{
SystemState dummyState;
dummyState.isWriting = false;
auto const dummyLedgerInfo = CreateLedgerInfo(LEDGERHASH, SEQ, AGE);
auto const dummyLedgerHeader = CreateLedgerHeader(LEDGERHASH, SEQ, AGE);
impl::LedgerPublisher publisher(ctx, backend, mockCache, mockSubscriptionManagerPtr, dummyState);
publisher.publish(dummyLedgerInfo);
publisher.publish(dummyLedgerHeader);
EXPECT_CALL(*backend, fetchLedgerDiff(SEQ, _)).WillOnce(Return(std::vector<LedgerObject>{}));
@@ -90,13 +90,13 @@ TEST_F(ETLLedgerPublisherTest, PublishLedgerInfoIsWritingFalse)
EXPECT_EQ(backend->fetchLedgerRange().value().maxSequence, SEQ);
}
TEST_F(ETLLedgerPublisherTest, PublishLedgerInfoIsWritingTrue)
TEST_F(ETLLedgerPublisherTest, PublishLedgerHeaderIsWritingTrue)
{
SystemState dummyState;
dummyState.isWriting = true;
auto const dummyLedgerInfo = CreateLedgerInfo(LEDGERHASH, SEQ, AGE);
auto const dummyLedgerHeader = CreateLedgerHeader(LEDGERHASH, SEQ, AGE);
impl::LedgerPublisher publisher(ctx, backend, mockCache, mockSubscriptionManagerPtr, dummyState);
publisher.publish(dummyLedgerInfo);
publisher.publish(dummyLedgerHeader);
// setLastPublishedSequence not in strand, should verify before run
EXPECT_TRUE(publisher.getLastPublishedSequence());
@@ -106,16 +106,16 @@ TEST_F(ETLLedgerPublisherTest, PublishLedgerInfoIsWritingTrue)
EXPECT_FALSE(backend->fetchLedgerRange());
}
TEST_F(ETLLedgerPublisherTest, PublishLedgerInfoInRange)
TEST_F(ETLLedgerPublisherTest, PublishLedgerHeaderInRange)
{
SystemState dummyState;
dummyState.isWriting = true;
auto const dummyLedgerInfo = CreateLedgerInfo(LEDGERHASH, SEQ, 0); // age is 0
auto const dummyLedgerHeader = CreateLedgerHeader(LEDGERHASH, SEQ, 0); // age is 0
impl::LedgerPublisher publisher(ctx, backend, mockCache, mockSubscriptionManagerPtr, dummyState);
backend->setRange(SEQ - 1, SEQ);
publisher.publish(dummyLedgerInfo);
publisher.publish(dummyLedgerHeader);
// mock fetch fee
EXPECT_CALL(*backend, doFetchLedgerObject(ripple::keylet::fees().key, SEQ, _))
@@ -143,20 +143,20 @@ TEST_F(ETLLedgerPublisherTest, PublishLedgerInfoInRange)
EXPECT_TRUE(publisher.lastPublishAgeSeconds() <= 1);
}
TEST_F(ETLLedgerPublisherTest, PublishLedgerInfoCloseTimeGreaterThanNow)
TEST_F(ETLLedgerPublisherTest, PublishLedgerHeaderCloseTimeGreaterThanNow)
{
SystemState dummyState;
dummyState.isWriting = true;
ripple::LedgerInfo dummyLedgerInfo = CreateLedgerInfo(LEDGERHASH, SEQ, 0);
ripple::LedgerHeader dummyLedgerHeader = CreateLedgerHeader(LEDGERHASH, SEQ, 0);
auto const nowPlus10 = system_clock::now() + seconds(10);
auto const closeTime = duration_cast<seconds>(nowPlus10.time_since_epoch()).count() - rippleEpochStart;
dummyLedgerInfo.closeTime = ripple::NetClock::time_point{seconds{closeTime}};
dummyLedgerHeader.closeTime = ripple::NetClock::time_point{seconds{closeTime}};
backend->setRange(SEQ - 1, SEQ);
impl::LedgerPublisher publisher(ctx, backend, mockCache, mockSubscriptionManagerPtr, dummyState);
publisher.publish(dummyLedgerInfo);
publisher.publish(dummyLedgerHeader);
// mock fetch fee
EXPECT_CALL(*backend, doFetchLedgerObject(ripple::keylet::fees().key, SEQ, _))
@@ -216,8 +216,8 @@ TEST_F(ETLLedgerPublisherTest, PublishLedgerSeqStopIsFalse)
LedgerRange const range{.minSequence = SEQ, .maxSequence = SEQ};
EXPECT_CALL(*backend, hardFetchLedgerRange).WillOnce(Return(range));
auto const dummyLedgerInfo = CreateLedgerInfo(LEDGERHASH, SEQ, AGE);
EXPECT_CALL(*backend, fetchLedgerBySequence(SEQ, _)).WillOnce(Return(dummyLedgerInfo));
auto const dummyLedgerHeader = CreateLedgerHeader(LEDGERHASH, SEQ, AGE);
EXPECT_CALL(*backend, fetchLedgerBySequence(SEQ, _)).WillOnce(Return(dummyLedgerHeader));
EXPECT_CALL(*backend, fetchLedgerDiff(SEQ, _)).WillOnce(Return(std::vector<LedgerObject>{}));
EXPECT_CALL(mockCache, updateImp);
@@ -231,11 +231,11 @@ TEST_F(ETLLedgerPublisherTest, PublishMultipleTxInOrder)
SystemState dummyState;
dummyState.isWriting = true;
auto const dummyLedgerInfo = CreateLedgerInfo(LEDGERHASH, SEQ, 0); // age is 0
auto const dummyLedgerHeader = CreateLedgerHeader(LEDGERHASH, SEQ, 0); // age is 0
impl::LedgerPublisher publisher(ctx, backend, mockCache, mockSubscriptionManagerPtr, dummyState);
backend->setRange(SEQ - 1, SEQ);
publisher.publish(dummyLedgerInfo);
publisher.publish(dummyLedgerHeader);
// mock fetch fee
EXPECT_CALL(*backend, doFetchLedgerObject(ripple::keylet::fees().key, SEQ, _))

View File

@@ -66,15 +66,14 @@ struct LoadBalancerConstructorTests : util::prometheus::WithPrometheus, MockBack
backend,
subscriptionManager_,
networkManager_,
[this](auto&&... args) -> SourcePtr {
return sourceFactory_.makeSourceMock(std::forward<decltype(args)>(args)...);
}
[this](auto&&... args) -> SourcePtr { return sourceFactory_(std::forward<decltype(args)>(args)...); }
);
}
};
TEST_F(LoadBalancerConstructorTests, construct)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
@@ -82,27 +81,68 @@ TEST_F(LoadBalancerConstructorTests, construct)
makeLoadBalancer();
}
TEST_F(LoadBalancerConstructorTests, fetchETLState_Source0Fails)
TEST_F(LoadBalancerConstructorTests, forwardingTimeoutPassedToSourceFactory)
{
auto const forwardingTimeout = 10;
configJson_.as_object()["forwarding"] = boost::json::object{{"timeout", float{forwardingTimeout}}};
EXPECT_CALL(
sourceFactory_,
makeSource(
testing::_,
testing::_,
testing::_,
testing::_,
testing::_,
std::chrono::steady_clock::duration{std::chrono::seconds{forwardingTimeout}},
testing::_,
testing::_,
testing::_
)
)
.Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(1), run);
makeLoadBalancer();
}
TEST_F(LoadBalancerConstructorTests, fetchETLState_AllSourcesFail)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(std::nullopt));
EXPECT_CALL(sourceFactory_.sourceAt(0), toString);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(std::nullopt));
EXPECT_THROW({ makeLoadBalancer(); }, std::logic_error);
}
TEST_F(LoadBalancerConstructorTests, fetchETLState_Source0ReturnsError)
TEST_F(LoadBalancerConstructorTests, fetchETLState_AllSourcesReturnError)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled)
.WillOnce(Return(boost::json::object{{"error", "some error"}}));
EXPECT_CALL(sourceFactory_.sourceAt(0), toString);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled)
.WillOnce(Return(boost::json::object{{"error", "some error"}}));
EXPECT_THROW({ makeLoadBalancer(); }, std::logic_error);
}
TEST_F(LoadBalancerConstructorTests, fetchETLState_Source1Fails)
TEST_F(LoadBalancerConstructorTests, fetchETLState_Source1Fails0OK)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(std::nullopt));
EXPECT_CALL(sourceFactory_.sourceAt(1), toString);
EXPECT_THROW({ makeLoadBalancer(); }, std::logic_error);
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), run);
makeLoadBalancer();
}
TEST_F(LoadBalancerConstructorTests, fetchETLState_Source0Fails1OK)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(std::nullopt));
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), run);
makeLoadBalancer();
}
TEST_F(LoadBalancerConstructorTests, fetchETLState_DifferentNetworkID)
@@ -110,17 +150,18 @@ TEST_F(LoadBalancerConstructorTests, fetchETLState_DifferentNetworkID)
auto const source1Json = boost::json::parse(R"({"result": {"info": {"network_id": 0}}})");
auto const source2Json = boost::json::parse(R"({"result": {"info": {"network_id": 1}}})");
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(source1Json.as_object()));
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(source2Json.as_object()));
EXPECT_THROW({ makeLoadBalancer(); }, std::logic_error);
}
TEST_F(LoadBalancerConstructorTests, fetchETLState_Source1FailsButAllowNoEtlIsTrue)
TEST_F(LoadBalancerConstructorTests, fetchETLState_AllSourcesFailButAllowNoEtlIsTrue)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(std::nullopt));
EXPECT_CALL(sourceFactory_.sourceAt(1), toString);
EXPECT_CALL(sourceFactory_.sourceAt(1), run);
configJson_.as_object()["allow_no_etl"] = true;
@@ -131,6 +172,7 @@ TEST_F(LoadBalancerConstructorTests, fetchETLState_DifferentNetworkIDButAllowNoE
{
auto const source1Json = boost::json::parse(R"({"result": {"info": {"network_id": 0}}})");
auto const source2Json = boost::json::parse(R"({"result": {"info": {"network_id": 1}}})");
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(source1Json.as_object()));
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(source2Json.as_object()));
@@ -152,6 +194,7 @@ TEST_F(LoadBalancerConstructorDeathTest, numMarkersSpecifiedInConfigIsInvalid)
struct LoadBalancerOnConnectHookTests : LoadBalancerConstructorTests {
LoadBalancerOnConnectHookTests()
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
@@ -280,8 +323,9 @@ TEST_F(LoadBalancerOnConnectHookTests, bothSourcesDisconnectAndConnectBack)
struct LoadBalancer3SourcesTests : LoadBalancerConstructorTests {
LoadBalancer3SourcesTests()
{
sourceFactory_ = StrictMockSourceFactory{3};
sourceFactory_.setSourcesNumber(3);
configJson_.as_object()["etl_sources"] = {"source1", "source2", "source3"};
EXPECT_CALL(sourceFactory_, makeSource).Times(3);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
@@ -381,6 +425,7 @@ TEST_F(LoadBalancerLoadInitialLedgerCustomNumMarkersTests, loadInitialLedger)
{
configJson_.as_object()["num_markers"] = numMarkers_;
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled).WillOnce(Return(boost::json::object{}));
EXPECT_CALL(sourceFactory_.sourceAt(0), run);
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled).WillOnce(Return(boost::json::object{}));
@@ -484,74 +529,125 @@ struct LoadBalancerForwardToRippledTests : LoadBalancerConstructorTests, SyncAsi
TEST_F(LoadBalancerForwardToRippledTests, forward)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
auto loadBalancer = makeLoadBalancer();
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled(request_, clientIP_, testing::_))
EXPECT_CALL(
sourceFactory_.sourceAt(0),
forwardToRippled(request_, clientIP_, LoadBalancer::ADMIN_FORWARDING_X_USER_VALUE, testing::_)
)
.WillOnce(Return(response_));
runSpawn([&](boost::asio::yield_context yield) {
EXPECT_EQ(loadBalancer->forwardToRippled(request_, clientIP_, yield), response_);
EXPECT_EQ(loadBalancer->forwardToRippled(request_, clientIP_, true, yield), response_);
});
}
TEST_F(LoadBalancerForwardToRippledTests, forwardWithXUserHeader)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
auto loadBalancer = makeLoadBalancer();
EXPECT_CALL(
sourceFactory_.sourceAt(0),
forwardToRippled(request_, clientIP_, LoadBalancer::USER_FORWARDING_X_USER_VALUE, testing::_)
)
.WillOnce(Return(response_));
runSpawn([&](boost::asio::yield_context yield) {
EXPECT_EQ(loadBalancer->forwardToRippled(request_, clientIP_, false, yield), response_);
});
}
TEST_F(LoadBalancerForwardToRippledTests, source0Fails)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
auto loadBalancer = makeLoadBalancer();
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled(request_, clientIP_, testing::_))
EXPECT_CALL(
sourceFactory_.sourceAt(0),
forwardToRippled(request_, clientIP_, LoadBalancer::USER_FORWARDING_X_USER_VALUE, testing::_)
)
.WillOnce(Return(std::nullopt));
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled(request_, clientIP_, testing::_))
EXPECT_CALL(
sourceFactory_.sourceAt(1),
forwardToRippled(request_, clientIP_, LoadBalancer::USER_FORWARDING_X_USER_VALUE, testing::_)
)
.WillOnce(Return(response_));
runSpawn([&](boost::asio::yield_context yield) {
EXPECT_EQ(loadBalancer->forwardToRippled(request_, clientIP_, yield), response_);
EXPECT_EQ(loadBalancer->forwardToRippled(request_, clientIP_, false, yield), response_);
});
}
TEST_F(LoadBalancerForwardToRippledTests, bothSourcesFail)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
auto loadBalancer = makeLoadBalancer();
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled(request_, clientIP_, testing::_))
EXPECT_CALL(
sourceFactory_.sourceAt(0),
forwardToRippled(request_, clientIP_, LoadBalancer::USER_FORWARDING_X_USER_VALUE, testing::_)
)
.WillOnce(Return(std::nullopt));
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled(request_, clientIP_, testing::_))
EXPECT_CALL(
sourceFactory_.sourceAt(1),
forwardToRippled(request_, clientIP_, LoadBalancer::USER_FORWARDING_X_USER_VALUE, testing::_)
)
.WillOnce(Return(std::nullopt));
runSpawn([&](boost::asio::yield_context yield) {
EXPECT_EQ(loadBalancer->forwardToRippled(request_, clientIP_, yield), std::nullopt);
EXPECT_EQ(loadBalancer->forwardToRippled(request_, clientIP_, false, yield), std::nullopt);
});
}
TEST_F(LoadBalancerForwardToRippledTests, forwardingCacheEnabled)
{
configJson_.as_object()["forwarding_cache_timeout"] = 10.;
configJson_.as_object()["forwarding"] = boost::json::object{{"cache_timeout", 10.}};
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
auto loadBalancer = makeLoadBalancer();
auto const request = boost::json::object{{"command", "server_info"}};
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled(request, clientIP_, testing::_))
EXPECT_CALL(
sourceFactory_.sourceAt(0),
forwardToRippled(request, clientIP_, LoadBalancer::USER_FORWARDING_X_USER_VALUE, testing::_)
)
.WillOnce(Return(response_));
runSpawn([&](boost::asio::yield_context yield) {
EXPECT_EQ(loadBalancer->forwardToRippled(request, clientIP_, yield), response_);
EXPECT_EQ(loadBalancer->forwardToRippled(request, clientIP_, yield), response_);
EXPECT_EQ(loadBalancer->forwardToRippled(request, clientIP_, false, yield), response_);
EXPECT_EQ(loadBalancer->forwardToRippled(request, clientIP_, false, yield), response_);
});
}
TEST_F(LoadBalancerForwardToRippledTests, forwardingCacheDisabledOnLedgerClosedHookCalled)
{
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
auto loadBalancer = makeLoadBalancer();
EXPECT_NO_THROW(sourceFactory_.callbacksAt(0).onLedgerClosed());
}
TEST_F(LoadBalancerForwardToRippledTests, onLedgerClosedHookInvalidatesCache)
{
configJson_.as_object()["forwarding_cache_timeout"] = 10.;
configJson_.as_object()["forwarding"] = boost::json::object{{"cache_timeout", 10.}};
EXPECT_CALL(sourceFactory_, makeSource).Times(2);
auto loadBalancer = makeLoadBalancer();
auto const request = boost::json::object{{"command", "server_info"}};
EXPECT_CALL(sourceFactory_.sourceAt(0), forwardToRippled(request, clientIP_, testing::_))
EXPECT_CALL(
sourceFactory_.sourceAt(0),
forwardToRippled(request, clientIP_, LoadBalancer::USER_FORWARDING_X_USER_VALUE, testing::_)
)
.WillOnce(Return(response_));
EXPECT_CALL(sourceFactory_.sourceAt(1), forwardToRippled(request, clientIP_, testing::_))
EXPECT_CALL(
sourceFactory_.sourceAt(1),
forwardToRippled(request, clientIP_, LoadBalancer::USER_FORWARDING_X_USER_VALUE, testing::_)
)
.WillOnce(Return(boost::json::object{}));
runSpawn([&](boost::asio::yield_context yield) {
EXPECT_EQ(loadBalancer->forwardToRippled(request, clientIP_, yield), response_);
EXPECT_EQ(loadBalancer->forwardToRippled(request, clientIP_, yield), response_);
EXPECT_EQ(loadBalancer->forwardToRippled(request, clientIP_, false, yield), response_);
EXPECT_EQ(loadBalancer->forwardToRippled(request, clientIP_, false, yield), response_);
sourceFactory_.callbacksAt(0).onLedgerClosed();
EXPECT_EQ(loadBalancer->forwardToRippled(request, clientIP_, yield), boost::json::object{});
EXPECT_EQ(loadBalancer->forwardToRippled(request, clientIP_, false, yield), boost::json::object{});
});
}

View File

@@ -33,6 +33,7 @@
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
@@ -67,7 +68,7 @@ struct ForwardingSourceMock {
MOCK_METHOD(
ForwardToRippledReturnType,
forwardToRippled,
(boost::json::object const&, ClientIpOpt const&, boost::asio::yield_context),
(boost::json::object const&, ClientIpOpt const&, std::string_view, boost::asio::yield_context),
(const)
);
};
@@ -175,12 +176,14 @@ TEST_F(SourceImplTest, forwardToRippled)
{
boost::json::object const request = {{"some_key", "some_value"}};
std::optional<std::string> const clientIp = "some_client_ip";
std::string_view xUserValue = "some_user";
EXPECT_CALL(forwardingSourceMock_, forwardToRippled(request, clientIp, testing::_)).WillOnce(Return(request));
EXPECT_CALL(forwardingSourceMock_, forwardToRippled(request, clientIp, xUserValue, testing::_))
.WillOnce(Return(request));
boost::asio::io_context ioContext;
boost::asio::spawn(ioContext, [&](boost::asio::yield_context yield) {
auto const response = source_.forwardToRippled(request, clientIp, yield);
auto const response = source_.forwardToRippled(request, clientIp, xUserValue, yield);
EXPECT_EQ(response, request);
});
ioContext.run();

View File

@@ -18,6 +18,7 @@
//==============================================================================
#include "etl/impl/SubscriptionSource.hpp"
#include "util/AssignRandomPort.hpp"
#include "util/Fixtures.hpp"
#include "util/MockNetworkValidatedLedgers.hpp"
#include "util/MockSubscriptionManager.hpp"
@@ -31,6 +32,7 @@
#include <gtest/gtest.h>
#include <chrono>
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
@@ -46,8 +48,7 @@ struct SubscriptionSourceConnectionTests : public NoLoggerFixture {
}
boost::asio::io_context ioContext_;
TestWsServer wsServer_{ioContext_, "0.0.0.0", 11113};
TestWsServer wsServer_{ioContext_, "0.0.0.0"};
StrictMockNetworkValidatedLedgersPtr networkValidatedLedgers_;
StrictMockSubscriptionManagerSharedPtr subscriptionManager_;
@@ -59,7 +60,7 @@ struct SubscriptionSourceConnectionTests : public NoLoggerFixture {
SubscriptionSource subscriptionSource_{
ioContext_,
"127.0.0.1",
"11113",
wsServer_.port(),
networkValidatedLedgers_,
subscriptionManager_,
onConnectHook_.AsStdFunction(),

View File

@@ -45,7 +45,7 @@ TEST_F(FeedBookChangeTest, Pub)
testFeedPtr->sub(sessionPtr);
EXPECT_EQ(testFeedPtr->count(), 1);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 32);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 32);
auto transactions = std::vector<TransactionAndMetadata>{};
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
@@ -55,7 +55,7 @@ TEST_F(FeedBookChangeTest, Pub)
trans1.metadata = metaObj.getSerializer().peekData();
transactions.push_back(trans1);
testFeedPtr->pub(ledgerinfo, transactions);
testFeedPtr->pub(ledgerHeader, transactions);
constexpr static auto bookChangePublish =
R"({
"type":"bookChanges",
@@ -82,7 +82,7 @@ TEST_F(FeedBookChangeTest, Pub)
testFeedPtr->unsub(sessionPtr);
EXPECT_EQ(testFeedPtr->count(), 0);
testFeedPtr->pub(ledgerinfo, transactions);
testFeedPtr->pub(ledgerHeader, transactions);
ctx.restart();
ctx.run();
}

View File

@@ -39,8 +39,8 @@ using FeedLedgerTest = FeedBaseTest<LedgerFeed>;
TEST_F(FeedLedgerTest, SubPub)
{
backend->setRange(10, 30);
auto const ledgerInfo = CreateLedgerInfo(LEDGERHASH, 30);
EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(testing::Return(ledgerInfo));
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 30);
EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(testing::Return(ledgerHeader));
auto const feeBlob = CreateLegacyFeeSettingBlob(1, 2, 3, 4, 0);
EXPECT_CALL(*backend, doFetchLedgerObject).WillOnce(testing::Return(feeBlob));
@@ -81,10 +81,10 @@ TEST_F(FeedLedgerTest, SubPub)
// test publish
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(ledgerPub))).Times(1);
auto const ledgerinfo2 = CreateLedgerInfo(LEDGERHASH, 31);
auto const ledgerHeader2 = CreateLedgerHeader(LEDGERHASH, 31);
auto fee2 = ripple::Fees();
fee2.reserve = 10;
testFeedPtr->pub(ledgerinfo2, fee2, "10-31", 8);
testFeedPtr->pub(ledgerHeader2, fee2, "10-31", 8);
ctx.restart();
ctx.run();
@@ -92,7 +92,7 @@ TEST_F(FeedLedgerTest, SubPub)
testFeedPtr->unsub(sessionPtr);
EXPECT_EQ(testFeedPtr->count(), 0);
EXPECT_CALL(*mockSessionPtr, send(_)).Times(0);
testFeedPtr->pub(ledgerinfo2, fee2, "10-31", 8);
testFeedPtr->pub(ledgerHeader2, fee2, "10-31", 8);
ctx.restart();
ctx.run();
}
@@ -100,8 +100,8 @@ TEST_F(FeedLedgerTest, SubPub)
TEST_F(FeedLedgerTest, AutoDisconnect)
{
backend->setRange(10, 30);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30);
EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(testing::Return(ledgerinfo));
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 30);
EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(testing::Return(ledgerHeader));
auto const feeBlob = CreateLegacyFeeSettingBlob(1, 2, 3, 4, 0);
EXPECT_CALL(*backend, doFetchLedgerObject).WillOnce(testing::Return(feeBlob));
@@ -127,11 +127,11 @@ TEST_F(FeedLedgerTest, AutoDisconnect)
sessionPtr.reset();
EXPECT_EQ(testFeedPtr->count(), 0);
auto const ledgerinfo2 = CreateLedgerInfo(LEDGERHASH, 31);
auto const ledgerHeader2 = CreateLedgerHeader(LEDGERHASH, 31);
auto fee2 = ripple::Fees();
fee2.reserve = 10;
// no error
testFeedPtr->pub(ledgerinfo2, fee2, "10-31", 8);
testFeedPtr->pub(ledgerHeader2, fee2, "10-31", 8);
ctx.restart();
ctx.run();
}

View File

@@ -239,7 +239,7 @@ TEST_F(SubscriptionManagerTest, BookChangesTest)
SubscriptionManagerPtr->subBookChanges(session);
EXPECT_EQ(SubscriptionManagerPtr->report()["book_changes"], 1);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 32);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 32);
auto transactions = std::vector<TransactionAndMetadata>{};
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
@@ -249,7 +249,7 @@ TEST_F(SubscriptionManagerTest, BookChangesTest)
trans1.metadata = metaObj.getSerializer().peekData();
transactions.push_back(trans1);
SubscriptionManagerPtr->pubBookChanges(ledgerinfo, transactions);
SubscriptionManagerPtr->pubBookChanges(ledgerHeader, transactions);
constexpr static auto bookChangePublish =
R"({
"type":"bookChanges",
@@ -280,8 +280,8 @@ TEST_F(SubscriptionManagerTest, BookChangesTest)
TEST_F(SubscriptionManagerTest, LedgerTest)
{
backend->setRange(10, 30);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30);
EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(testing::Return(ledgerinfo));
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 30);
EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(testing::Return(ledgerHeader));
auto const feeBlob = CreateLegacyFeeSettingBlob(1, 2, 3, 4, 0);
EXPECT_CALL(*backend, doFetchLedgerObject).WillOnce(testing::Return(feeBlob));
@@ -308,10 +308,10 @@ TEST_F(SubscriptionManagerTest, LedgerTest)
EXPECT_EQ(SubscriptionManagerPtr->report()["ledger"], 1);
// test publish
auto const ledgerinfo2 = CreateLedgerInfo(LEDGERHASH, 31);
auto const ledgerHeader2 = CreateLedgerHeader(LEDGERHASH, 31);
auto fee2 = ripple::Fees();
fee2.reserve = 10;
SubscriptionManagerPtr->pubLedger(ledgerinfo2, fee2, "10-31", 8);
SubscriptionManagerPtr->pubLedger(ledgerHeader2, fee2, "10-31", 8);
constexpr static auto ledgerPub =
R"({
"type":"ledgerClosed",
@@ -345,7 +345,7 @@ TEST_F(SubscriptionManagerTest, TransactionTest)
EXPECT_EQ(SubscriptionManagerPtr->report()["transactions"], 1);
EXPECT_EQ(SubscriptionManagerPtr->report()["books"], 1);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
auto obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
@@ -353,7 +353,7 @@ TEST_F(SubscriptionManagerTest, TransactionTest)
auto const metaObj = CreateMetaDataForBookChange(CURRENCY, ISSUER, 22, 3, 1, 1, 3);
trans1.metadata = metaObj.getSerializer().peekData();
SubscriptionManagerPtr->pubTransaction(trans1, ledgerinfo);
SubscriptionManagerPtr->pubTransaction(trans1, ledgerHeader);
constexpr static auto OrderbookPublish =
R"({
@@ -506,7 +506,7 @@ TEST_F(SubscriptionManagerTest, ProposedTransactionTest)
EXPECT_CALL(*sessionPtr, send(SharedStringJsonEq(OrderbookPublish))).Times(2);
SubscriptionManagerPtr->forwardProposedTransaction(json::parse(dummyTransaction).get_object());
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
auto obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
@@ -514,7 +514,7 @@ TEST_F(SubscriptionManagerTest, ProposedTransactionTest)
auto const metaObj = CreateMetaDataForBookChange(CURRENCY, ACCOUNT1, 22, 3, 1, 1, 3);
trans1.metadata = metaObj.getSerializer().peekData();
SubscriptionManagerPtr->pubTransaction(trans1, ledgerinfo);
SubscriptionManagerPtr->pubTransaction(trans1, ledgerHeader);
ctx.run();
// unsub account1
@@ -533,7 +533,7 @@ TEST_F(SubscriptionManagerTest, DuplicateResponseSubTxAndProposedTx)
EXPECT_CALL(*sessionPtr, send(testing::_)).Times(2);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
auto obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
@@ -541,7 +541,7 @@ TEST_F(SubscriptionManagerTest, DuplicateResponseSubTxAndProposedTx)
auto const metaObj = CreateMetaDataForBookChange(CURRENCY, ACCOUNT1, 22, 3, 1, 1, 3);
trans1.metadata = metaObj.getSerializer().peekData();
SubscriptionManagerPtr->pubTransaction(trans1, ledgerinfo);
SubscriptionManagerPtr->pubTransaction(trans1, ledgerHeader);
ctx.run();
SubscriptionManagerPtr->unsubTransactions(session);
@@ -560,7 +560,7 @@ TEST_F(SubscriptionManagerTest, NoDuplicateResponseSubAccountAndProposedAccount)
EXPECT_CALL(*sessionPtr, send(testing::_)).Times(1);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
auto obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
@@ -568,7 +568,7 @@ TEST_F(SubscriptionManagerTest, NoDuplicateResponseSubAccountAndProposedAccount)
auto const metaObj = CreateMetaDataForBookChange(CURRENCY, ACCOUNT1, 22, 3, 1, 1, 3);
trans1.metadata = metaObj.getSerializer().peekData();
SubscriptionManagerPtr->pubTransaction(trans1, ledgerinfo);
SubscriptionManagerPtr->pubTransaction(trans1, ledgerHeader);
ctx.run();
// unsub account1

View File

@@ -168,13 +168,13 @@ TEST_F(FeedTransactionTest, SubTransactionV1)
testFeedPtr->sub(sessionPtr);
EXPECT_EQ(testFeedPtr->transactionSubCount(), 1);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
trans1.ledgerSequence = 32;
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(1);
ctx.run();
@@ -182,7 +182,7 @@ TEST_F(FeedTransactionTest, SubTransactionV1)
testFeedPtr->unsub(sessionPtr);
EXPECT_EQ(testFeedPtr->transactionSubCount(), 0);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}
@@ -192,19 +192,19 @@ TEST_F(FeedTransactionTest, SubTransactionForProposedTx)
testFeedPtr->subProposed(sessionPtr);
EXPECT_EQ(testFeedPtr->transactionSubCount(), 0);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
trans1.ledgerSequence = 32;
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(1);
ctx.run();
testFeedPtr->unsubProposed(sessionPtr);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}
@@ -215,13 +215,13 @@ TEST_F(FeedTransactionTest, SubTransactionV2)
testFeedPtr->sub(sessionPtr);
EXPECT_EQ(testFeedPtr->transactionSubCount(), 1);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
trans1.ledgerSequence = 32;
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(1);
ctx.run();
@@ -229,7 +229,7 @@ TEST_F(FeedTransactionTest, SubTransactionV2)
testFeedPtr->unsub(sessionPtr);
EXPECT_EQ(testFeedPtr->transactionSubCount(), 0);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}
@@ -240,21 +240,21 @@ TEST_F(FeedTransactionTest, SubAccountV1)
testFeedPtr->sub(account, sessionPtr);
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
trans1.ledgerSequence = 32;
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(1);
ctx.run();
testFeedPtr->unsub(account, sessionPtr);
EXPECT_EQ(testFeedPtr->accountSubCount(), 0);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}
@@ -265,19 +265,19 @@ TEST_F(FeedTransactionTest, SubForProposedAccount)
testFeedPtr->subProposed(account, sessionPtr);
EXPECT_EQ(testFeedPtr->accountSubCount(), 0);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
trans1.ledgerSequence = 32;
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(1);
ctx.run();
testFeedPtr->unsubProposed(account, sessionPtr);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}
@@ -289,7 +289,7 @@ TEST_F(FeedTransactionTest, SubAccountV2)
testFeedPtr->sub(account, sessionPtr);
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
@@ -297,14 +297,14 @@ TEST_F(FeedTransactionTest, SubAccountV2)
trans1.ledgerSequence = 32;
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(1);
ctx.run();
testFeedPtr->unsub(account, sessionPtr);
EXPECT_EQ(testFeedPtr->accountSubCount(), 0);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}
@@ -318,7 +318,7 @@ TEST_F(FeedTransactionTest, SubBothTransactionAndAccount)
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
EXPECT_EQ(testFeedPtr->transactionSubCount(), 1);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
@@ -326,7 +326,7 @@ TEST_F(FeedTransactionTest, SubBothTransactionAndAccount)
trans1.ledgerSequence = 32;
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(2);
ctx.run();
@@ -336,7 +336,7 @@ TEST_F(FeedTransactionTest, SubBothTransactionAndAccount)
testFeedPtr->unsub(sessionPtr);
EXPECT_EQ(testFeedPtr->transactionSubCount(), 0);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}
@@ -348,7 +348,7 @@ TEST_F(FeedTransactionTest, SubBookV1)
testFeedPtr->sub(book, sessionPtr);
EXPECT_EQ(testFeedPtr->bookSubCount(), 1);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
auto obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
@@ -356,7 +356,7 @@ TEST_F(FeedTransactionTest, SubBookV1)
auto metaObj = CreateMetaDataForBookChange(CURRENCY, ISSUER, 22, 3, 1, 1, 3);
trans1.metadata = metaObj.getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
constexpr static auto OrderbookPublish =
R"({
@@ -424,7 +424,7 @@ TEST_F(FeedTransactionTest, SubBookV1)
// trigger by offer cancel meta data
metaObj = CreateMetaDataForCancelOffer(CURRENCY, ISSUER, 22, 3, 1);
trans1.metadata = metaObj.getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
constexpr static auto OrderbookCancelPublish =
R"({
@@ -529,7 +529,7 @@ TEST_F(FeedTransactionTest, SubBookV1)
})";
metaObj = CreateMetaDataForCreateOffer(CURRENCY, ISSUER, 22, 3, 1);
trans1.metadata = metaObj.getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(OrderbookCreatePublish))).Times(1);
ctx.restart();
ctx.run();
@@ -537,7 +537,7 @@ TEST_F(FeedTransactionTest, SubBookV1)
testFeedPtr->unsub(book, sessionPtr);
EXPECT_EQ(testFeedPtr->bookSubCount(), 0);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}
@@ -550,7 +550,7 @@ TEST_F(FeedTransactionTest, SubBookV2)
testFeedPtr->sub(book, sessionPtr);
EXPECT_EQ(testFeedPtr->bookSubCount(), 1);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
auto obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
@@ -558,7 +558,7 @@ TEST_F(FeedTransactionTest, SubBookV2)
auto const metaObj = CreateMetaDataForBookChange(CURRENCY, ISSUER, 22, 3, 1, 1, 3);
trans1.metadata = metaObj.getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
constexpr static auto OrderbookPublish =
R"({
@@ -626,7 +626,7 @@ TEST_F(FeedTransactionTest, SubBookV2)
testFeedPtr->unsub(book, sessionPtr);
EXPECT_EQ(testFeedPtr->bookSubCount(), 0);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}
@@ -642,27 +642,27 @@ TEST_F(FeedTransactionTest, TransactionContainsBothAccountsSubed)
EXPECT_EQ(testFeedPtr->accountSubCount(), 2);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
trans1.ledgerSequence = 32;
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(1);
ctx.run();
testFeedPtr->unsub(account, sessionPtr);
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(1);
ctx.run();
testFeedPtr->unsub(account2, sessionPtr);
EXPECT_EQ(testFeedPtr->accountSubCount(), 0);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}
@@ -678,20 +678,20 @@ TEST_F(FeedTransactionTest, SubAccountRepeatWithDifferentVersion)
EXPECT_EQ(testFeedPtr->accountSubCount(), 2);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
trans1.ledgerSequence = 32;
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(1);
ctx.run();
testFeedPtr->unsub(account, sessionPtr);
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(1);
ctx.run();
@@ -699,7 +699,7 @@ TEST_F(FeedTransactionTest, SubAccountRepeatWithDifferentVersion)
testFeedPtr->unsub(account2, sessionPtr);
EXPECT_EQ(testFeedPtr->accountSubCount(), 0);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}
@@ -714,21 +714,21 @@ TEST_F(FeedTransactionTest, SubTransactionRepeatWithDifferentVersion)
testFeedPtr->sub(sessionPtr);
EXPECT_EQ(testFeedPtr->transactionSubCount(), 1);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
trans1.ledgerSequence = 32;
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V2))).Times(1);
ctx.run();
testFeedPtr->unsub(sessionPtr);
EXPECT_EQ(testFeedPtr->transactionSubCount(), 0);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}
@@ -789,7 +789,7 @@ TEST_F(FeedTransactionTest, PubTransactionWithOwnerFund)
{
testFeedPtr->sub(sessionPtr);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreateCreateOfferTransactionObject(ACCOUNT1, 1, 32, CURRENCY, ISSUER, 1, 3);
trans1.transaction = obj.getSerializer().peekData();
@@ -820,7 +820,7 @@ TEST_F(FeedTransactionTest, PubTransactionWithOwnerFund)
ON_CALL(*backend, doFetchLedgerObject(kk, testing::_, testing::_))
.WillByDefault(testing::Return(accountRoot.getSerializer().peekData()));
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
constexpr static auto TransactionForOwnerFund =
R"({
"transaction":
@@ -902,7 +902,7 @@ TEST_F(FeedTransactionTest, PubTransactionOfferCreationFrozenLine)
{
testFeedPtr->sub(sessionPtr);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreateCreateOfferTransactionObject(ACCOUNT1, 1, 32, CURRENCY, ISSUER, 1, 3);
trans1.transaction = obj.getSerializer().peekData();
@@ -931,7 +931,7 @@ TEST_F(FeedTransactionTest, PubTransactionOfferCreationFrozenLine)
ripple::STObject const accountRoot = CreateAccountRootObject(ISSUER, 0, 1, 10, 2, TXNID, 3);
ON_CALL(*backend, doFetchLedgerObject(kk, testing::_, testing::_))
.WillByDefault(testing::Return(accountRoot.getSerializer().peekData()));
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_FROZEN))).Times(1);
ctx.run();
}
@@ -940,7 +940,7 @@ TEST_F(FeedTransactionTest, SubTransactionOfferCreationGlobalFrozen)
{
testFeedPtr->sub(sessionPtr);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreateCreateOfferTransactionObject(ACCOUNT1, 1, 32, CURRENCY, ISSUER, 1, 3);
trans1.transaction = obj.getSerializer().peekData();
@@ -969,7 +969,7 @@ TEST_F(FeedTransactionTest, SubTransactionOfferCreationGlobalFrozen)
ripple::STObject const accountRoot = CreateAccountRootObject(ISSUER, ripple::lsfGlobalFreeze, 1, 10, 2, TXNID, 3);
ON_CALL(*backend, doFetchLedgerObject(kk, testing::_, testing::_))
.WillByDefault(testing::Return(accountRoot.getSerializer().peekData()));
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_FROZEN))).Times(1);
ctx.run();
@@ -982,13 +982,13 @@ TEST_F(FeedTransactionTest, SubBothProposedAndValidatedAccount)
testFeedPtr->subProposed(account, sessionPtr);
EXPECT_EQ(testFeedPtr->accountSubCount(), 1);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
trans1.ledgerSequence = 32;
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(1);
ctx.run();
@@ -996,7 +996,7 @@ TEST_F(FeedTransactionTest, SubBothProposedAndValidatedAccount)
testFeedPtr->unsubProposed(account, sessionPtr);
EXPECT_EQ(testFeedPtr->accountSubCount(), 0);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}
@@ -1007,19 +1007,19 @@ TEST_F(FeedTransactionTest, SubBothProposedAndValidated)
testFeedPtr->subProposed(sessionPtr);
EXPECT_EQ(testFeedPtr->transactionSubCount(), 1);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
trans1.ledgerSequence = 32;
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(2);
ctx.run();
testFeedPtr->unsub(sessionPtr);
testFeedPtr->unsubProposed(sessionPtr);
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}
@@ -1029,18 +1029,18 @@ TEST_F(FeedTransactionTest, SubProposedDisconnect)
testFeedPtr->subProposed(sessionPtr);
EXPECT_EQ(testFeedPtr->transactionSubCount(), 0);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
trans1.ledgerSequence = 32;
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(1);
ctx.run();
sessionPtr.reset();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}
@@ -1051,18 +1051,18 @@ TEST_F(FeedTransactionTest, SubProposedAccountDisconnect)
testFeedPtr->subProposed(account, sessionPtr);
EXPECT_EQ(testFeedPtr->accountSubCount(), 0);
auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 33);
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, 33);
auto trans1 = TransactionAndMetadata();
ripple::STObject const obj = CreatePaymentTransactionObject(ACCOUNT1, ACCOUNT2, 1, 1, 32);
trans1.transaction = obj.getSerializer().peekData();
trans1.ledgerSequence = 32;
trans1.metadata = CreatePaymentTransactionMetaObject(ACCOUNT1, ACCOUNT2, 110, 30, 22).getSerializer().peekData();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
EXPECT_CALL(*mockSessionPtr, send(SharedStringJsonEq(TRAN_V1))).Times(1);
ctx.run();
sessionPtr.reset();
testFeedPtr->pub(trans1, ledgerinfo, backend);
testFeedPtr->pub(trans1, ledgerHeader, backend);
ctx.restart();
ctx.run();
}

View File

@@ -31,6 +31,7 @@
#include <boost/json/parse.hpp>
#include <boost/json/value.hpp>
#include <fmt/core.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/ErrorCodes.h>
@@ -604,3 +605,49 @@ TEST_F(RPCBaseTest, ToLowerModifier)
ASSERT_TRUE(spec.process(passingInput4)); // empty str no problem
ASSERT_EQ(passingInput4.at("str").as_string(), "");
}
TEST_F(RPCBaseTest, ToNumberModifier)
{
auto const spec = RpcSpec{
{"str", ToNumber{}},
};
auto passingInput = json::parse(R"({ "str": [] })");
ASSERT_TRUE(spec.process(passingInput));
passingInput = json::parse(R"({ "str2": "TesT" })");
ASSERT_TRUE(spec.process(passingInput));
passingInput = json::parse(R"([])");
ASSERT_TRUE(spec.process(passingInput));
passingInput = json::parse(R"({ "str": "123" })");
ASSERT_TRUE(spec.process(passingInput));
ASSERT_EQ(passingInput.at("str").as_int64(), 123);
auto failingInput = json::parse(R"({ "str": "ok" })");
ASSERT_FALSE(spec.process(failingInput));
failingInput = json::parse(R"({ "str": "123.123" })");
ASSERT_FALSE(spec.process(failingInput));
}
TEST_F(RPCBaseTest, CustomModifier)
{
testing::StrictMock<testing::MockFunction<MaybeError(json::value & value, std::string_view)>> mockModifier;
auto const customModifier = CustomModifier{mockModifier.AsStdFunction()};
auto const spec = RpcSpec{
{"str", customModifier},
};
EXPECT_CALL(mockModifier, Call).WillOnce(testing::Return(MaybeError{}));
auto passingInput = json::parse(R"({ "str": "sss" })");
ASSERT_TRUE(spec.process(passingInput));
passingInput = json::parse(R"({ "strNotExist": 123 })");
ASSERT_TRUE(spec.process(passingInput));
// not a json object
passingInput = json::parse(R"([])");
ASSERT_TRUE(spec.process(passingInput));
}

View File

@@ -260,6 +260,38 @@ TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsFalseIfAPIVersionIsV2)
});
}
TEST_F(RPCForwardingProxyTest, ShouldForwardFeatureWithoutVetoedFlag)
{
auto const apiVersion = 1u;
auto const method = "feature";
auto const params = json::parse(R"({"feature": "foo"})");
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();
auto const ctx =
web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true);
auto const res = proxy.shouldForward(ctx);
ASSERT_TRUE(res);
});
}
TEST_F(RPCForwardingProxyTest, ShouldNeverForwardFeatureWithVetoedFlag)
{
auto const apiVersion = 1u;
auto const method = "feature";
auto const params = json::parse(R"({"vetoed": true, "feature": "foo"})");
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();
auto const ctx =
web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true);
auto const res = proxy.shouldForward(ctx);
ASSERT_FALSE(res);
});
}
TEST_F(RPCForwardingProxyTest, ShouldNeverForwardSubscribe)
{
auto const apiVersion = 1u;
@@ -301,15 +333,14 @@ TEST_F(RPCForwardingProxyTest, ForwardCallsBalancerWithCorrectParams)
auto const params = json::parse(R"({"test": true})");
auto const forwarded = json::parse(R"({"test": true, "command": "submit"})");
ON_CALL(*rawBalancerPtr, forwardToRippled).WillByDefault(Return(std::make_optional<json::object>()));
EXPECT_CALL(*rawBalancerPtr, forwardToRippled(forwarded.as_object(), std::make_optional<std::string>(CLIENT_IP), _))
.Times(1);
EXPECT_CALL(
*rawBalancerPtr, forwardToRippled(forwarded.as_object(), std::make_optional<std::string>(CLIENT_IP), true, _)
)
.WillOnce(Return(std::make_optional<json::object>()));
ON_CALL(*rawHandlerProviderPtr, contains).WillByDefault(Return(true));
EXPECT_CALL(*rawHandlerProviderPtr, contains(method)).Times(1);
EXPECT_CALL(*rawHandlerProviderPtr, contains(method)).WillOnce(Return(true));
ON_CALL(counters, rpcForwarded).WillByDefault(Return());
EXPECT_CALL(counters, rpcForwarded(method)).Times(1);
EXPECT_CALL(counters, rpcForwarded(method));
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();
@@ -332,15 +363,14 @@ TEST_F(RPCForwardingProxyTest, ForwardingFailYieldsErrorStatus)
auto const params = json::parse(R"({"test": true})");
auto const forwarded = json::parse(R"({"test": true, "command": "submit"})");
ON_CALL(*rawBalancerPtr, forwardToRippled).WillByDefault(Return(std::nullopt));
EXPECT_CALL(*rawBalancerPtr, forwardToRippled(forwarded.as_object(), std::make_optional<std::string>(CLIENT_IP), _))
.Times(1);
EXPECT_CALL(
*rawBalancerPtr, forwardToRippled(forwarded.as_object(), std::make_optional<std::string>(CLIENT_IP), true, _)
)
.WillOnce(Return(std::nullopt));
ON_CALL(*rawHandlerProviderPtr, contains).WillByDefault(Return(true));
EXPECT_CALL(*rawHandlerProviderPtr, contains(method)).Times(1);
EXPECT_CALL(*rawHandlerProviderPtr, contains(method)).WillOnce(Return(true));
ON_CALL(counters, rpcFailedToForward).WillByDefault(Return());
EXPECT_CALL(counters, rpcFailedToForward(method)).Times(1);
EXPECT_CALL(counters, rpcFailedToForward(method));
runSpawn([&](auto yield) {
auto const range = backend->fetchLedgerRange();

View File

@@ -17,6 +17,7 @@
*/
//==============================================================================
#include "rpc/common/JsonBool.hpp"
#include "util/NameGenerator.hpp"
#include <boost/json/parse.hpp>
#include <boost/json/value_to.hpp>
@@ -37,16 +38,6 @@ struct JsonBoolTestsCaseBundle {
class JsonBoolTests : public TestWithParam<JsonBoolTestsCaseBundle> {
public:
struct NameGenerator {
template <class ParamType>
std::string
operator()(testing::TestParamInfo<ParamType> const& info) const
{
auto bundle = static_cast<JsonBoolTestsCaseBundle>(info.param);
return bundle.testName;
}
};
static auto
generateTestValuesForParametersTest()
{
@@ -72,7 +63,7 @@ INSTANTIATE_TEST_CASE_P(
JsonBoolCheckGroup,
JsonBoolTests,
ValuesIn(JsonBoolTests::generateTestValuesForParametersTest()),
JsonBoolTests::NameGenerator{}
tests::util::NameGenerator
);
TEST_P(JsonBoolTests, Parse)

View File

@@ -427,7 +427,7 @@ TEST_F(RPCHelpersTest, DeliverMaxAliasV2)
TEST_F(RPCHelpersTest, LedgerHeaderJson)
{
auto const ledgerHeader = CreateLedgerInfo(INDEX1, 30);
auto const ledgerHeader = CreateLedgerHeader(INDEX1, 30);
auto const binJson = toJson(ledgerHeader, true, 1u);
auto constexpr EXPECTBIN = R"({
@@ -462,7 +462,7 @@ TEST_F(RPCHelpersTest, LedgerHeaderJson)
TEST_F(RPCHelpersTest, LedgerHeaderJsonV2)
{
auto const ledgerHeader = CreateLedgerInfo(INDEX1, 30);
auto const ledgerHeader = CreateLedgerHeader(INDEX1, 30);
auto const EXPECTJSON = fmt::format(
R"({{

Some files were not shown because too many files have changed in this diff Show More