mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-04 11:55:51 +00:00
Compare commits
24 Commits
2.2.0-b3
...
release/2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b18e28c47 | ||
|
|
4940d463dc | ||
|
|
c795cf371a | ||
|
|
e135aa49d5 | ||
|
|
5ba08b1d26 | ||
|
|
37cd79ceb0 | ||
|
|
1334bd05d9 | ||
|
|
437ea7bf98 | ||
|
|
f9f3bc928e | ||
|
|
aa1f3efda2 | ||
|
|
a6d21c1a02 | ||
|
|
49b80c7ad8 | ||
|
|
56ab943be5 | ||
|
|
9d3b4f0313 | ||
|
|
42c970a2a3 | ||
|
|
1125b09611 | ||
|
|
ce94f0f513 | ||
|
|
d39fb20022 | ||
|
|
967b85ca33 | ||
|
|
55b8134e6d | ||
|
|
66e8a65732 | ||
|
|
067dd72aed | ||
|
|
da5bf5c441 | ||
|
|
ff4bc5b0aa |
4
.clangd
4
.clangd
@@ -5,4 +5,6 @@ Diagnostics:
|
||||
UnusedIncludes: Strict
|
||||
MissingIncludes: Strict
|
||||
Includes:
|
||||
IgnoreHeader: ".*/(detail|impl)/.*"
|
||||
IgnoreHeader:
|
||||
- ".*/(detail|impl)/.*"
|
||||
- ".*expected.*"
|
||||
|
||||
@@ -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
|
||||
|
||||
10
.github/actions/create_issue/action.yml
vendored
10
.github/actions/create_issue/action.yml
vendored
@@ -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
28
.github/scripts/update-libxrpl-version
vendored
Executable 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
91
.github/workflows/check_libxrpl.yml
vendored
Normal 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 }}/
|
||||
@@ -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:**
|
||||
|
||||
|
||||
@@ -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'
|
||||
]
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
47
docs/trouble_shooting.md
Normal 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.
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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)}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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}};
|
||||
|
||||
@@ -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}}},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
84
src/rpc/handlers/Feature.cpp
Normal file
84
src/rpc/handlers/Feature.cpp
Normal 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
|
||||
95
src/rpc/handlers/Feature.hpp
Normal file
95
src/rpc/handlers/Feature.hpp
Normal 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
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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>{}},
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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{}},
|
||||
};
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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)) &&
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
@@ -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)
|
||||
|
||||
45
tests/common/util/AssignRandomPort.cpp
Normal file
45
tests/common/util/AssignRandomPort.cpp
Normal 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
|
||||
29
tests/common/util/AssignRandomPort.hpp
Normal file
29
tests/common/util/AssignRandomPort.hpp
Normal 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
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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), ());
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
28
tests/common/util/NameGenerator.hpp
Normal file
28
tests/common/util/NameGenerator.hpp
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -32,4 +32,4 @@ ripple::uint256
|
||||
binaryStringToUint256(std::string const& bin);
|
||||
|
||||
std::string
|
||||
ledgerInfoToBinaryString(ripple::LedgerInfo const& info);
|
||||
ledgerHeaderToBinaryString(ripple::LedgerHeader const& info);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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, _))
|
||||
|
||||
@@ -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{});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user