Compare commits

...

3 Commits

Author SHA1 Message Date
Michael Legleux
ec5d1eb65c Set version to 1.0.4 2023-02-02 11:59:47 -08:00
Michael Legleux
5b417bdc45 1.0.4 Release Candidate 2 (#465)
* Implement logging abstraction (#371)

Fixes #290

* Fix pre-commit to only check staged files

* Implement account ownership check and fix paging (#383)

Fixes #222

* Remove the github action package signing step

This will be done elsewhere.

* include searched_all in error response of tx (#407)

* helper function for subscribe to ensure cleanup (#402)

* Add closed to header for all paths of ledger_data (#416)

Fixes #219

* Add custom error for malformed owner and request (#417)

Fixes #274

* Use custom malformedAddress error in ledger_entry (#419)

Fixes #272

* Return lgrIdxsInvalid error for ledger_max_index less than ledger_min_index (#339)

Fixes #263

* Update headers to use #pragma once

* Add custom error for malformed request (#414)

Fixes #276

* Return srcCurMalformed on invalid taker_pays in book_offers (#413)

Fixes #267

* Fix source_location issue on MacOSX and Debug build (#431)

Fixes #428

* Implement always adding git ref to version string (#430)

Fixes #427

* add connection counting (#433)

* Fix malformed output format over ws rpc (#426)

Fixes #405

* Remove branch name from version string (#437)

Fixes a bug from #430

* Implement cli parsing using boost::po (#436)

Fixes #367

* Update documentation and config with ssl_cert_file and ssl_key_file (#443)

Fixes #424

* Fix gateway balances to match rippled output (#441)

Fixes #271

* Update README and example config to describe start_sequence (#438)

Fixes #250

* Add copyright to top of each source file (#444)

Fixes #411

* Increase file descriptor limit (#449)

* Update readme with more log configurations (#447)

Fixes #446

* Document dos_guard in example config. Log when client surpasses rate limit (#451)

* Add unit tests for DOSGuard (#453)

Fixes #452

* Build macOS and Ubuntu 22.04 (#456)

build release/x.y.z branches

* Add time measurement profiler (#458)

Rebase

* Match format to rippled error code (#461)

Fixes #263

* Change error message to match rippled (#463)

Fixes #263

* Add requests limit to DosGuard (#462)

Fixing #448

* Set version to 1.0.4-rc2

Co-authored-by: Alex Kremer <akremer@ripple.com>
Co-authored-by: CJ Cobb <46455409+cjcobb23@users.noreply.github.com>
Co-authored-by: Francis Mendoza <francissamuelmendoza7@gmail.com>
Co-authored-by: cyan317 <120398799+cindyyan317@users.noreply.github.com>
2023-01-10 16:04:07 -08:00
manojsdoshi
ce631a1f5a Set version to 1.0.4-rc1 2022-11-17 11:33:48 -08:00
93 changed files with 4426 additions and 1703 deletions

View File

@@ -2,14 +2,16 @@
exec 1>&2 exec 1>&2
# format all relevant sources # paths to check and re-format
find src unittests -type f \( -name '*.cpp' -o -name '*.h' -o -name '*.ipp' \) -print0 | xargs -0 clang-format -i sources="src unittests"
formatter="clang-format -i"
# check how many lines differ first=$(git diff $sources)
lines=$(git diff src unittests | wc -l) find $sources -type f \( -name '*.cpp' -o -name '*.h' -o -name '*.ipp' \) -print0 | xargs -0 $formatter
second=$(git diff $sources)
changes=$(diff <(echo "$first") <(echo "$second") | wc -l | sed -e 's/^[[:space:]]*//')
# check if there is any updated files if [ "$changes" != "0" ]; then
if [ "$lines" != "0" ]; then
cat <<\EOF cat <<\EOF
WARNING WARNING

View File

@@ -1,21 +0,0 @@
name: 'Sign packages'
runs:
using: "composite"
steps:
- name: Sign
shell: bash
run: |
set -ex -o pipefail
echo "$GPG_KEY_B64"| base64 -d | gpg --batch --no-tty --allow-secret-key-import --import -
unset GPG_KEY_B64
export GPG_PASSPHRASE=$(echo $GPG_KEY_PASS_B64 | base64 -di)
unset GPG_KEY_PASS_B64
export GPG_KEYID=$(gpg --with-colon --list-secret-keys | head -n1 | cut -d : -f 5)
for PKG in $(ls *.deb); do
dpkg-sig \
-g "--no-tty --digest-algo 'sha512' --passphrase '${GPG_PASSPHRASE}' --pinentry-mode=loopback" \
-k "${GPG_KEYID}" \
--sign builder \
$PKG
done

View File

@@ -1,9 +1,9 @@
name: Build Clio name: Build Clio
on: on:
push: push:
branches: [master, release, develop, develop-next] branches: [master, release/*, develop, develop-next]
pull_request: pull_request:
branches: [master, release, develop, develop-next] branches: [master, release/*, develop, develop-next]
workflow_dispatch: workflow_dispatch:
jobs: jobs:
@@ -29,6 +29,7 @@ jobs:
- suffix: rpm - suffix: rpm
image: rippleci/clio-rpm-builder:2022-09-17 image: rippleci/clio-rpm-builder:2022-09-17
script: rpm script: rpm
container: container:
image: ${{ matrix.type.image }} image: ${{ matrix.type.image }}
@@ -67,59 +68,63 @@ jobs:
name: clio_tests-${{ matrix.type.suffix }} name: clio_tests-${{ matrix.type.suffix }}
path: ${{ github.workspace }}/clio_tests path: ${{ github.workspace }}/clio_tests
sign: build_dev:
name: Sign packages name: ${{ matrix.os.name }} test
needs: build_clio needs: lint
runs-on: ubuntu-20.04 continue-on-error: ${{ matrix.os.experimental }}
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/release' || github.ref == 'refs/heads/develop'
env:
GPG_KEY_B64: ${{ secrets.GPG_KEY_B64 }}
GPG_KEY_PASS_B64: ${{ secrets.GPG_KEY_PASS_B64 }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
type: os:
- suffix: deb - name: ubuntu-22.04
image: ubuntu:20.04 experimental: true
script: dpkg - name: macos-11
# - suffix: rpm experimental: true
# image: centos:7 - name: macos-12
# script: rpm experimental: false
container: runs-on: ${{ matrix.os.name }}
image: ${{ matrix.type.image }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install dpkg-sig with:
run: | path: clio
apt-get update && apt-get install -y dpkg-sig gnupg
- name: Get package artifact
uses: actions/download-artifact@v3
with:
name: clio_${{ matrix.type.suffix }}_packages
- name: find packages - name: Check Boost cache
run: find . -name "*.${{ matrix.type.suffix }}" id: boost
uses: actions/cache@v3
with:
path: boost
key: ${{ runner.os }}-boost
- name: Sign packages - name: Build boost
uses: ./.github/actions/sign if: steps.boost.outputs.cache-hit != 'true'
run: |
curl -s -OJL "https://boostorg.jfrog.io/artifactory/main/release/1.77.0/source/boost_1_77_0.tar.gz"
tar zxf boost_1_77_0.tar.gz
mv boost_1_77_0 boost
cd boost
./bootstrap.sh
if [[ ${{ matrix.os.name }} =~ mac ]];then
mac_flags='cxxflags="-std=c++14"'
fi
./b2 ${mac_flags}
- name: install deps
run: |
if [[ ${{ matrix.os.name }} =~ mac ]];then
brew install pkg-config protobuf openssl ninja cassandra-cpp-driver bison
elif [[ ${{matrix.os.name }} =~ ubuntu ]];then
sudo apt-get -y install git pkg-config protobuf-compiler libprotobuf-dev libssl-dev wget build-essential doxygen bison flex autoconf clang-format
fi
- name: Verify the signature - name: Build clio
run: | run: |
set -e export BOOST_ROOT=$(pwd)/boost
for PKG in $(ls *.deb); do cd clio
gpg --verify "${PKG}" cmake -B build
done if ! cmake --build build -j$(nproc); then
echo '# 🔥${{ matrix.os.name }}🔥 failed!💥' >> $GITHUB_STEP_SUMMARY
- name: Get short SHA fi
id: shortsha
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- name: Artifact signed packages
uses: actions/upload-artifact@v2
with:
name: signed-clio-deb-packages-${{ steps.shortsha.outputs.sha8 }}
path: ${{ github.workspace }}/*.deb
test_clio: test_clio:
name: Test Clio name: Test Clio

View File

@@ -0,0 +1,11 @@
include(CheckIncludeFileCXX)
check_include_file_cxx("source_location" SOURCE_LOCATION_AVAILABLE)
if(SOURCE_LOCATION_AVAILABLE)
target_compile_definitions(clio PUBLIC "HAS_SOURCE_LOCATION")
endif()
check_include_file_cxx("experimental/source_location" EXPERIMENTAL_SOURCE_LOCATION_AVAILABLE)
if(EXPERIMENTAL_SOURCE_LOCATION_AVAILABLE)
target_compile_definitions(clio PUBLIC "HAS_EXPERIMENTAL_SOURCE_LOCATION")
endif()

View File

@@ -10,7 +10,8 @@ if(NOT googletest_POPULATED)
add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL) add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL)
endif() endif()
target_link_libraries(clio_tests PUBLIC clio gtest_main) target_link_libraries(clio_tests PUBLIC clio gmock_main)
target_include_directories(clio_tests PRIVATE unittests)
enable_testing() enable_testing()

View File

@@ -11,6 +11,7 @@ ExecStart=@CLIO_INSTALL_DIR@/bin/clio_server @CLIO_INSTALL_DIR@/etc/config.json
Restart=on-failure Restart=on-failure
User=clio User=clio
Group=clio Group=clio
LimitNOFILE=65536
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -1 +1,6 @@
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-narrowing -Wall -Werror -Wno-dangling-else") target_compile_options(clio
PUBLIC -Wall
-Werror
-Wno-narrowing
-Wno-deprecated-declarations
-Wno-dangling-else)

View File

@@ -21,11 +21,11 @@ if(NOT GIT_COMMIT_HASH)
find_package(Git) find_package(Git)
if(Git_FOUND) if(Git_FOUND)
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE gch) OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE git-ref)
if(gch) if(git-ref)
set(GIT_COMMIT_HASH "${gch}") set(BUILD "${git-ref}")
message(STATUS "Git commit: ${GIT_COMMIT_HASH}") message(STATUS "Build version: ${BUILD}")
add_definitions(-DCLIO_GIT_COMMIT_HASH="${GIT_COMMIT_HASH}") add_definitions(-DCLIO_BUILD="${BUILD}")
endif() endif()
endif() endif()
endif() #git endif() #git
@@ -33,7 +33,6 @@ if(PACKAGING)
add_definitions(-DPKG=1) add_definitions(-DPKG=1)
endif() endif()
add_library(clio) add_library(clio)
target_compile_features(clio PUBLIC cxx_std_20) target_compile_features(clio PUBLIC cxx_std_20)
target_include_directories(clio PUBLIC src) target_include_directories(clio PUBLIC src)
@@ -45,6 +44,7 @@ include(CMake/ClioVersion.cmake)
include(CMake/deps/rippled.cmake) include(CMake/deps/rippled.cmake)
include(CMake/deps/Boost.cmake) include(CMake/deps/Boost.cmake)
include(CMake/deps/cassandra.cmake) include(CMake/deps/cassandra.cmake)
include(CMake/deps/SourceLocation.cmake)
target_sources(clio PRIVATE target_sources(clio PRIVATE
## Main ## Main
@@ -99,10 +99,11 @@ target_sources(clio PRIVATE
src/rpc/handlers/Subscribe.cpp src/rpc/handlers/Subscribe.cpp
# Server # Server
src/rpc/handlers/ServerInfo.cpp src/rpc/handlers/ServerInfo.cpp
# Utility # Utilities
src/rpc/handlers/Random.cpp src/rpc/handlers/Random.cpp
src/util/Taggable.cpp src/config/Config.cpp
src/config/Config.cpp) src/log/Logger.cpp
src/util/Taggable.cpp)
add_executable(clio_server src/main/main.cpp) add_executable(clio_server src/main/main.cpp)
target_link_libraries(clio_server PUBLIC clio) target_link_libraries(clio_server PUBLIC clio)
@@ -110,8 +111,11 @@ target_link_libraries(clio_server PUBLIC clio)
if(BUILD_TESTS) if(BUILD_TESTS)
add_executable(clio_tests add_executable(clio_tests
unittests/RPCErrors.cpp unittests/RPCErrors.cpp
unittests/config.cpp unittests/Backend.cpp
unittests/main.cpp) unittests/Logger.cpp
unittests/Config.cpp
unittests/ProfilerTest.cpp
unittests/DOSGuard.cpp)
include(CMake/deps/gtest.cmake) include(CMake/deps/gtest.cmake)
endif() endif()

View File

@@ -22,7 +22,7 @@ from which data can be extracted. The rippled node does not need to be running o
## Building ## Building
Clio is built with CMake. Clio requires at least GCC-11 (C++20), and Boost 1.75.0 or later. Clio is built with CMake. Clio requires at least GCC-11/clang-14.0.0 (C++20), and Boost 1.75.0.
Use these instructions to build a Clio executable from the source. These instructions were tested on Ubuntu 20.04 LTS. Use these instructions to build a Clio executable from the source. These instructions were tested on Ubuntu 20.04 LTS.
@@ -72,6 +72,36 @@ server is running
to the IP of your Clio server. This entry can take the form of a comma-separated list if 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. you are running multiple Clio nodes.
In addition, the parameter `start_sequence` can be included and configured within the top level of the config file. This parameter specifies the sequence of first ledger to extract if the database is empty. Note that ETL extracts ledgers in order and that no backfilling functionality currently exists, meaning Clio will not retroactively learn ledgers older than the one you specify. Choosing to specify this or not will yield the following behavior:
- If this setting is absent and the database is empty, ETL will start with the next ledger validated by the network.
- If this setting is present and the database is not empty, an exception is thrown.
In addition, the optional parameter `finish_sequence` can be added to the json file as well, specifying where the ledger can stop.
To add `start_sequence` and/or `finish_sequence` to the config.json file appropriately, they will be on the same top level of precedence as other parameters (such as `database`, `etl_sources`, `read_only`, etc.) and be specified with an integer. Here is an example snippet from the config file:
```json
"start_sequence": 12345,
"finish_sequence": 54321
```
The parameters `ssl_cert_file` and `ssl_key_file` can also be added to the top level of precedence of our Clio config. `ssl_cert_file` specifies the filepath for your SSL cert while `ssl_key_file` specifies the filepath for your SSL key. It is up to you how to change ownership of these folders for your designated Clio user. Your options include:
- Copying the two files as root somewhere that's accessible by the Clio user, then running `sudo chown` to your user
- Changing the permissions directly so it's readable by your Clio user
- Running Clio as root (strongly discouraged)
An example of how to specify `ssl_cert_file` and `ssl_key_file` in the config:
```json
"server":{
"ip": "0.0.0.0",
"port": 51233
},
"ssl_cert_file" : "/full/path/to/cert.file",
"ssl_key_file" : "/full/path/to/key.file"
```
Once your config files are ready, start rippled and Clio. It doesn't matter which you Once your config files are ready, start rippled and Clio. It doesn't matter which you
start first, and it's fine to stop one or the other and restart at any given time. start first, and it's fine to stop one or the other and restart at any given time.
@@ -152,9 +182,33 @@ You must:
## Logging ## Logging
Clio provides several logging options, all are configurable via the config file and are detailed below. Clio provides several logging options, all are configurable via the config file and are detailed below.
`log_level`: The minimum level of severity at which the log message will be outputted. `log_level`: The minimum level of severity at which the log message will be outputted by default.
Severity options are `trace`, `debug`, `info`, `warning`, `error`, `fatal`. Defaults to `info`. Severity options are `trace`, `debug`, `info`, `warning`, `error`, `fatal`. Defaults to `info`.
`log_format`: The format of log lines produced by clio. Defaults to `"%TimeStamp% (%SourceLocation%) [%ThreadID%] %Channel%:%Severity% %Message%"`.
Each of the variables expands like so
- `TimeStamp`: The full date and time of the log entry
- `SourceLocation`: A partial path to the c++ file and the line number in said file (`source/file/path:linenumber`)
- `ThreadID`: The ID of the thread the log entry is written from
- `Channel`: The channel that this log entry was sent to
- `Severity`: The severity (aka log level) the entry was sent at
- `Message`: The actual log message
`log_channels`: An array of json objects, each overriding properties for a logging `channel`.
At the moment of writing, only `log_level` can be overriden using this mechanism.
Each object is of this format:
```json
{
"channel": "Backend",
"log_level": "fatal"
}
```
If no override is present for a given channel, that channel will log at the severity specified by the global `log_level`.
Overridable log channels: `Backend`, `WebServer`, `Subscriptions`, `RPC`, `ETL` and `Performance`.
> **Note:** See `example-config.json` for more details.
`log_to_console`: Enable/disable log output to console. Options are `true`/`false`. Defaults to true. `log_to_console`: Enable/disable log output to console. Options are `true`/`false`. Defaults to true.
`log_directory`: Path to the directory where log files are stored. If such directory doesn't exist, Clio will create it. If not specified, logs are not written to a file. `log_directory`: Path to the directory where log files are stored. If such directory doesn't exist, Clio will create it. If not specified, logs are not written to a file.
@@ -170,6 +224,11 @@ rotate the current log file. Defaults to 12 hours.
Note, time-based log rotation occurs dependently on size-based log rotation, where if a Note, time-based log rotation occurs dependently on size-based log rotation, where if a
size-based log rotation occurs, the timer for the time-based rotation will reset. size-based log rotation occurs, the timer for the time-based rotation will reset.
`log_tag_style`: Tag implementation to use. Must be one of:
- `uint`: Lock free and threadsafe but outputs just a simple unsigned integer
- `uuid`: Threadsafe and outputs a UUID tag
- `none`: Don't use tagging at all
## Cassandra / Scylla Administration ## Cassandra / Scylla Administration
Since Clio relies on either Cassandra or Scylla for its database backend, here are some important considerations: Since Clio relies on either Cassandra or Scylla for its database backend, here are some important considerations:

View File

@@ -1,46 +1,87 @@
{ {
"database": "database": {
{ "type": "cassandra",
"type":"cassandra", "cassandra": {
"cassandra": "contact_points": "127.0.0.1",
{ "port": 9042,
"contact_points":"127.0.0.1", "keyspace": "clio",
"port":9042, "replication_factor": 1,
"keyspace":"clio", "table_prefix": "",
"replication_factor":1, "max_write_requests_outstanding": 25000,
"table_prefix":"", "max_read_requests_outstanding": 30000,
"max_write_requests_outstanding":25000, "threads": 8
"max_read_requests_outstanding":30000,
"threads":8
} }
}, },
"etl_sources": "etl_sources": [
[
{ {
"ip":"127.0.0.1", "ip": "127.0.0.1",
"ws_port":"6006", "ws_port": "6006",
"grpc_port":"50051" "grpc_port": "50051"
} }
], ],
"dos_guard": "dos_guard":
{ {
"whitelist":["127.0.0.1"] "whitelist":["127.0.0.1"], // comma-separated list of ips to exclude from rate limiting
/* The below values are the default values and are only specified here
* for documentation purposes. The rate limiter currently limits
* connections and bandwidth per ip. The rate limiter looks at the raw
* ip of a client connection, and so requests routed through a load
* balancer will all have the same ip and be treated as a single client
*/
"max_fetches":100000000, // max bytes per ip per sweep interval
"max_connections":1, // max connections per ip
"sweep_interval": 10 // time in seconds before resetting bytes per ip count
}, },
"cache": "cache":
{ {
"peers": [{"ip":"127.0.0.1","port":51234}] "peers": [{"ip":"127.0.0.1","port":51234}]
}, },
"server":{ "server":{
"ip":"0.0.0.0", "ip": "0.0.0.0",
"port":51233 "port": 51233,
/* Max number of requests to queue up before rejecting further requests.
* Defaults to 0, which disables the limit
*/
"max_queue_size":500
}, },
"log_level":"debug", "log_channels": [
{
"channel": "Backend",
"log_level": "fatal"
},
{
"channel": "WebServer",
"log_level": "info"
},
{
"channel": "Subscriptions",
"log_level": "info"
},
{
"channel": "RPC",
"log_level": "error"
},
{
"channel": "ETL",
"log_level": "debug"
},
{
"channel": "Performance",
"log_level": "trace"
}
],
"log_level": "info",
"log_format": "%TimeStamp% (%SourceLocation%) [%ThreadID%] %Channel%:%Severity% %Message%", // This is the default format
"log_to_console": true, "log_to_console": true,
"log_directory":"./clio_log", "log_directory": "./clio_log",
"log_rotation_size": 2048, "log_rotation_size": 2048,
"log_directory_max_size": 51200, "log_directory_max_size": 51200,
"log_rotation_hour_interval": 12, "log_rotation_hour_interval": 12,
"log_tag_style": "uint", "log_tag_style": "uint",
"extractor_threads":8, "extractor_threads": 8,
"read_only":false "read_only": false,
//"start_sequence": [integer] the ledger index to start from,
//"finish_sequence": [integer] the ledger index to finish at,
//"ssl_cert_file" : "/full/path/to/cert.file",
//"ssl_key_file" : "/full/path/to/key.file"
} }

View File

@@ -1,16 +1,37 @@
#ifndef RIPPLE_APP_REPORTING_BACKENDFACTORY_H_INCLUDED //------------------------------------------------------------------------------
#define RIPPLE_APP_REPORTING_BACKENDFACTORY_H_INCLUDED /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <boost/algorithm/string.hpp>
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <backend/CassandraBackend.h> #include <backend/CassandraBackend.h>
#include <config/Config.h> #include <config/Config.h>
#include <log/Logger.h>
#include <boost/algorithm/string.hpp>
namespace Backend { namespace Backend {
std::shared_ptr<BackendInterface> std::shared_ptr<BackendInterface>
make_Backend(boost::asio::io_context& ioc, clio::Config const& config) make_Backend(boost::asio::io_context& ioc, clio::Config const& config)
{ {
BOOST_LOG_TRIVIAL(info) << __func__ << ": Constructing BackendInterface"; static clio::Logger log{"Backend"};
log.info() << "Constructing BackendInterface";
auto readOnly = config.valueOr("read_only", false); auto readOnly = config.valueOr("read_only", false);
auto type = config.value<std::string>("database.type"); auto type = config.value<std::string>("database.type");
@@ -34,11 +55,8 @@ make_Backend(boost::asio::io_context& ioc, clio::Config const& config)
backend->updateRange(rng->maxSequence); backend->updateRange(rng->maxSequence);
} }
BOOST_LOG_TRIVIAL(info) log.info() << "Constructed BackendInterface Successfully";
<< __func__ << ": Constructed BackendInterface Successfully";
return backend; return backend;
} }
} // namespace Backend } // namespace Backend
#endif // RIPPLE_REPORTING_BACKEND_FACTORY

View File

@@ -1,6 +1,34 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h> #include <ripple/protocol/STLedgerEntry.h>
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <log/Logger.h>
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gLog{"Backend"};
} // namespace
namespace Backend { namespace Backend {
bool bool
BackendInterface::finishWrites(std::uint32_t const ledgerSequence) BackendInterface::finishWrites(std::uint32_t const ledgerSequence)
@@ -26,7 +54,7 @@ std::optional<LedgerRange>
BackendInterface::hardFetchLedgerRangeNoThrow( BackendInterface::hardFetchLedgerRangeNoThrow(
boost::asio::yield_context& yield) const boost::asio::yield_context& yield) const
{ {
BOOST_LOG_TRIVIAL(trace) << __func__ << "(yield)"; gLog.trace() << "called";
while (true) while (true)
{ {
try try
@@ -43,7 +71,7 @@ BackendInterface::hardFetchLedgerRangeNoThrow(
std::optional<LedgerRange> std::optional<LedgerRange>
BackendInterface::hardFetchLedgerRangeNoThrow() const BackendInterface::hardFetchLedgerRangeNoThrow() const
{ {
BOOST_LOG_TRIVIAL(trace) << __func__ << "()"; gLog.trace() << "called";
return retryOnTimeout([&]() { return hardFetchLedgerRange(); }); return retryOnTimeout([&]() { return hardFetchLedgerRange(); });
} }
@@ -57,21 +85,17 @@ BackendInterface::fetchLedgerObject(
auto obj = cache_.get(key, sequence); auto obj = cache_.get(key, sequence);
if (obj) if (obj)
{ {
BOOST_LOG_TRIVIAL(trace) gLog.trace() << "Cache hit - " << ripple::strHex(key);
<< __func__ << " - cache hit - " << ripple::strHex(key);
return *obj; return *obj;
} }
else else
{ {
BOOST_LOG_TRIVIAL(trace) gLog.trace() << "Cache miss - " << ripple::strHex(key);
<< __func__ << " - cache miss - " << ripple::strHex(key);
auto dbObj = doFetchLedgerObject(key, sequence, yield); auto dbObj = doFetchLedgerObject(key, sequence, yield);
if (!dbObj) if (!dbObj)
BOOST_LOG_TRIVIAL(trace) gLog.trace() << "Missed cache and missed in db";
<< __func__ << " - missed cache and missed in db";
else else
BOOST_LOG_TRIVIAL(trace) gLog.trace() << "Missed cache but found in db";
<< __func__ << " - missed cache but found in db";
return dbObj; return dbObj;
} }
} }
@@ -93,9 +117,8 @@ BackendInterface::fetchLedgerObjects(
else else
misses.push_back(keys[i]); misses.push_back(keys[i]);
} }
BOOST_LOG_TRIVIAL(trace) gLog.trace() << "Cache hits = " << keys.size() - misses.size()
<< __func__ << " - cache hits = " << keys.size() - misses.size() << " - cache misses = " << misses.size();
<< " - cache misses = " << misses.size();
if (misses.size()) if (misses.size())
{ {
@@ -121,11 +144,9 @@ BackendInterface::fetchSuccessorKey(
{ {
auto succ = cache_.getSuccessor(key, ledgerSequence); auto succ = cache_.getSuccessor(key, ledgerSequence);
if (succ) if (succ)
BOOST_LOG_TRIVIAL(trace) gLog.trace() << "Cache hit - " << ripple::strHex(key);
<< __func__ << " - cache hit - " << ripple::strHex(key);
else else
BOOST_LOG_TRIVIAL(trace) gLog.trace() << "Cache miss - " << ripple::strHex(key);
<< __func__ << " - cache miss - " << ripple::strHex(key);
return succ ? succ->key : doFetchSuccessorKey(key, ledgerSequence, yield); return succ ? succ->key : doFetchSuccessorKey(key, ledgerSequence, yield);
} }
@@ -179,8 +200,8 @@ BackendInterface::fetchBookOffers(
succMillis += getMillis(mid2 - mid1); succMillis += getMillis(mid2 - mid1);
if (!offerDir || offerDir->key >= bookEnd) if (!offerDir || offerDir->key >= bookEnd)
{ {
BOOST_LOG_TRIVIAL(trace) << __func__ << " - offerDir.has_value() " gLog.trace() << "offerDir.has_value() " << offerDir.has_value()
<< offerDir.has_value() << " breaking"; << " breaking";
break; break;
} }
uTipIndex = offerDir->key; uTipIndex = offerDir->key;
@@ -196,8 +217,7 @@ BackendInterface::fetchBookOffers(
auto next = sle.getFieldU64(ripple::sfIndexNext); auto next = sle.getFieldU64(ripple::sfIndexNext);
if (!next) if (!next)
{ {
BOOST_LOG_TRIVIAL(trace) gLog.trace() << "Next is empty. breaking";
<< __func__ << " next is empty. breaking";
break; break;
} }
auto nextKey = ripple::keylet::page(uTipIndex, next); auto nextKey = ripple::keylet::page(uTipIndex, next);
@@ -214,29 +234,27 @@ BackendInterface::fetchBookOffers(
auto objs = fetchLedgerObjects(keys, ledgerSequence, yield); auto objs = fetchLedgerObjects(keys, ledgerSequence, yield);
for (size_t i = 0; i < keys.size() && i < limit; ++i) for (size_t i = 0; i < keys.size() && i < limit; ++i)
{ {
BOOST_LOG_TRIVIAL(trace) gLog.trace() << "Key = " << ripple::strHex(keys[i])
<< __func__ << " key = " << ripple::strHex(keys[i]) << " blob = " << ripple::strHex(objs[i])
<< " blob = " << ripple::strHex(objs[i]) << " ledgerSequence = " << ledgerSequence;
<< " ledgerSequence = " << ledgerSequence;
assert(objs[i].size()); assert(objs[i].size());
page.offers.push_back({keys[i], objs[i]}); page.offers.push_back({keys[i], objs[i]});
} }
auto end = std::chrono::system_clock::now(); auto end = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(debug) gLog.debug() << "Fetching " << std::to_string(keys.size())
<< __func__ << " " << " offers took " << std::to_string(getMillis(mid - begin))
<< "Fetching " << std::to_string(keys.size()) << " offers took " << " milliseconds. Fetching next dir took "
<< std::to_string(getMillis(mid - begin)) << std::to_string(succMillis)
<< " milliseconds. Fetching next dir took " << " milliseonds. Fetched next dir " << std::to_string(numSucc)
<< std::to_string(succMillis) << " milliseonds. Fetched next dir " << " times"
<< std::to_string(numSucc) << " times" << " Fetching next page of dir took "
<< " Fetching next page of dir took " << std::to_string(pageMillis) << std::to_string(pageMillis) << " milliseconds"
<< " milliseconds" << ". num pages = " << std::to_string(numPages)
<< ". num pages = " << std::to_string(numPages) << ". Fetching all objects took "
<< ". Fetching all objects took " << std::to_string(getMillis(end - mid))
<< std::to_string(getMillis(end - mid)) << " milliseconds. total time = "
<< " milliseconds. total time = " << std::to_string(getMillis(end - begin)) << " milliseconds"
<< std::to_string(getMillis(end - begin)) << " milliseconds" << " book = " << ripple::strHex(book);
<< " book = " << ripple::strHex(book);
return page; return page;
} }
@@ -274,16 +292,15 @@ BackendInterface::fetchLedgerPage(
page.objects.push_back({std::move(keys[i]), std::move(objects[i])}); page.objects.push_back({std::move(keys[i]), std::move(objects[i])});
else if (!outOfOrder) else if (!outOfOrder)
{ {
BOOST_LOG_TRIVIAL(error) gLog.error()
<< __func__ << "Deleted or non-existent object in successor table. key = "
<< " deleted or non-existent object in successor table. key = "
<< ripple::strHex(keys[i]) << " - seq = " << ledgerSequence; << ripple::strHex(keys[i]) << " - seq = " << ledgerSequence;
std::stringstream msg; std::stringstream msg;
for (size_t j = 0; j < objects.size(); ++j) for (size_t j = 0; j < objects.size(); ++j)
{ {
msg << " - " << ripple::strHex(keys[j]); msg << " - " << ripple::strHex(keys[j]);
} }
BOOST_LOG_TRIVIAL(error) << __func__ << msg.str(); gLog.error() << msg.str();
} }
} }
if (keys.size() && !reachedEnd) if (keys.size() && !reachedEnd)
@@ -304,7 +321,7 @@ BackendInterface::fetchFees(
if (!bytes) if (!bytes)
{ {
BOOST_LOG_TRIVIAL(error) << __func__ << " - could not find fees"; gLog.error() << "Could not find fees";
return {}; return {};
} }

View File

@@ -1,17 +1,33 @@
#ifndef RIPPLE_APP_REPORTING_BACKENDINTERFACE_H_INCLUDED //------------------------------------------------------------------------------
#define RIPPLE_APP_REPORTING_BACKENDINTERFACE_H_INCLUDED /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, the clio developers.
#include <boost/asio/spawn.hpp> Permission to use, copy, modify, and distribute this software for any
#include <boost/json.hpp> purpose with or without fee is hereby granted, provided that the above
#include <boost/log/trivial.hpp> 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 <ripple/ledger/ReadView.h> #include <ripple/ledger/ReadView.h>
#include <backend/DBHelpers.h> #include <backend/DBHelpers.h>
#include <backend/SimpleCache.h> #include <backend/SimpleCache.h>
#include <backend/Types.h> #include <backend/Types.h>
#include <config/Config.h> #include <config/Config.h>
#include <log/Logger.h>
#include <boost/asio/spawn.hpp>
#include <boost/json.hpp>
#include <thread> #include <thread>
#include <type_traits> #include <type_traits>
@@ -46,6 +62,8 @@ template <class F>
auto auto
retryOnTimeout(F func, size_t waitMs = 500) retryOnTimeout(F func, size_t waitMs = 500)
{ {
static clio::Logger log{"Backend"};
while (true) while (true)
{ {
try try
@@ -54,9 +72,8 @@ retryOnTimeout(F func, size_t waitMs = 500)
} }
catch (DatabaseTimeout& t) catch (DatabaseTimeout& t)
{ {
BOOST_LOG_TRIVIAL(error) log.error()
<< __func__ << "Database request timed out. Sleeping and retrying ... ";
<< " Database request timed out. Sleeping and retrying ... ";
std::this_thread::sleep_for(std::chrono::milliseconds(waitMs)); std::this_thread::sleep_for(std::chrono::milliseconds(waitMs));
} }
} }
@@ -644,4 +661,3 @@ private:
} // namespace Backend } // namespace Backend
using BackendInterface = Backend::BackendInterface; using BackendInterface = Backend::BackendInterface;
#endif

View File

@@ -1,9 +1,33 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/app/tx/impl/details/NFTokenUtils.h> #include <ripple/app/tx/impl/details/NFTokenUtils.h>
#include <backend/CassandraBackend.h> #include <backend/CassandraBackend.h>
#include <backend/DBHelpers.h> #include <backend/DBHelpers.h>
#include <log/Logger.h>
#include <util/Profiler.h>
#include <functional> #include <functional>
#include <unordered_map> #include <unordered_map>
using namespace clio;
namespace Backend { namespace Backend {
// Type alias for async completion handlers // Type alias for async completion handlers
@@ -16,6 +40,8 @@ template <class T, class F>
void void
processAsyncWriteResponse(T& requestParams, CassFuture* fut, F func) processAsyncWriteResponse(T& requestParams, CassFuture* fut, F func)
{ {
static clio::Logger log{"Backend"};
CassandraBackend const& backend = *requestParams.backend; CassandraBackend const& backend = *requestParams.backend;
auto rc = cass_future_error_code(fut); auto rc = cass_future_error_code(fut);
if (rc != CASS_OK) if (rc != CASS_OK)
@@ -23,11 +49,11 @@ processAsyncWriteResponse(T& requestParams, CassFuture* fut, F func)
// exponential backoff with a max wait of 2^10 ms (about 1 second) // exponential backoff with a max wait of 2^10 ms (about 1 second)
auto wait = std::chrono::milliseconds( auto wait = std::chrono::milliseconds(
lround(std::pow(2, std::min(10u, requestParams.currentRetries)))); lround(std::pow(2, std::min(10u, requestParams.currentRetries))));
BOOST_LOG_TRIVIAL(error) log.error() << "ERROR!!! Cassandra write error: " << rc << ", "
<< "ERROR!!! Cassandra write error: " << rc << ", " << cass_error_desc(rc)
<< cass_error_desc(rc) << " id= " << requestParams.toString() << " id= " << requestParams.toString()
<< ", current retries " << requestParams.currentRetries << ", current retries " << requestParams.currentRetries
<< ", retrying in " << wait.count() << " milliseconds"; << ", retrying in " << wait.count() << " milliseconds";
++requestParams.currentRetries; ++requestParams.currentRetries;
std::shared_ptr<boost::asio::steady_timer> timer = std::shared_ptr<boost::asio::steady_timer> timer =
std::make_shared<boost::asio::steady_timer>( std::make_shared<boost::asio::steady_timer>(
@@ -40,11 +66,11 @@ processAsyncWriteResponse(T& requestParams, CassFuture* fut, F func)
} }
else else
{ {
BOOST_LOG_TRIVIAL(trace) log.trace() << "Succesfully inserted a record";
<< __func__ << " Succesfully inserted a record";
requestParams.finish(); requestParams.finish();
} }
} }
template <class T> template <class T>
void void
processAsyncWrite(CassFuture* fut, void* cbData) processAsyncWrite(CassFuture* fut, void* cbData)
@@ -155,6 +181,7 @@ makeAndExecuteAsyncWrite(
auto* cb = new WriteCallbackData<T, B>(b, std::move(d), bind, id); auto* cb = new WriteCallbackData<T, B>(b, std::move(d), bind, id);
cb->start(); cb->start();
} }
template <class T, class B> template <class T, class B>
std::shared_ptr<BulkWriteCallbackData<T, B>> std::shared_ptr<BulkWriteCallbackData<T, B>>
makeAndExecuteBulkAsyncWrite( makeAndExecuteBulkAsyncWrite(
@@ -170,13 +197,14 @@ makeAndExecuteBulkAsyncWrite(
cb->start(); cb->start();
return cb; return cb;
} }
void void
CassandraBackend::doWriteLedgerObject( CassandraBackend::doWriteLedgerObject(
std::string&& key, std::string&& key,
std::uint32_t const seq, std::uint32_t const seq,
std::string&& blob) std::string&& blob)
{ {
BOOST_LOG_TRIVIAL(trace) << "Writing ledger object to cassandra"; log_.trace() << "Writing ledger object to cassandra";
if (range) if (range)
makeAndExecuteAsyncWrite( makeAndExecuteAsyncWrite(
this, this,
@@ -204,15 +232,16 @@ CassandraBackend::doWriteLedgerObject(
}, },
"ledger_object"); "ledger_object");
} }
void void
CassandraBackend::writeSuccessor( CassandraBackend::writeSuccessor(
std::string&& key, std::string&& key,
std::uint32_t const seq, std::uint32_t const seq,
std::string&& successor) std::string&& successor)
{ {
BOOST_LOG_TRIVIAL(trace) log_.trace() << "Writing successor. key = " << key.size() << " bytes. "
<< "Writing successor. key = " << key << " seq = " << std::to_string(seq)
<< " seq = " << std::to_string(seq) << " successor = " << successor; << " successor = " << successor.size() << " bytes.";
assert(key.size() != 0); assert(key.size() != 0);
assert(successor.size() != 0); assert(successor.size() != 0);
makeAndExecuteAsyncWrite( makeAndExecuteAsyncWrite(
@@ -319,7 +348,7 @@ CassandraBackend::writeTransaction(
std::string&& transaction, std::string&& transaction,
std::string&& metadata) std::string&& metadata)
{ {
BOOST_LOG_TRIVIAL(trace) << "Writing txn to cassandra"; log_.trace() << "Writing txn to cassandra";
std::string hashCpy = hash; std::string hashCpy = hash;
makeAndExecuteAsyncWrite( makeAndExecuteAsyncWrite(
@@ -393,13 +422,13 @@ CassandraBackend::writeNFTs(std::vector<NFTsData>&& data)
std::optional<LedgerRange> std::optional<LedgerRange>
CassandraBackend::hardFetchLedgerRange(boost::asio::yield_context& yield) const CassandraBackend::hardFetchLedgerRange(boost::asio::yield_context& yield) const
{ {
BOOST_LOG_TRIVIAL(trace) << "Fetching from cassandra"; log_.trace() << "Fetching from cassandra";
CassandraStatement statement{selectLedgerRange_}; CassandraStatement statement{selectLedgerRange_};
CassandraResult result = executeAsyncRead(statement, yield); CassandraResult result = executeAsyncRead(statement, yield);
if (!result) if (!result)
{ {
BOOST_LOG_TRIVIAL(error) << __func__ << " - no rows"; log_.error() << "No rows";
return {}; return {};
} }
LedgerRange range; LedgerRange range;
@@ -496,43 +525,39 @@ CassandraBackend::fetchTransactions(
std::vector<TransactionAndMetadata> results{numHashes}; std::vector<TransactionAndMetadata> results{numHashes};
std::vector<std::shared_ptr<ReadCallbackData<result_type>>> cbs; std::vector<std::shared_ptr<ReadCallbackData<result_type>>> cbs;
cbs.reserve(numHashes); cbs.reserve(numHashes);
auto start = std::chrono::system_clock::now(); auto timeDiff = util::timed([&]() {
for (std::size_t i = 0; i < hashes.size(); ++i)
{
CassandraStatement statement{selectTransaction_};
statement.bindNextBytes(hashes[i]);
for (std::size_t i = 0; i < hashes.size(); ++i) cbs.push_back(std::make_shared<ReadCallbackData<result_type>>(
{ numOutstanding, handler, [i, &results](auto& result) {
CassandraStatement statement{selectTransaction_}; if (result.hasResult())
statement.bindNextBytes(hashes[i]); results[i] = {
result.getBytes(),
result.getBytes(),
result.getUInt32(),
result.getUInt32()};
}));
cbs.push_back(std::make_shared<ReadCallbackData<result_type>>( executeAsyncRead(statement, processAsyncRead, *cbs[i]);
numOutstanding, handler, [i, &results](auto& result) { }
if (result.hasResult()) assert(results.size() == cbs.size());
results[i] = {
result.getBytes(),
result.getBytes(),
result.getUInt32(),
result.getUInt32()};
}));
executeAsyncRead(statement, processAsyncRead, *cbs[i]); // suspend the coroutine until completion handler is called.
} result.get();
assert(results.size() == cbs.size()); numReadRequestsOutstanding_ -= hashes.size();
});
// suspend the coroutine until completion handler is called.
result.get();
numReadRequestsOutstanding_ -= hashes.size();
auto end = std::chrono::system_clock::now();
for (auto const& cb : cbs) for (auto const& cb : cbs)
{ {
if (cb->errored) if (cb->errored)
throw DatabaseTimeout(); throw DatabaseTimeout();
} }
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Fetched " << numHashes
<< "Fetched " << numHashes << " transactions from Cassandra in " << " transactions from Cassandra in " << timeDiff
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start) << " milliseconds";
.count()
<< " milliseconds";
return results; return results;
} }
@@ -550,9 +575,7 @@ CassandraBackend::fetchAllTransactionHashesInLedger(
auto end = std::chrono::system_clock::now(); auto end = std::chrono::system_clock::now();
if (!result) if (!result)
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "No rows. Ledger = " << std::to_string(ledgerSequence);
<< __func__
<< " - no rows . ledger = " << std::to_string(ledgerSequence);
return {}; return {};
} }
std::vector<ripple::uint256> hashes; std::vector<ripple::uint256> hashes;
@@ -560,12 +583,12 @@ CassandraBackend::fetchAllTransactionHashesInLedger(
{ {
hashes.push_back(result.getUInt256()); hashes.push_back(result.getUInt256());
} while (result.nextRow()); } while (result.nextRow());
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Fetched " << hashes.size()
<< "Fetched " << hashes.size() << " transaction hashes from Cassandra in "
<< " transaction hashes from Cassandra in " << std::chrono::duration_cast<std::chrono::milliseconds>(
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start) end - start)
.count() .count()
<< " milliseconds"; << " milliseconds";
return hashes; return hashes;
} }
@@ -613,9 +636,9 @@ CassandraBackend::fetchNFTTransactions(
{ {
statement.bindNextIntTuple( statement.bindNextIntTuple(
cursor->ledgerSequence, cursor->transactionIndex); cursor->ledgerSequence, cursor->transactionIndex);
BOOST_LOG_TRIVIAL(debug) << " token_id = " << ripple::strHex(tokenID) log_.debug() << "token_id = " << ripple::strHex(tokenID)
<< " tuple = " << cursor->ledgerSequence << " tuple = " << cursor->ledgerSequence
<< " : " << cursor->transactionIndex; << cursor->transactionIndex;
} }
else else
{ {
@@ -624,9 +647,8 @@ CassandraBackend::fetchNFTTransactions(
forward ? 0 : std::numeric_limits<std::uint32_t>::max(); forward ? 0 : std::numeric_limits<std::uint32_t>::max();
statement.bindNextIntTuple(placeHolder, placeHolder); statement.bindNextIntTuple(placeHolder, placeHolder);
BOOST_LOG_TRIVIAL(debug) log_.debug() << "token_id = " << ripple::strHex(tokenID)
<< " token_id = " << ripple::strHex(tokenID) << " idx = " << seq << " idx = " << seq << " tuple = " << placeHolder;
<< " tuple = " << placeHolder;
} }
statement.bindNextUInt(limit); statement.bindNextUInt(limit);
@@ -635,19 +657,19 @@ CassandraBackend::fetchNFTTransactions(
if (!result.hasResult()) if (!result.hasResult())
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " - no rows returned"; log_.debug() << "No rows returned";
return {}; return {};
} }
std::vector<ripple::uint256> hashes = {}; std::vector<ripple::uint256> hashes = {};
auto numRows = result.numRows(); auto numRows = result.numRows();
BOOST_LOG_TRIVIAL(info) << "num_rows = " << numRows; log_.info() << "num_rows = " << numRows;
do do
{ {
hashes.push_back(result.getUInt256()); hashes.push_back(result.getUInt256());
if (--numRows == 0) if (--numRows == 0)
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " setting cursor"; log_.debug() << "Setting cursor";
auto const [lgrSeq, txnIdx] = result.getInt64Tuple(); auto const [lgrSeq, txnIdx] = result.getInt64Tuple();
cursor = { cursor = {
static_cast<std::uint32_t>(lgrSeq), static_cast<std::uint32_t>(lgrSeq),
@@ -662,11 +684,11 @@ CassandraBackend::fetchNFTTransactions(
} while (result.nextRow()); } while (result.nextRow());
auto txns = fetchTransactions(hashes, yield); auto txns = fetchTransactions(hashes, yield);
BOOST_LOG_TRIVIAL(debug) << __func__ << " txns = " << txns.size(); log_.debug() << "Txns = " << txns.size();
if (txns.size() == limit) if (txns.size() == limit)
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " returning cursor"; log_.debug() << "Returning cursor";
return {txns, cursor}; return {txns, cursor};
} }
@@ -698,9 +720,9 @@ CassandraBackend::fetchAccountTransactions(
{ {
statement.bindNextIntTuple( statement.bindNextIntTuple(
cursor->ledgerSequence, cursor->transactionIndex); cursor->ledgerSequence, cursor->transactionIndex);
BOOST_LOG_TRIVIAL(debug) << " account = " << ripple::strHex(account) log_.debug() << "account = " << ripple::strHex(account)
<< " tuple = " << cursor->ledgerSequence << " tuple = " << cursor->ledgerSequence
<< " : " << cursor->transactionIndex; << cursor->transactionIndex;
} }
else else
{ {
@@ -709,9 +731,8 @@ CassandraBackend::fetchAccountTransactions(
forward ? 0 : std::numeric_limits<std::uint32_t>::max(); forward ? 0 : std::numeric_limits<std::uint32_t>::max();
statement.bindNextIntTuple(placeHolder, placeHolder); statement.bindNextIntTuple(placeHolder, placeHolder);
BOOST_LOG_TRIVIAL(debug) log_.debug() << "account = " << ripple::strHex(account)
<< " account = " << ripple::strHex(account) << " idx = " << seq << " idx = " << seq << " tuple = " << placeHolder;
<< " tuple = " << placeHolder;
} }
statement.bindNextUInt(limit); statement.bindNextUInt(limit);
@@ -719,19 +740,19 @@ CassandraBackend::fetchAccountTransactions(
if (!result.hasResult()) if (!result.hasResult())
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " - no rows returned"; log_.debug() << "No rows returned";
return {}; return {};
} }
std::vector<ripple::uint256> hashes = {}; std::vector<ripple::uint256> hashes = {};
auto numRows = result.numRows(); auto numRows = result.numRows();
BOOST_LOG_TRIVIAL(info) << "num_rows = " << std::to_string(numRows); log_.info() << "num_rows = " << std::to_string(numRows);
do do
{ {
hashes.push_back(result.getUInt256()); hashes.push_back(result.getUInt256());
if (--numRows == 0) if (--numRows == 0)
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " setting cursor"; log_.debug() << "Setting cursor";
auto [lgrSeq, txnIdx] = result.getInt64Tuple(); auto [lgrSeq, txnIdx] = result.getInt64Tuple();
cursor = { cursor = {
static_cast<std::uint32_t>(lgrSeq), static_cast<std::uint32_t>(lgrSeq),
@@ -746,11 +767,11 @@ CassandraBackend::fetchAccountTransactions(
} while (result.nextRow()); } while (result.nextRow());
auto txns = fetchTransactions(hashes, yield); auto txns = fetchTransactions(hashes, yield);
BOOST_LOG_TRIVIAL(debug) << __func__ << "txns = " << txns.size(); log_.debug() << "Txns = " << txns.size();
if (txns.size() == limit) if (txns.size() == limit)
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " returning cursor"; log_.debug() << "Returning cursor";
return {txns, cursor}; return {txns, cursor};
} }
@@ -763,7 +784,7 @@ CassandraBackend::doFetchSuccessorKey(
std::uint32_t const ledgerSequence, std::uint32_t const ledgerSequence,
boost::asio::yield_context& yield) const boost::asio::yield_context& yield) const
{ {
BOOST_LOG_TRIVIAL(trace) << "Fetching from cassandra"; log_.trace() << "Fetching from cassandra";
CassandraStatement statement{selectSuccessor_}; CassandraStatement statement{selectSuccessor_};
statement.bindNextBytes(key); statement.bindNextBytes(key);
statement.bindNextInt(ledgerSequence); statement.bindNextInt(ledgerSequence);
@@ -772,7 +793,7 @@ CassandraBackend::doFetchSuccessorKey(
if (!result) if (!result)
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " - no rows"; log_.debug() << "No rows";
return {}; return {};
} }
auto next = result.getUInt256(); auto next = result.getUInt256();
@@ -787,7 +808,7 @@ CassandraBackend::doFetchLedgerObject(
std::uint32_t const sequence, std::uint32_t const sequence,
boost::asio::yield_context& yield) const boost::asio::yield_context& yield) const
{ {
BOOST_LOG_TRIVIAL(trace) << "Fetching from cassandra"; log_.trace() << "Fetching from cassandra";
CassandraStatement statement{selectObject_}; CassandraStatement statement{selectObject_};
statement.bindNextBytes(key); statement.bindNextBytes(key);
statement.bindNextInt(sequence); statement.bindNextInt(sequence);
@@ -796,7 +817,7 @@ CassandraBackend::doFetchLedgerObject(
if (!result) if (!result)
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " - no rows"; log_.debug() << "No rows";
return {}; return {};
} }
auto res = result.getBytes(); auto res = result.getBytes();
@@ -820,8 +841,7 @@ CassandraBackend::doFetchLedgerObjects(
result_type result(handler); result_type result(handler);
std::size_t const numKeys = keys.size(); std::size_t const numKeys = keys.size();
BOOST_LOG_TRIVIAL(trace) log_.trace() << "Fetching " << numKeys << " records from Cassandra";
<< "Fetching " << numKeys << " records from Cassandra";
std::atomic_int numOutstanding = numKeys; std::atomic_int numOutstanding = numKeys;
std::vector<Blob> results{numKeys}; std::vector<Blob> results{numKeys};
std::vector<std::shared_ptr<ReadCallbackData<result_type>>> cbs; std::vector<std::shared_ptr<ReadCallbackData<result_type>>> cbs;
@@ -850,8 +870,7 @@ CassandraBackend::doFetchLedgerObjects(
throw DatabaseTimeout(); throw DatabaseTimeout();
} }
BOOST_LOG_TRIVIAL(trace) log_.trace() << "Fetched " << numKeys << " records from Cassandra";
<< "Fetched " << numKeys << " records from Cassandra";
return results; return results;
} }
@@ -870,9 +889,7 @@ CassandraBackend::fetchLedgerDiff(
if (!result) if (!result)
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "No rows. Ledger = " << std::to_string(ledgerSequence);
<< __func__
<< " - no rows . ledger = " << std::to_string(ledgerSequence);
return {}; return {};
} }
std::vector<ripple::uint256> keys; std::vector<ripple::uint256> keys;
@@ -880,11 +897,12 @@ CassandraBackend::fetchLedgerDiff(
{ {
keys.push_back(result.getUInt256()); keys.push_back(result.getUInt256());
} while (result.nextRow()); } while (result.nextRow());
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Fetched " << keys.size()
<< "Fetched " << keys.size() << " diff hashes from Cassandra in " << " diff hashes from Cassandra in "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start) << std::chrono::duration_cast<std::chrono::milliseconds>(
.count() end - start)
<< " milliseconds"; .count()
<< " milliseconds";
auto objs = fetchLedgerObjects(keys, ledgerSequence, yield); auto objs = fetchLedgerObjects(keys, ledgerSequence, yield);
std::vector<LedgerObject> results; std::vector<LedgerObject> results;
std::transform( std::transform(
@@ -951,12 +969,12 @@ CassandraBackend::doOnlineDelete(
cv)); cv));
std::unique_lock<std::mutex> lck(mtx); std::unique_lock<std::mutex> lck(mtx);
BOOST_LOG_TRIVIAL(trace) << __func__ << "Got the mutex"; log_.trace() << "Got the mutex";
cv.wait(lck, [&numOutstanding, concurrentLimit]() { cv.wait(lck, [&numOutstanding, concurrentLimit]() {
return numOutstanding < concurrentLimit; return numOutstanding < concurrentLimit;
}); });
} }
BOOST_LOG_TRIVIAL(debug) << __func__ << " fetched a page"; log_.debug() << "Fetched a page";
cursor = curCursor; cursor = curCursor;
if (!cursor) if (!cursor)
break; break;
@@ -982,11 +1000,11 @@ CassandraBackend::open(bool readOnly)
if (open_) if (open_)
{ {
assert(false); assert(false);
BOOST_LOG_TRIVIAL(error) << "database is already open"; log_.error() << "Database is already open";
return; return;
} }
BOOST_LOG_TRIVIAL(info) << "Opening Cassandra Backend"; log_.info() << "Opening Cassandra Backend";
CassCluster* cluster = cass_cluster_new(); CassCluster* cluster = cass_cluster_new();
if (!cluster) if (!cluster)
@@ -1002,9 +1020,9 @@ CassandraBackend::open(bool readOnly)
if (cass_cluster_set_cloud_secure_connection_bundle( if (cass_cluster_set_cloud_secure_connection_bundle(
cluster, secureConnectBundle.c_str()) != CASS_OK) cluster, secureConnectBundle.c_str()) != CASS_OK)
{ {
BOOST_LOG_TRIVIAL(error) << "Unable to configure cloud using the " log_.error() << "Unable to configure cloud using the "
"secure connection bundle: " "secure connection bundle: "
<< secureConnectBundle; << secureConnectBundle;
throw std::runtime_error( throw std::runtime_error(
"nodestore: Failed to connect using secure connection " "nodestore: Failed to connect using secure connection "
"bundle"); "bundle");
@@ -1057,7 +1075,7 @@ CassandraBackend::open(bool readOnly)
auto username = config_.maybeValue<std::string>("username"); auto username = config_.maybeValue<std::string>("username");
if (username) if (username)
{ {
BOOST_LOG_TRIVIAL(debug) << "user = " << *username; log_.debug() << "user = " << *username;
auto password = config_.value<std::string>("password"); auto password = config_.value<std::string>("password");
cass_cluster_set_credentials( cass_cluster_set_credentials(
cluster, username->c_str(), password.c_str()); cluster, username->c_str(), password.c_str());
@@ -1080,10 +1098,11 @@ CassandraBackend::open(bool readOnly)
"max_read_requests_outstanding", maxReadRequestsOutstanding); "max_read_requests_outstanding", maxReadRequestsOutstanding);
syncInterval_ = config_.valueOr<int>("sync_interval", syncInterval_); syncInterval_ = config_.valueOr<int>("sync_interval", syncInterval_);
BOOST_LOG_TRIVIAL(info) log_.info() << "Sync interval is " << syncInterval_
<< __func__ << " sync interval is " << syncInterval_ << ". max write requests outstanding is "
<< ". max write requests outstanding is " << maxWriteRequestsOutstanding << maxWriteRequestsOutstanding
<< ". max read requests outstanding is " << maxReadRequestsOutstanding; << ". max read requests outstanding is "
<< maxReadRequestsOutstanding;
cass_cluster_set_request_timeout(cluster, 10000); cass_cluster_set_request_timeout(cluster, 10000);
@@ -1098,7 +1117,7 @@ CassandraBackend::open(bool readOnly)
ss << "nodestore: Error setting Cassandra max core connections per " ss << "nodestore: Error setting Cassandra max core connections per "
"host" "host"
<< ", result: " << rc << ", " << cass_error_desc(rc); << ", result: " << rc << ", " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << ss.str(); log_.error() << ss.str();
throw std::runtime_error(ss.str()); throw std::runtime_error(ss.str());
} }
@@ -1140,8 +1159,7 @@ CassandraBackend::open(bool readOnly)
auto keyspace = config_.valueOr<std::string>("keyspace", ""); auto keyspace = config_.valueOr<std::string>("keyspace", "");
if (keyspace.empty()) if (keyspace.empty())
{ {
BOOST_LOG_TRIVIAL(warning) log_.warn() << "No keyspace specified. Using keyspace clio";
<< "No keyspace specified. Using keyspace clio";
keyspace = "clio"; keyspace = "clio";
} }
@@ -1149,14 +1167,13 @@ CassandraBackend::open(bool readOnly)
auto tablePrefix = config_.valueOr<std::string>("table_prefix", ""); auto tablePrefix = config_.valueOr<std::string>("table_prefix", "");
if (tablePrefix.empty()) if (tablePrefix.empty())
{ {
BOOST_LOG_TRIVIAL(warning) << "Table prefix is empty"; log_.warn() << "Table prefix is empty";
} }
cass_cluster_set_connect_timeout(cluster, 10000); cass_cluster_set_connect_timeout(cluster, 10000);
auto ttl = ttl_ * 2; auto ttl = ttl_ * 2;
BOOST_LOG_TRIVIAL(info) log_.info() << "Setting ttl to " << std::to_string(ttl);
<< __func__ << " setting ttl to " << std::to_string(ttl);
auto executeSimpleStatement = [this](std::string const& query) { auto executeSimpleStatement = [this](std::string const& query) {
CassStatement* statement = makeStatement(query.c_str(), 0); CassStatement* statement = makeStatement(query.c_str(), 0);
@@ -1169,7 +1186,7 @@ CassandraBackend::open(bool readOnly)
std::stringstream ss; std::stringstream ss;
ss << "nodestore: Error executing simple statement: " << rc << ", " ss << "nodestore: Error executing simple statement: " << rc << ", "
<< cass_error_desc(rc) << " - " << query; << cass_error_desc(rc) << " - " << query;
BOOST_LOG_TRIVIAL(error) << ss.str(); log_.error() << ss.str();
return false; return false;
} }
return true; return true;
@@ -1192,7 +1209,7 @@ CassandraBackend::open(bool readOnly)
ss << "nodestore: Error connecting Cassandra session keyspace: " ss << "nodestore: Error connecting Cassandra session keyspace: "
<< rc << ", " << cass_error_desc(rc) << rc << ", " << cass_error_desc(rc)
<< ", trying to create it ourselves"; << ", trying to create it ourselves";
BOOST_LOG_TRIVIAL(error) << ss.str(); log_.error() << ss.str();
// if the keyspace doesn't exist, try to create it // if the keyspace doesn't exist, try to create it
session_.reset(cass_session_new()); session_.reset(cass_session_new());
fut = cass_session_connect(session_.get(), cluster); fut = cass_session_connect(session_.get(), cluster);
@@ -1203,7 +1220,7 @@ CassandraBackend::open(bool readOnly)
std::stringstream ss; std::stringstream ss;
ss << "nodestore: Error connecting Cassandra session at all: " ss << "nodestore: Error connecting Cassandra session at all: "
<< rc << ", " << cass_error_desc(rc); << rc << ", " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << ss.str(); log_.error() << ss.str();
} }
else else
{ {
@@ -1626,6 +1643,6 @@ CassandraBackend::open(bool readOnly)
open_ = true; open_ = true;
BOOST_LOG_TRIVIAL(info) << "Opened CassandraBackend successfully"; log_.info() << "Opened CassandraBackend successfully";
} }
} // namespace Backend } // namespace Backend

View File

@@ -1,17 +1,38 @@
#ifndef RIPPLE_APP_REPORTING_CASSANDRABACKEND_H_INCLUDED //------------------------------------------------------------------------------
#define RIPPLE_APP_REPORTING_CASSANDRABACKEND_H_INCLUDED /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/basics/base_uint.h> #include <ripple/basics/base_uint.h>
#include <backend/BackendInterface.h>
#include <backend/DBHelpers.h>
#include <log/Logger.h>
#include <cassandra.h>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/asio/async_result.hpp> #include <boost/asio/async_result.hpp>
#include <boost/asio/spawn.hpp> #include <boost/asio/spawn.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/json.hpp> #include <boost/json.hpp>
#include <boost/log/trivial.hpp>
#include <atomic> #include <atomic>
#include <backend/BackendInterface.h>
#include <backend/DBHelpers.h>
#include <cassandra.h>
#include <cstddef> #include <cstddef>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
@@ -25,6 +46,7 @@ namespace Backend {
class CassandraPreparedStatement class CassandraPreparedStatement
{ {
private: private:
clio::Logger log_{"Backend"};
CassPrepared const* prepared_ = nullptr; CassPrepared const* prepared_ = nullptr;
public: public:
@@ -65,7 +87,7 @@ public:
std::stringstream ss; std::stringstream ss;
ss << "nodestore: Error preparing statement : " << rc << ", " ss << "nodestore: Error preparing statement : " << rc << ", "
<< cass_error_desc(rc) << ". query : " << query; << cass_error_desc(rc) << ". query : " << query;
BOOST_LOG_TRIVIAL(error) << ss.str(); log_.error() << ss.str();
} }
cass_future_free(prepareFuture); cass_future_free(prepareFuture);
return rc == CASS_OK; return rc == CASS_OK;
@@ -73,7 +95,7 @@ public:
~CassandraPreparedStatement() ~CassandraPreparedStatement()
{ {
BOOST_LOG_TRIVIAL(trace) << __func__; log_.trace() << "called";
if (prepared_) if (prepared_)
{ {
cass_prepared_free(prepared_); cass_prepared_free(prepared_);
@@ -86,6 +108,7 @@ class CassandraStatement
{ {
CassStatement* statement_ = nullptr; CassStatement* statement_ = nullptr;
size_t curBindingIndex_ = 0; size_t curBindingIndex_ = 0;
clio::Logger log_{"Backend"};
public: public:
CassandraStatement(CassandraPreparedStatement const& prepared) CassandraStatement(CassandraPreparedStatement const& prepared)
@@ -123,7 +146,7 @@ public:
std::stringstream ss; std::stringstream ss;
ss << "Error binding boolean to statement: " << rc << ", " ss << "Error binding boolean to statement: " << rc << ", "
<< cass_error_desc(rc); << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << __func__ << " : " << ss.str(); log_.error() << ss.str();
throw std::runtime_error(ss.str()); throw std::runtime_error(ss.str());
} }
curBindingIndex_++; curBindingIndex_++;
@@ -179,7 +202,7 @@ public:
std::stringstream ss; std::stringstream ss;
ss << "Error binding bytes to statement: " << rc << ", " ss << "Error binding bytes to statement: " << rc << ", "
<< cass_error_desc(rc); << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << __func__ << " : " << ss.str(); log_.error() << ss.str();
throw std::runtime_error(ss.str()); throw std::runtime_error(ss.str());
} }
curBindingIndex_++; curBindingIndex_++;
@@ -191,8 +214,8 @@ public:
if (!statement_) if (!statement_)
throw std::runtime_error( throw std::runtime_error(
"CassandraStatement::bindNextUInt - statement_ is null"); "CassandraStatement::bindNextUInt - statement_ is null");
BOOST_LOG_TRIVIAL(trace) log_.trace() << std::to_string(curBindingIndex_) << " "
<< std::to_string(curBindingIndex_) << " " << std::to_string(value); << std::to_string(value);
CassError rc = CassError rc =
cass_statement_bind_int32(statement_, curBindingIndex_, value); cass_statement_bind_int32(statement_, curBindingIndex_, value);
if (rc != CASS_OK) if (rc != CASS_OK)
@@ -200,7 +223,7 @@ public:
std::stringstream ss; std::stringstream ss;
ss << "Error binding uint to statement: " << rc << ", " ss << "Error binding uint to statement: " << rc << ", "
<< cass_error_desc(rc); << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << __func__ << " : " << ss.str(); log_.error() << ss.str();
throw std::runtime_error(ss.str()); throw std::runtime_error(ss.str());
} }
curBindingIndex_++; curBindingIndex_++;
@@ -225,7 +248,7 @@ public:
std::stringstream ss; std::stringstream ss;
ss << "Error binding int to statement: " << rc << ", " ss << "Error binding int to statement: " << rc << ", "
<< cass_error_desc(rc); << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << __func__ << " : " << ss.str(); log_.error() << ss.str();
throw std::runtime_error(ss.str()); throw std::runtime_error(ss.str());
} }
curBindingIndex_++; curBindingIndex_++;
@@ -241,7 +264,7 @@ public:
std::stringstream ss; std::stringstream ss;
ss << "Error binding int to tuple: " << rc << ", " ss << "Error binding int to tuple: " << rc << ", "
<< cass_error_desc(rc); << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << __func__ << " : " << ss.str(); log_.error() << ss.str();
throw std::runtime_error(ss.str()); throw std::runtime_error(ss.str());
} }
rc = cass_tuple_set_int64(tuple, 1, second); rc = cass_tuple_set_int64(tuple, 1, second);
@@ -250,7 +273,7 @@ public:
std::stringstream ss; std::stringstream ss;
ss << "Error binding int to tuple: " << rc << ", " ss << "Error binding int to tuple: " << rc << ", "
<< cass_error_desc(rc); << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << __func__ << " : " << ss.str(); log_.error() << ss.str();
throw std::runtime_error(ss.str()); throw std::runtime_error(ss.str());
} }
rc = cass_statement_bind_tuple(statement_, curBindingIndex_, tuple); rc = cass_statement_bind_tuple(statement_, curBindingIndex_, tuple);
@@ -259,7 +282,7 @@ public:
std::stringstream ss; std::stringstream ss;
ss << "Error binding tuple to statement: " << rc << ", " ss << "Error binding tuple to statement: " << rc << ", "
<< cass_error_desc(rc); << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << __func__ << " : " << ss.str(); log_.error() << ss.str();
throw std::runtime_error(ss.str()); throw std::runtime_error(ss.str());
} }
cass_tuple_free(tuple); cass_tuple_free(tuple);
@@ -275,6 +298,7 @@ public:
class CassandraResult class CassandraResult
{ {
clio::Logger log_{"Backend"};
CassResult const* result_ = nullptr; CassResult const* result_ = nullptr;
CassRow const* row_ = nullptr; CassRow const* row_ = nullptr;
CassIterator* iter_ = nullptr; CassIterator* iter_ = nullptr;
@@ -365,7 +389,7 @@ public:
std::stringstream msg; std::stringstream msg;
msg << "CassandraResult::getBytes - error getting value: " << rc msg << "CassandraResult::getBytes - error getting value: " << rc
<< ", " << cass_error_desc(rc); << ", " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << msg.str(); log_.error() << msg.str();
throw std::runtime_error(msg.str()); throw std::runtime_error(msg.str());
} }
curGetIndex_++; curGetIndex_++;
@@ -386,7 +410,7 @@ public:
std::stringstream msg; std::stringstream msg;
msg << "CassandraResult::getuint256 - error getting value: " << rc msg << "CassandraResult::getuint256 - error getting value: " << rc
<< ", " << cass_error_desc(rc); << ", " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << msg.str(); log_.error() << msg.str();
throw std::runtime_error(msg.str()); throw std::runtime_error(msg.str());
} }
curGetIndex_++; curGetIndex_++;
@@ -406,7 +430,7 @@ public:
std::stringstream msg; std::stringstream msg;
msg << "CassandraResult::getInt64 - error getting value: " << rc msg << "CassandraResult::getInt64 - error getting value: " << rc
<< ", " << cass_error_desc(rc); << ", " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << msg.str(); log_.error() << msg.str();
throw std::runtime_error(msg.str()); throw std::runtime_error(msg.str());
} }
++curGetIndex_; ++curGetIndex_;
@@ -490,10 +514,9 @@ public:
{ {
if (!row_) if (!row_)
{ {
std::stringstream msg; std::string msg{"No result"};
msg << __func__ << " - no result"; log_.error() << msg;
BOOST_LOG_TRIVIAL(error) << msg.str(); throw std::runtime_error(msg);
throw std::runtime_error(msg.str());
} }
cass_bool_t val; cass_bool_t val;
CassError rc = CassError rc =
@@ -501,9 +524,8 @@ public:
if (rc != CASS_OK) if (rc != CASS_OK)
{ {
std::stringstream msg; std::stringstream msg;
msg << __func__ << " - error getting value: " << rc << ", " msg << "Error getting value: " << rc << ", " << cass_error_desc(rc);
<< cass_error_desc(rc); log_.error() << msg.str();
BOOST_LOG_TRIVIAL(error) << msg.str();
throw std::runtime_error(msg.str()); throw std::runtime_error(msg.str());
} }
++curGetIndex_; ++curGetIndex_;
@@ -518,6 +540,7 @@ public:
cass_iterator_free(iter_); cass_iterator_free(iter_);
} }
}; };
inline bool inline bool
isTimeout(CassError rc) isTimeout(CassError rc)
{ {
@@ -597,6 +620,7 @@ private:
return ret; return ret;
} }
clio::Logger log_{"Backend"};
std::atomic<bool> open_{false}; std::atomic<bool> open_{false};
std::unique_ptr<CassSession, void (*)(CassSession*)> session_{ std::unique_ptr<CassSession, void (*)(CassSession*)> session_{
@@ -751,13 +775,11 @@ public:
statement.bindNextInt(ledgerSequence_ - 1); statement.bindNextInt(ledgerSequence_ - 1);
if (!executeSyncUpdate(statement)) if (!executeSyncUpdate(statement))
{ {
BOOST_LOG_TRIVIAL(warning) log_.warn() << "Update failed for ledger "
<< __func__ << " Update failed for ledger " << std::to_string(ledgerSequence_) << ". Returning";
<< std::to_string(ledgerSequence_) << ". Returning";
return false; return false;
} }
BOOST_LOG_TRIVIAL(info) << __func__ << " Committed ledger " log_.info() << "Committed ledger " << std::to_string(ledgerSequence_);
<< std::to_string(ledgerSequence_);
return true; return true;
} }
@@ -791,22 +813,20 @@ public:
statement.bindNextInt(lastSync_); statement.bindNextInt(lastSync_);
if (!executeSyncUpdate(statement)) if (!executeSyncUpdate(statement))
{ {
BOOST_LOG_TRIVIAL(warning) log_.warn() << "Update failed for ledger "
<< __func__ << " Update failed for ledger " << std::to_string(ledgerSequence_) << ". Returning";
<< std::to_string(ledgerSequence_) << ". Returning";
return false; return false;
} }
BOOST_LOG_TRIVIAL(info) << __func__ << " Committed ledger " log_.info() << "Committed ledger "
<< std::to_string(ledgerSequence_); << std::to_string(ledgerSequence_);
lastSync_ = ledgerSequence_; lastSync_ = ledgerSequence_;
} }
else else
{ {
BOOST_LOG_TRIVIAL(info) log_.info() << "Skipping commit. sync interval is "
<< __func__ << " Skipping commit. sync interval is " << std::to_string(syncInterval_) << " - last sync is "
<< std::to_string(syncInterval_) << " - last sync is " << std::to_string(lastSync_) << " - ledger sequence is "
<< std::to_string(lastSync_) << " - ledger sequence is " << std::to_string(ledgerSequence_);
<< std::to_string(ledgerSequence_);
} }
return true; return true;
} }
@@ -826,12 +846,12 @@ public:
std::optional<std::uint32_t> std::optional<std::uint32_t>
fetchLatestLedgerSequence(boost::asio::yield_context& yield) const override fetchLatestLedgerSequence(boost::asio::yield_context& yield) const override
{ {
BOOST_LOG_TRIVIAL(trace) << __func__; log_.trace() << "called";
CassandraStatement statement{selectLatestLedger_}; CassandraStatement statement{selectLatestLedger_};
CassandraResult result = executeAsyncRead(statement, yield); CassandraResult result = executeAsyncRead(statement, yield);
if (!result.hasResult()) if (!result.hasResult())
{ {
BOOST_LOG_TRIVIAL(error) log_.error()
<< "CassandraBackend::fetchLatestLedgerSequence - no rows"; << "CassandraBackend::fetchLatestLedgerSequence - no rows";
return {}; return {};
} }
@@ -843,13 +863,13 @@ public:
std::uint32_t const sequence, std::uint32_t const sequence,
boost::asio::yield_context& yield) const override boost::asio::yield_context& yield) const override
{ {
BOOST_LOG_TRIVIAL(trace) << __func__; log_.trace() << "called";
CassandraStatement statement{selectLedgerBySeq_}; CassandraStatement statement{selectLedgerBySeq_};
statement.bindNextInt(sequence); statement.bindNextInt(sequence);
CassandraResult result = executeAsyncRead(statement, yield); CassandraResult result = executeAsyncRead(statement, yield);
if (!result) if (!result)
{ {
BOOST_LOG_TRIVIAL(error) << __func__ << " - no rows"; log_.error() << "No rows";
return {}; return {};
} }
std::vector<unsigned char> header = result.getBytes(); std::vector<unsigned char> header = result.getBytes();
@@ -869,7 +889,7 @@ public:
if (!result.hasResult()) if (!result.hasResult())
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " - no rows returned"; log_.debug() << "No rows returned";
return {}; return {};
} }
@@ -916,7 +936,7 @@ public:
std::optional<int64_t> std::optional<int64_t>
getToken(void const* key, boost::asio::yield_context& yield) const getToken(void const* key, boost::asio::yield_context& yield) const
{ {
BOOST_LOG_TRIVIAL(trace) << "Fetching from cassandra"; log_.trace() << "Fetching from cassandra";
CassandraStatement statement{getToken_}; CassandraStatement statement{getToken_};
statement.bindNextBytes(key, 32); statement.bindNextBytes(key, 32);
@@ -924,7 +944,7 @@ public:
if (!result) if (!result)
{ {
BOOST_LOG_TRIVIAL(error) << __func__ << " - no rows"; log_.error() << "No rows";
return {}; return {};
} }
int64_t token = result.getInt64(); int64_t token = result.getInt64();
@@ -939,14 +959,14 @@ public:
ripple::uint256 const& hash, ripple::uint256 const& hash,
boost::asio::yield_context& yield) const override boost::asio::yield_context& yield) const override
{ {
BOOST_LOG_TRIVIAL(trace) << __func__; log_.trace() << "called";
CassandraStatement statement{selectTransaction_}; CassandraStatement statement{selectTransaction_};
statement.bindNextBytes(hash); statement.bindNextBytes(hash);
CassandraResult result = executeAsyncRead(statement, yield); CassandraResult result = executeAsyncRead(statement, yield);
if (!result) if (!result)
{ {
BOOST_LOG_TRIVIAL(error) << __func__ << " - no rows"; log_.error() << "No rows";
return {}; return {};
} }
return { return {
@@ -1036,10 +1056,8 @@ public:
std::unique_lock<std::mutex> lck(throttleMutex_); std::unique_lock<std::mutex> lck(throttleMutex_);
if (!canAddRequest()) if (!canAddRequest())
{ {
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Max outstanding requests reached. "
<< __func__ << " : " << "Waiting for other requests to finish";
<< "Max outstanding requests reached. "
<< "Waiting for other requests to finish";
throttleCv_.wait(lck, [this]() { return canAddRequest(); }); throttleCv_.wait(lck, [this]() { return canAddRequest(); });
} }
} }
@@ -1142,7 +1160,7 @@ public:
ss << "Cassandra sync write error"; ss << "Cassandra sync write error";
ss << ", retrying"; ss << ", retrying";
ss << ": " << cass_error_desc(rc); ss << ": " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(warning) << ss.str(); log_.warn() << ss.str();
std::this_thread::sleep_for(std::chrono::milliseconds(5)); std::this_thread::sleep_for(std::chrono::milliseconds(5));
} }
} while (rc != CASS_OK); } while (rc != CASS_OK);
@@ -1166,7 +1184,7 @@ public:
ss << "Cassandra sync update error"; ss << "Cassandra sync update error";
ss << ", retrying"; ss << ", retrying";
ss << ": " << cass_error_desc(rc); ss << ": " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(warning) << ss.str(); log_.warn() << ss.str();
std::this_thread::sleep_for(std::chrono::milliseconds(5)); std::this_thread::sleep_for(std::chrono::milliseconds(5));
} }
} while (rc != CASS_OK); } while (rc != CASS_OK);
@@ -1176,7 +1194,7 @@ public:
CassRow const* row = cass_result_first_row(res); CassRow const* row = cass_result_first_row(res);
if (!row) if (!row)
{ {
BOOST_LOG_TRIVIAL(error) << "executeSyncUpdate - no rows"; log_.error() << "executeSyncUpdate - no rows";
cass_result_free(res); cass_result_free(res);
return false; return false;
} }
@@ -1185,16 +1203,14 @@ public:
if (rc != CASS_OK) if (rc != CASS_OK)
{ {
cass_result_free(res); cass_result_free(res);
BOOST_LOG_TRIVIAL(error) log_.error() << "executeSyncUpdate - error getting result " << rc
<< "executeSyncUpdate - error getting result " << rc << ", " << ", " << cass_error_desc(rc);
<< cass_error_desc(rc);
return false; return false;
} }
cass_result_free(res); cass_result_free(res);
if (success != cass_true && timedOut) if (success != cass_true && timedOut)
{ {
BOOST_LOG_TRIVIAL(warning) log_.warn() << "Update failed, but timedOut is true";
<< __func__ << " Update failed, but timedOut is true";
// if there was a timeout, the update may have succeeded in the // if there was a timeout, the update may have succeeded in the
// background on the first attempt. To determine if this happened, // background on the first attempt. To determine if this happened,
// we query the range from the db, making sure the range is what // we query the range from the db, making sure the range is what
@@ -1230,15 +1246,14 @@ public:
if (ec) if (ec)
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "Cannot read async cass_future_error_code";
<< "Cannot read async cass_future_error_code";
} }
if (rc != CASS_OK) if (rc != CASS_OK)
{ {
std::stringstream ss; std::stringstream ss;
ss << "Cassandra executeAsyncRead error"; ss << "Cassandra executeAsyncRead error";
ss << ": " << cass_error_desc(rc); ss << ": " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << ss.str(); log_.error() << ss.str();
} }
if (isTimeout(rc)) if (isTimeout(rc))
{ {
@@ -1261,4 +1276,3 @@ public:
}; };
} // namespace Backend } // namespace Backend
#endif

View File

@@ -1,5 +1,23 @@
#ifndef CLIO_BACKEND_DBHELPERS_H_INCLUDED //------------------------------------------------------------------------------
#define CLIO_BACKEND_DBHELPERS_H_INCLUDED /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/basics/Log.h> #include <ripple/basics/Log.h>
#include <ripple/basics/StringUtilities.h> #include <ripple/basics/StringUtilities.h>
@@ -181,4 +199,3 @@ uint256ToString(ripple::uint256 const& uint)
} }
static constexpr std::uint32_t rippleEpochStart = 946684800; static constexpr std::uint32_t rippleEpochStart = 946684800;
#endif

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <backend/SimpleCache.h> #include <backend/SimpleCache.h>
namespace Backend { namespace Backend {

View File

@@ -1,5 +1,23 @@
#ifndef CLIO_SIMPLECACHE_H_INCLUDED //------------------------------------------------------------------------------
#define CLIO_SIMPLECACHE_H_INCLUDED /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/basics/base_uint.h> #include <ripple/basics/base_uint.h>
#include <ripple/basics/hardened_hash.h> #include <ripple/basics/hardened_hash.h>
@@ -78,4 +96,3 @@ public:
}; };
} // namespace Backend } // namespace Backend
#endif

View File

@@ -1,5 +1,24 @@
#ifndef CLIO_TYPES_H_INCLUDED //------------------------------------------------------------------------------
#define CLIO_TYPES_H_INCLUDED /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/basics/base_uint.h> #include <ripple/basics/base_uint.h>
#include <ripple/protocol/AccountID.h> #include <ripple/protocol/AccountID.h>
#include <optional> #include <optional>
@@ -89,4 +108,3 @@ constexpr ripple::uint256 lastKey{
constexpr ripple::uint256 hi192{ constexpr ripple::uint256 hi192{
"0000000000000000000000000000000000000000000000001111111111111111"}; "0000000000000000000000000000000000000000000000001111111111111111"};
} // namespace Backend } // namespace Backend
#endif

View File

@@ -1,6 +1,25 @@
#include <config/Config.h> //------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <config/Config.h>
#include <log/Logger.h>
#include <boost/log/trivial.hpp>
#include <fstream> #include <fstream>
namespace clio { namespace clio {
@@ -161,11 +180,10 @@ ConfigReader::open(std::filesystem::path path)
} }
catch (std::exception const& e) catch (std::exception const& e)
{ {
BOOST_LOG_TRIVIAL(error) << "Could not read configuration file from '" LogService::error() << "Could not read configuration file from '"
<< path.string() << "': " << e.what(); << path.string() << "': " << e.what();
} }
BOOST_LOG_TRIVIAL(warning) << "Using empty default configuration";
return Config{}; return Config{};
} }

View File

@@ -1,5 +1,23 @@
#ifndef RIPPLE_APP_CONFIG_H_INCLUDED //------------------------------------------------------------------------------
#define RIPPLE_APP_CONFIG_H_INCLUDED /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <config/detail/Helpers.h> #include <config/detail/Helpers.h>
@@ -344,7 +362,7 @@ private:
} }
else if constexpr (std::is_same_v<Return, double>) else if constexpr (std::is_same_v<Return, double>)
{ {
if (not value.is_double()) if (not value.is_number())
has_error = true; has_error = true;
} }
else if constexpr ( else if constexpr (
@@ -385,5 +403,3 @@ public:
}; };
} // namespace clio } // namespace clio
#endif // RIPPLE_APP_CONFIG_H_INCLUDED

View File

@@ -1,5 +1,23 @@
#ifndef RIPPLE_APP_CONFIG_DETAIL_H_INCLUDED //------------------------------------------------------------------------------
#define RIPPLE_APP_CONFIG_DETAIL_H_INCLUDED /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <optional> #include <optional>
#include <queue> #include <queue>
@@ -144,5 +162,3 @@ typeName<double>()
} }
}; // namespace clio::detail }; // namespace clio::detail
#endif // RIPPLE_APP_CONFIG_DETAIL_H_INCLUDED

View File

@@ -1,5 +1,24 @@
#ifndef RIPPLE_APP_REPORTING_ETLHELPERS_H_INCLUDED //------------------------------------------------------------------------------
#define RIPPLE_APP_REPORTING_ETLHELPERS_H_INCLUDED /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/basics/base_uint.h> #include <ripple/basics/base_uint.h>
#include <condition_variable> #include <condition_variable>
#include <mutex> #include <mutex>
@@ -171,5 +190,3 @@ getMarkers(size_t numMarkers)
} }
return markers; return markers;
} }
#endif // RIPPLE_APP_REPORTING_ETLHELPERS_H_INCLUDED

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/beast/net/IPEndpoint.h> #include <ripple/beast/net/IPEndpoint.h>
#include <ripple/protocol/STLedgerEntry.h> #include <ripple/protocol/STLedgerEntry.h>
#include <boost/asio/strand.hpp> #include <boost/asio/strand.hpp>
@@ -5,18 +24,23 @@
#include <boost/beast/ssl.hpp> #include <boost/beast/ssl.hpp>
#include <boost/json.hpp> #include <boost/json.hpp>
#include <boost/json/src.hpp> #include <boost/json/src.hpp>
#include <boost/log/trivial.hpp>
#include <backend/DBHelpers.h> #include <backend/DBHelpers.h>
#include <etl/ETLSource.h> #include <etl/ETLSource.h>
#include <etl/ProbingETLSource.h> #include <etl/ProbingETLSource.h>
#include <etl/ReportingETL.h> #include <etl/ReportingETL.h>
#include <log/Logger.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <util/Profiler.h>
#include <thread> #include <thread>
using namespace clio;
void void
ForwardCache::freshen() ForwardCache::freshen()
{ {
BOOST_LOG_TRIVIAL(trace) << "Freshening ForwardCache"; log_.trace() << "Freshening ForwardCache";
auto numOutstanding = auto numOutstanding =
std::make_shared<std::atomic_uint>(latestForwarded_.size()); std::make_shared<std::atomic_uint>(latestForwarded_.size());
@@ -127,15 +151,11 @@ ETLSourceImpl<Derived>::reconnect(boost::beast::error_code ec)
if (ec != boost::asio::error::operation_aborted && if (ec != boost::asio::error::operation_aborted &&
ec != boost::asio::error::connection_refused) ec != boost::asio::error::connection_refused)
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "error code = " << ec << " - " << toString();
<< __func__ << " : "
<< "error code = " << ec << " - " << toString();
} }
else else
{ {
BOOST_LOG_TRIVIAL(warning) log_.warn() << "error code = " << ec << " - " << toString();
<< __func__ << " : "
<< "error code = " << ec << " - " << toString();
} }
// exponentially increasing timeouts, with a max of 30 seconds // exponentially increasing timeouts, with a max of 30 seconds
@@ -144,7 +164,7 @@ ETLSourceImpl<Derived>::reconnect(boost::beast::error_code ec)
timer_.expires_after(boost::asio::chrono::seconds(waitTime)); timer_.expires_after(boost::asio::chrono::seconds(waitTime));
timer_.async_wait([this](auto ec) { timer_.async_wait([this](auto ec) {
bool startAgain = (ec != boost::asio::error::operation_aborted); bool startAgain = (ec != boost::asio::error::operation_aborted);
BOOST_LOG_TRIVIAL(trace) << __func__ << " async_wait : ec = " << ec; log_.trace() << "async_wait : ec = " << ec;
derived().close(startAgain); derived().close(startAgain);
}); });
} }
@@ -168,8 +188,8 @@ PlainETLSource::close(bool startAgain)
[this, startAgain](auto ec) { [this, startAgain](auto ec) {
if (ec) if (ec)
{ {
BOOST_LOG_TRIVIAL(error) log_.error()
<< __func__ << " async_close : " << " async_close : "
<< "error code = " << ec << " - " << toString(); << "error code = " << ec << " - " << toString();
} }
closing_ = false; closing_ = false;
@@ -213,8 +233,8 @@ SslETLSource::close(bool startAgain)
[this, startAgain](auto ec) { [this, startAgain](auto ec) {
if (ec) if (ec)
{ {
BOOST_LOG_TRIVIAL(error) log_.error()
<< __func__ << " async_close : " << " async_close : "
<< "error code = " << ec << " - " << toString(); << "error code = " << ec << " - " << toString();
} }
closing_ = false; closing_ = false;
@@ -246,8 +266,7 @@ ETLSourceImpl<Derived>::onResolve(
boost::beast::error_code ec, boost::beast::error_code ec,
boost::asio::ip::tcp::resolver::results_type results) boost::asio::ip::tcp::resolver::results_type results)
{ {
BOOST_LOG_TRIVIAL(trace) log_.trace() << "ec = " << ec << " - " << toString();
<< __func__ << " : ec = " << ec << " - " << toString();
if (ec) if (ec)
{ {
// try again // try again
@@ -269,8 +288,7 @@ PlainETLSource::onConnect(
boost::beast::error_code ec, boost::beast::error_code ec,
boost::asio::ip::tcp::resolver::results_type::endpoint_type endpoint) boost::asio::ip::tcp::resolver::results_type::endpoint_type endpoint)
{ {
BOOST_LOG_TRIVIAL(trace) log_.trace() << "ec = " << ec << " - " << toString();
<< __func__ << " : ec = " << ec << " - " << toString();
if (ec) if (ec)
{ {
// start over // start over
@@ -311,8 +329,7 @@ SslETLSource::onConnect(
boost::beast::error_code ec, boost::beast::error_code ec,
boost::asio::ip::tcp::resolver::results_type::endpoint_type endpoint) boost::asio::ip::tcp::resolver::results_type::endpoint_type endpoint)
{ {
BOOST_LOG_TRIVIAL(trace) log_.trace() << "ec = " << ec << " - " << toString();
<< __func__ << " : ec = " << ec << " - " << toString();
if (ec) if (ec)
{ {
// start over // start over
@@ -371,8 +388,7 @@ template <class Derived>
void void
ETLSourceImpl<Derived>::onHandshake(boost::beast::error_code ec) ETLSourceImpl<Derived>::onHandshake(boost::beast::error_code ec)
{ {
BOOST_LOG_TRIVIAL(trace) log_.trace() << "ec = " << ec << " - " << toString();
<< __func__ << " : ec = " << ec << " - " << toString();
if (auto action = hooks_.onConnected(ec); if (auto action = hooks_.onConnected(ec);
action == ETLSourceHooks::Action::STOP) action == ETLSourceHooks::Action::STOP)
return; return;
@@ -389,7 +405,7 @@ ETLSourceImpl<Derived>::onHandshake(boost::beast::error_code ec)
{"streams", {"streams",
{"ledger", "manifests", "validations", "transactions_proposed"}}}; {"ledger", "manifests", "validations", "transactions_proposed"}}};
std::string s = boost::json::serialize(jv); std::string s = boost::json::serialize(jv);
BOOST_LOG_TRIVIAL(trace) << "Sending subscribe stream message"; log_.trace() << "Sending subscribe stream message";
derived().ws().set_option( derived().ws().set_option(
boost::beast::websocket::stream_base::decorator( boost::beast::websocket::stream_base::decorator(
@@ -415,8 +431,7 @@ ETLSourceImpl<Derived>::onWrite(
boost::beast::error_code ec, boost::beast::error_code ec,
size_t bytesWritten) size_t bytesWritten)
{ {
BOOST_LOG_TRIVIAL(trace) log_.trace() << "ec = " << ec << " - " << toString();
<< __func__ << " : ec = " << ec << " - " << toString();
if (ec) if (ec)
{ {
// start over // start over
@@ -433,8 +448,7 @@ template <class Derived>
void void
ETLSourceImpl<Derived>::onRead(boost::beast::error_code ec, size_t size) ETLSourceImpl<Derived>::onRead(boost::beast::error_code ec, size_t size)
{ {
BOOST_LOG_TRIVIAL(trace) log_.trace() << "ec = " << ec << " - " << toString();
<< __func__ << " : ec = " << ec << " - " << toString();
// if error or error reading message, start over // if error or error reading message, start over
if (ec) if (ec)
{ {
@@ -446,8 +460,7 @@ ETLSourceImpl<Derived>::onRead(boost::beast::error_code ec, size_t size)
boost::beast::flat_buffer buffer; boost::beast::flat_buffer buffer;
swap(readBuffer_, buffer); swap(readBuffer_, buffer);
BOOST_LOG_TRIVIAL(trace) log_.trace() << "calling async_read - " << toString();
<< __func__ << " : calling async_read - " << toString();
derived().ws().async_read( derived().ws().async_read(
readBuffer_, [this](auto ec, size_t size) { onRead(ec, size); }); readBuffer_, [this](auto ec, size_t size) { onRead(ec, size); });
} }
@@ -457,7 +470,7 @@ template <class Derived>
bool bool
ETLSourceImpl<Derived>::handleMessage() ETLSourceImpl<Derived>::handleMessage()
{ {
BOOST_LOG_TRIVIAL(trace) << __func__ << " : " << toString(); log_.trace() << toString();
setLastMsgTime(); setLastMsgTime();
connected_ = true; connected_ = true;
@@ -466,9 +479,9 @@ ETLSourceImpl<Derived>::handleMessage()
std::string msg{ std::string msg{
static_cast<char const*>(readBuffer_.data().data()), static_cast<char const*>(readBuffer_.data().data()),
readBuffer_.size()}; readBuffer_.size()};
BOOST_LOG_TRIVIAL(trace) << __func__ << msg; log_.trace() << msg;
boost::json::value raw = boost::json::parse(msg); boost::json::value raw = boost::json::parse(msg);
BOOST_LOG_TRIVIAL(trace) << __func__ << " parsed"; log_.trace() << "parsed";
boost::json::object response = raw.as_object(); boost::json::object response = raw.as_object();
uint32_t ledgerIndex = 0; uint32_t ledgerIndex = 0;
@@ -487,20 +500,16 @@ ETLSourceImpl<Derived>::handleMessage()
setValidatedRange( setValidatedRange(
{validatedLedgers.c_str(), validatedLedgers.size()}); {validatedLedgers.c_str(), validatedLedgers.size()});
} }
BOOST_LOG_TRIVIAL(debug) log_.info() << "Received a message on ledger "
<< __func__ << " : " << " subscription stream. Message : " << response
<< "Received a message on ledger " << " - " << toString();
<< " subscription stream. Message : " << response << " - "
<< toString();
} }
else if ( else if (
response.contains("type") && response["type"] == "ledgerClosed") response.contains("type") && response["type"] == "ledgerClosed")
{ {
BOOST_LOG_TRIVIAL(debug) log_.info() << "Received a message on ledger "
<< __func__ << " : " << " subscription stream. Message : " << response
<< "Received a message on ledger " << " - " << toString();
<< " subscription stream. Message : " << response << " - "
<< toString();
if (response.contains("ledger_index")) if (response.contains("ledger_index"))
{ {
ledgerIndex = response["ledger_index"].as_int64(); ledgerIndex = response["ledger_index"].as_int64();
@@ -539,23 +548,23 @@ ETLSourceImpl<Derived>::handleMessage()
if (ledgerIndex != 0) if (ledgerIndex != 0)
{ {
BOOST_LOG_TRIVIAL(trace) log_.trace() << "Pushing ledger sequence = " << ledgerIndex << " - "
<< __func__ << " : " << toString();
<< "Pushing ledger sequence = " << ledgerIndex << " - "
<< toString();
networkValidatedLedgers_->push(ledgerIndex); networkValidatedLedgers_->push(ledgerIndex);
} }
return true; return true;
} }
catch (std::exception const& e) catch (std::exception const& e)
{ {
BOOST_LOG_TRIVIAL(error) << "Exception in handleMessage : " << e.what(); log_.error() << "Exception in handleMessage : " << e.what();
return false; return false;
} }
} }
class AsyncCallData class AsyncCallData
{ {
clio::Logger log_{"ETL"};
std::unique_ptr<org::xrpl::rpc::v1::GetLedgerDataResponse> cur_; std::unique_ptr<org::xrpl::rpc::v1::GetLedgerDataResponse> cur_;
std::unique_ptr<org::xrpl::rpc::v1::GetLedgerDataResponse> next_; std::unique_ptr<org::xrpl::rpc::v1::GetLedgerDataResponse> next_;
@@ -585,11 +594,11 @@ public:
unsigned char prefix = marker.data()[0]; unsigned char prefix = marker.data()[0];
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Setting up AsyncCallData. marker = "
<< "Setting up AsyncCallData. marker = " << ripple::strHex(marker) << ripple::strHex(marker)
<< " . prefix = " << ripple::strHex(std::string(1, prefix)) << " . prefix = " << ripple::strHex(std::string(1, prefix))
<< " . nextPrefix_ = " << " . nextPrefix_ = "
<< ripple::strHex(std::string(1, nextPrefix_)); << ripple::strHex(std::string(1, nextPrefix_));
assert(nextPrefix_ > prefix || nextPrefix_ == 0x00); assert(nextPrefix_ > prefix || nextPrefix_ == 0x00);
@@ -609,26 +618,24 @@ public:
bool abort, bool abort,
bool cacheOnly = false) bool cacheOnly = false)
{ {
BOOST_LOG_TRIVIAL(trace) << "Processing response. " log_.trace() << "Processing response. "
<< "Marker prefix = " << getMarkerPrefix(); << "Marker prefix = " << getMarkerPrefix();
if (abort) if (abort)
{ {
BOOST_LOG_TRIVIAL(error) << "AsyncCallData aborted"; log_.error() << "AsyncCallData aborted";
return CallStatus::ERRORED; return CallStatus::ERRORED;
} }
if (!status_.ok()) if (!status_.ok())
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "AsyncCallData status_ not ok: "
<< "AsyncCallData status_ not ok: " << " code = " << status_.error_code()
<< " code = " << status_.error_code() << " message = " << status_.error_message();
<< " message = " << status_.error_message();
return CallStatus::ERRORED; return CallStatus::ERRORED;
} }
if (!next_->is_unlimited()) if (!next_->is_unlimited())
{ {
BOOST_LOG_TRIVIAL(warning) log_.warn() << "AsyncCallData is_unlimited is false. Make sure "
<< "AsyncCallData is_unlimited is false. Make sure " "secure_gateway is set correctly at the ETL source";
"secure_gateway is set correctly at the ETL source";
} }
std::swap(cur_, next_); std::swap(cur_, next_);
@@ -651,7 +658,7 @@ public:
call(stub, cq); call(stub, cq);
} }
BOOST_LOG_TRIVIAL(trace) << "Writing objects"; log_.trace() << "Writing objects";
std::vector<Backend::LedgerObject> cacheUpdates; std::vector<Backend::LedgerObject> cacheUpdates;
cacheUpdates.reserve(cur_->ledger_objects().objects_size()); cacheUpdates.reserve(cur_->ledger_objects().objects_size());
for (int i = 0; i < cur_->ledger_objects().objects_size(); ++i) for (int i = 0; i < cur_->ledger_objects().objects_size(); ++i)
@@ -681,7 +688,7 @@ public:
} }
backend.cache().update( backend.cache().update(
cacheUpdates, request_.ledger().sequence(), cacheOnly); cacheUpdates, request_.ledger().sequence(), cacheOnly);
BOOST_LOG_TRIVIAL(trace) << "Wrote objects"; log_.trace() << "Wrote objects";
return more ? CallStatus::MORE : CallStatus::DONE; return more ? CallStatus::MORE : CallStatus::DONE;
} }
@@ -745,8 +752,8 @@ ETLSourceImpl<Derived>::loadInitialLedger(
calls.emplace_back(sequence, markers[i], nextMarker); calls.emplace_back(sequence, markers[i], nextMarker);
} }
BOOST_LOG_TRIVIAL(debug) << "Starting data download for ledger " << sequence log_.debug() << "Starting data download for ledger " << sequence
<< ". Using source = " << toString(); << ". Using source = " << toString();
for (auto& c : calls) for (auto& c : calls)
c.call(stub_, cq); c.call(stub_, cq);
@@ -764,21 +771,19 @@ ETLSourceImpl<Derived>::loadInitialLedger(
if (!ok) if (!ok)
{ {
BOOST_LOG_TRIVIAL(error) << "loadInitialLedger - ok is false"; log_.error() << "loadInitialLedger - ok is false";
return false; return false;
// handle cancelled // handle cancelled
} }
else else
{ {
BOOST_LOG_TRIVIAL(trace) log_.trace() << "Marker prefix = " << ptr->getMarkerPrefix();
<< "Marker prefix = " << ptr->getMarkerPrefix();
auto result = ptr->process(stub_, cq, *backend_, abort, cacheOnly); auto result = ptr->process(stub_, cq, *backend_, abort, cacheOnly);
if (result != AsyncCallData::CallStatus::MORE) if (result != AsyncCallData::CallStatus::MORE)
{ {
numFinished++; numFinished++;
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Finished a marker. "
<< "Finished a marker. " << "Current number of finished = " << numFinished;
<< "Current number of finished = " << numFinished;
std::string lastKey = ptr->getLastKey(); std::string lastKey = ptr->getLastKey();
if (lastKey.size()) if (lastKey.size())
edgeKeys.push_back(ptr->getLastKey()); edgeKeys.push_back(ptr->getLastKey());
@@ -789,89 +794,84 @@ ETLSourceImpl<Derived>::loadInitialLedger(
} }
if (backend_->cache().size() > progress) if (backend_->cache().size() > progress)
{ {
BOOST_LOG_TRIVIAL(info) log_.info() << "Downloaded " << backend_->cache().size()
<< "Downloaded " << backend_->cache().size() << " records from rippled";
<< " records from rippled";
progress += incr; progress += incr;
} }
} }
} }
BOOST_LOG_TRIVIAL(info) log_.info() << "Finished loadInitialLedger. cache size = "
<< __func__ << " - finished loadInitialLedger. cache size = " << backend_->cache().size();
<< backend_->cache().size();
size_t numWrites = 0; size_t numWrites = 0;
if (!abort) if (!abort)
{ {
backend_->cache().setFull(); backend_->cache().setFull();
if (!cacheOnly) if (!cacheOnly)
{ {
auto start = std::chrono::system_clock::now(); auto seconds = util::timed<std::chrono::seconds>([&]() {
for (auto& key : edgeKeys) for (auto& key : edgeKeys)
{
BOOST_LOG_TRIVIAL(debug)
<< __func__
<< " writing edge key = " << ripple::strHex(key);
auto succ = backend_->cache().getSuccessor(
*ripple::uint256::fromVoidChecked(key), sequence);
if (succ)
backend_->writeSuccessor(
std::move(key), sequence, uint256ToString(succ->key));
}
ripple::uint256 prev = Backend::firstKey;
while (auto cur = backend_->cache().getSuccessor(prev, sequence))
{
assert(cur);
if (prev == Backend::firstKey)
{ {
backend_->writeSuccessor( log_.debug()
uint256ToString(prev), << "Writing edge key = " << ripple::strHex(key);
sequence, auto succ = backend_->cache().getSuccessor(
uint256ToString(cur->key)); *ripple::uint256::fromVoidChecked(key), sequence);
if (succ)
backend_->writeSuccessor(
std::move(key),
sequence,
uint256ToString(succ->key));
} }
ripple::uint256 prev = Backend::firstKey;
if (isBookDir(cur->key, cur->blob)) while (auto cur =
backend_->cache().getSuccessor(prev, sequence))
{ {
auto base = getBookBase(cur->key); assert(cur);
// make sure the base is not an actual object if (prev == Backend::firstKey)
if (!backend_->cache().get(cur->key, sequence))
{ {
auto succ = backend_->writeSuccessor(
backend_->cache().getSuccessor(base, sequence); uint256ToString(prev),
assert(succ); sequence,
if (succ->key == cur->key) uint256ToString(cur->key));
{
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " Writing book successor = "
<< ripple::strHex(base) << " - "
<< ripple::strHex(cur->key);
backend_->writeSuccessor(
uint256ToString(base),
sequence,
uint256ToString(cur->key));
}
} }
++numWrites;
if (isBookDir(cur->key, cur->blob))
{
auto base = getBookBase(cur->key);
// make sure the base is not an actual object
if (!backend_->cache().get(cur->key, sequence))
{
auto succ =
backend_->cache().getSuccessor(base, sequence);
assert(succ);
if (succ->key == cur->key)
{
log_.debug() << "Writing book successor = "
<< ripple::strHex(base) << " - "
<< ripple::strHex(cur->key);
backend_->writeSuccessor(
uint256ToString(base),
sequence,
uint256ToString(cur->key));
}
}
++numWrites;
}
prev = std::move(cur->key);
if (numWrites % 100000 == 0 && numWrites != 0)
log_.info()
<< "Wrote " << numWrites << " book successors";
} }
prev = std::move(cur->key);
if (numWrites % 100000 == 0 && numWrites != 0)
BOOST_LOG_TRIVIAL(info) << __func__ << " Wrote "
<< numWrites << " book successors";
}
backend_->writeSuccessor( backend_->writeSuccessor(
uint256ToString(prev), uint256ToString(prev),
sequence, sequence,
uint256ToString(Backend::lastKey)); uint256ToString(Backend::lastKey));
++numWrites; ++numWrites;
auto end = std::chrono::system_clock::now(); });
auto seconds = log_.info()
std::chrono::duration_cast<std::chrono::seconds>(end - start) << "Looping through cache and submitting all writes took "
.count();
BOOST_LOG_TRIVIAL(info)
<< __func__
<< " - Looping through cache and submitting all writes took "
<< seconds << seconds
<< " seconds. numWrites = " << std::to_string(numWrites); << " seconds. numWrites = " << std::to_string(numWrites);
} }
@@ -902,11 +902,10 @@ ETLSourceImpl<Derived>::fetchLedger(
grpc::Status status = stub_->GetLedger(&context, request, &response); grpc::Status status = stub_->GetLedger(&context, request, &response);
if (status.ok() && !response.is_unlimited()) if (status.ok() && !response.is_unlimited())
{ {
BOOST_LOG_TRIVIAL(warning) log_.warn() << "ETLSourceImpl::fetchLedger - is_unlimited is "
<< "ETLSourceImpl::fetchLedger - is_unlimited is " "false. Make sure secure_gateway is set "
"false. Make sure secure_gateway is set " "correctly on the ETL source. source = "
"correctly on the ETL source. source = " << toString() << " status = " << status.error_message();
<< toString() << " status = " << status.error_message();
} }
return {status, std::move(response)}; return {status, std::move(response)};
} }
@@ -951,8 +950,7 @@ ETLLoadBalancer::ETLLoadBalancer(
entry, ioContext, backend, subscriptions, nwvl, *this); entry, ioContext, backend, subscriptions, nwvl, *this);
sources_.push_back(std::move(source)); sources_.push_back(std::move(source));
BOOST_LOG_TRIVIAL(info) << __func__ << " : added etl source - " log_.info() << "Added etl source - " << sources_.back()->toString();
<< sources_.back()->toString();
} }
} }
@@ -965,9 +963,9 @@ ETLLoadBalancer::loadInitialLedger(uint32_t sequence, bool cacheOnly)
source->loadInitialLedger(sequence, downloadRanges_, cacheOnly); source->loadInitialLedger(sequence, downloadRanges_, cacheOnly);
if (!res) if (!res)
{ {
BOOST_LOG_TRIVIAL(error) << "Failed to download initial ledger." log_.error() << "Failed to download initial ledger."
<< " Sequence = " << sequence << " Sequence = " << sequence
<< " source = " << source->toString(); << " source = " << source->toString();
} }
return res; return res;
}, },
@@ -982,26 +980,24 @@ ETLLoadBalancer::fetchLedger(
{ {
org::xrpl::rpc::v1::GetLedgerResponse response; org::xrpl::rpc::v1::GetLedgerResponse response;
bool success = execute( bool success = execute(
[&response, ledgerSequence, getObjects, getObjectNeighbors]( [&response, ledgerSequence, getObjects, getObjectNeighbors, log = log_](
auto& source) { auto& source) {
auto [status, data] = source->fetchLedger( auto [status, data] = source->fetchLedger(
ledgerSequence, getObjects, getObjectNeighbors); ledgerSequence, getObjects, getObjectNeighbors);
response = std::move(data); response = std::move(data);
if (status.ok() && response.validated()) if (status.ok() && response.validated())
{ {
BOOST_LOG_TRIVIAL(info) log.info() << "Successfully fetched ledger = " << ledgerSequence
<< "Successfully fetched ledger = " << ledgerSequence << " from source = " << source->toString();
<< " from source = " << source->toString();
return true; return true;
} }
else else
{ {
BOOST_LOG_TRIVIAL(warning) log.warn() << "Error getting ledger = " << ledgerSequence
<< "Error getting ledger = " << ledgerSequence << ", Reply: " << response.DebugString()
<< " Reply : " << response.DebugString() << ", error_code: " << status.error_code()
<< " error_code : " << status.error_code() << ", error_msg: " << status.error_message()
<< " error_msg : " << status.error_message() << ", source = " << source->toString();
<< " source = " << source->toString();
return false; return false;
} }
}, },
@@ -1042,7 +1038,7 @@ ETLSourceImpl<Derived>::forwardToRippled(
{ {
if (auto resp = forwardCache_.get(request); resp) if (auto resp = forwardCache_.get(request); resp)
{ {
BOOST_LOG_TRIVIAL(debug) << "request hit forwardCache"; log_.debug() << "request hit forwardCache";
return resp; return resp;
} }
@@ -1056,14 +1052,13 @@ ETLSourceImpl<Derived>::requestFromRippled(
std::string const& clientIp, std::string const& clientIp,
boost::asio::yield_context& yield) const boost::asio::yield_context& yield) const
{ {
BOOST_LOG_TRIVIAL(trace) << "Attempting to forward request to tx. " log_.trace() << "Attempting to forward request to tx. "
<< "request = " << boost::json::serialize(request); << "request = " << boost::json::serialize(request);
boost::json::object response; boost::json::object response;
if (!connected_) if (!connected_)
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "Attempted to proxy but failed to connect to tx";
<< "Attempted to proxy but failed to connect to tx";
return {}; return {};
} }
namespace beast = boost::beast; // from <boost/beast.hpp> namespace beast = boost::beast; // from <boost/beast.hpp>
@@ -1077,7 +1072,7 @@ ETLSourceImpl<Derived>::requestFromRippled(
// These objects perform our I/O // These objects perform our I/O
tcp::resolver resolver{ioc_}; tcp::resolver resolver{ioc_};
BOOST_LOG_TRIVIAL(trace) << "Creating websocket"; log_.trace() << "Creating websocket";
auto ws = std::make_unique<websocket::stream<beast::tcp_stream>>(ioc_); auto ws = std::make_unique<websocket::stream<beast::tcp_stream>>(ioc_);
// Look up the domain name // Look up the domain name
@@ -1087,7 +1082,7 @@ ETLSourceImpl<Derived>::requestFromRippled(
ws->next_layer().expires_after(std::chrono::seconds(3)); ws->next_layer().expires_after(std::chrono::seconds(3));
BOOST_LOG_TRIVIAL(trace) << "Connecting websocket"; log_.trace() << "Connecting websocket";
// Make the connection on the IP address we get from a lookup // Make the connection on the IP address we get from a lookup
ws->next_layer().async_connect(results, yield[ec]); ws->next_layer().async_connect(results, yield[ec]);
if (ec) if (ec)
@@ -1106,15 +1101,15 @@ ETLSourceImpl<Derived>::requestFromRippled(
" websocket-client-coro"); " websocket-client-coro");
req.set(http::field::forwarded, "for=" + clientIp); req.set(http::field::forwarded, "for=" + clientIp);
})); }));
BOOST_LOG_TRIVIAL(trace) << "client ip: " << clientIp; log_.trace() << "client ip: " << clientIp;
BOOST_LOG_TRIVIAL(trace) << "Performing websocket handshake"; log_.trace() << "Performing websocket handshake";
// Perform the websocket handshake // Perform the websocket handshake
ws->async_handshake(ip_, "/", yield[ec]); ws->async_handshake(ip_, "/", yield[ec]);
if (ec) if (ec)
return {}; return {};
BOOST_LOG_TRIVIAL(trace) << "Sending request"; log_.trace() << "Sending request";
// Send the message // Send the message
ws->async_write( ws->async_write(
net::buffer(boost::json::serialize(request)), yield[ec]); net::buffer(boost::json::serialize(request)), yield[ec]);
@@ -1132,11 +1127,11 @@ ETLSourceImpl<Derived>::requestFromRippled(
if (!parsed.is_object()) if (!parsed.is_object())
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "Error parsing response: "
<< "Error parsing response: " << std::string{begin, end}; << std::string{begin, end};
return {}; return {};
} }
BOOST_LOG_TRIVIAL(trace) << "Successfully forward request"; log_.trace() << "Successfully forward request";
response = parsed.as_object(); response = parsed.as_object();
@@ -1145,7 +1140,7 @@ ETLSourceImpl<Derived>::requestFromRippled(
} }
catch (std::exception const& e) catch (std::exception const& e)
{ {
BOOST_LOG_TRIVIAL(error) << "Encountered exception : " << e.what(); log_.error() << "Encountered exception : " << e.what();
return {}; return {};
} }
} }
@@ -1162,47 +1157,38 @@ ETLLoadBalancer::execute(Func f, uint32_t ledgerSequence)
{ {
auto& source = sources_[sourceIdx]; auto& source = sources_[sourceIdx];
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Attempting to execute func. ledger sequence = "
<< __func__ << " : " << ledgerSequence << " - source = " << source->toString();
<< "Attempting to execute func. ledger sequence = "
<< ledgerSequence << " - source = " << source->toString();
if (source->hasLedger(ledgerSequence) || true) if (source->hasLedger(ledgerSequence) || true)
{ {
bool res = f(source); bool res = f(source);
if (res) if (res)
{ {
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Successfully executed func at source = "
<< __func__ << " : " << source->toString()
<< "Successfully executed func at source = " << " - ledger sequence = " << ledgerSequence;
<< source->toString()
<< " - ledger sequence = " << ledgerSequence;
break; break;
} }
else else
{ {
BOOST_LOG_TRIVIAL(warning) log_.warn() << "Failed to execute func at source = "
<< __func__ << " : " << source->toString()
<< "Failed to execute func at source = " << " - ledger sequence = " << ledgerSequence;
<< source->toString()
<< " - ledger sequence = " << ledgerSequence;
} }
} }
else else
{ {
BOOST_LOG_TRIVIAL(warning) log_.warn() << "Ledger not present at source = "
<< __func__ << " : " << source->toString()
<< "Ledger not present at source = " << source->toString() << " - ledger sequence = " << ledgerSequence;
<< " - ledger sequence = " << ledgerSequence;
} }
sourceIdx = (sourceIdx + 1) % sources_.size(); sourceIdx = (sourceIdx + 1) % sources_.size();
numAttempts++; numAttempts++;
if (numAttempts % sources_.size() == 0) if (numAttempts % sources_.size() == 0)
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "Error executing function "
<< __func__ << " : " << " - ledger sequence = " << ledgerSequence
<< "Error executing function " << " - Tried all sources. Sleeping and trying again";
<< " - ledger sequence = " << ledgerSequence
<< " - Tried all sources. Sleeping and trying again";
std::this_thread::sleep_for(std::chrono::seconds(2)); std::this_thread::sleep_for(std::chrono::seconds(2));
} }
} }

View File

@@ -1,5 +1,32 @@
#ifndef RIPPLE_APP_REPORTING_ETLSOURCE_H_INCLUDED //------------------------------------------------------------------------------
#define RIPPLE_APP_REPORTING_ETLSOURCE_H_INCLUDED /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include <backend/BackendInterface.h>
#include <config/Config.h>
#include <etl/ETLHelpers.h>
#include <log/Logger.h>
#include <subscriptions/SubscriptionManager.h>
#include "org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h"
#include <grpcpp/grpcpp.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/asio.hpp> #include <boost/asio.hpp>
@@ -8,14 +35,6 @@
#include <boost/beast/ssl.hpp> #include <boost/beast/ssl.hpp>
#include <boost/beast/websocket.hpp> #include <boost/beast/websocket.hpp>
#include <backend/BackendInterface.h>
#include <config/Config.h>
#include <etl/ETLHelpers.h>
#include <subscriptions/SubscriptionManager.h>
#include "org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h"
#include <grpcpp/grpcpp.h>
class ETLLoadBalancer; class ETLLoadBalancer;
class ETLSource; class ETLSource;
class ProbingETLSource; class ProbingETLSource;
@@ -32,6 +51,7 @@ class ForwardCache
{ {
using response_type = std::optional<boost::json::object>; using response_type = std::optional<boost::json::object>;
clio::Logger log_{"ETL"};
mutable std::atomic_bool stopping_ = false; mutable std::atomic_bool stopping_ = false;
mutable std::shared_mutex mtx_; mutable std::shared_mutex mtx_;
std::unordered_map<std::string, response_type> latestForwarded_; std::unordered_map<std::string, response_type> latestForwarded_;
@@ -127,6 +147,9 @@ public:
{ {
} }
protected:
clio::Logger log_{"ETL"};
private: private:
friend ForwardCache; friend ForwardCache;
friend ProbingETLSource; friend ProbingETLSource;
@@ -161,7 +184,7 @@ class ETLSourceImpl : public ETLSource
std::vector<std::pair<uint32_t, uint32_t>> validatedLedgers_; std::vector<std::pair<uint32_t, uint32_t>> validatedLedgers_;
std::string validatedLedgersRaw_; std::string validatedLedgersRaw_{"N/A"};
std::shared_ptr<NetworkValidatedLedgers> networkValidatedLedgers_; std::shared_ptr<NetworkValidatedLedgers> networkValidatedLedgers_;
@@ -218,7 +241,7 @@ protected:
void void
run() override run() override
{ {
BOOST_LOG_TRIVIAL(trace) << __func__ << " : " << toString(); log_.trace() << toString();
auto const host = ip_; auto const host = ip_;
auto const port = wsPort_; auto const port = wsPort_;
@@ -292,14 +315,12 @@ public:
stub_ = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub( stub_ = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
grpc::CreateCustomChannel( grpc::CreateCustomChannel(
ss.str(), grpc::InsecureChannelCredentials(), chArgs)); ss.str(), grpc::InsecureChannelCredentials(), chArgs));
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Made stub for remote = " << toString();
<< "Made stub for remote = " << toString();
} }
catch (std::exception const& e) catch (std::exception const& e)
{ {
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Exception while creating stub = " << e.what()
<< "Exception while creating stub = " << e.what() << " . Remote = " << toString();
<< " . Remote = " << toString();
} }
} }
} }
@@ -371,7 +392,6 @@ public:
getValidatedRange() const getValidatedRange() const
{ {
std::lock_guard lck(mtx_); std::lock_guard lck(mtx_);
return validatedLedgersRaw_; return validatedLedgersRaw_;
} }
@@ -389,9 +409,8 @@ public:
std::string std::string
toString() const override toString() const override
{ {
return "{ validated_ledger : " + getValidatedRange() + return "{validated_ledger: " + getValidatedRange() + ", ip: " + ip_ +
" , ip : " + ip_ + " , web socket port : " + wsPort_ + ", web socket port: " + wsPort_ + ", grpc port: " + grpcPort_ + "}";
", grpc port : " + grpcPort_ + " }";
} }
boost::json::object boost::json::object
@@ -592,8 +611,8 @@ public:
class ETLLoadBalancer class ETLLoadBalancer
{ {
private: private:
clio::Logger log_{"ETL"};
std::vector<std::unique_ptr<ETLSource>> sources_; std::vector<std::unique_ptr<ETLSource>> sources_;
std::uint32_t downloadRanges_ = 16; std::uint32_t downloadRanges_ = 16;
public: public:
@@ -703,5 +722,3 @@ private:
bool bool
execute(Func f, uint32_t ledgerSequence); execute(Func f, uint32_t ledgerSequence);
}; };
#endif

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/app/tx/impl/details/NFTokenUtils.h> #include <ripple/app/tx/impl/details/NFTokenUtils.h>
#include <ripple/protocol/STBase.h> #include <ripple/protocol/STBase.h>
#include <ripple/protocol/STTx.h> #include <ripple/protocol/STTx.h>
@@ -107,8 +126,7 @@ getNFTokenMintData(ripple::TxMeta const& txMeta, ripple::STTx const& sttx)
NFTsData(tokenIDResult.front(), *owner, txMeta, false)}; NFTsData(tokenIDResult.front(), *owner, txMeta, false)};
std::stringstream msg; std::stringstream msg;
msg << __func__ << " - unexpected NFTokenMint data in tx " msg << " - unexpected NFTokenMint data in tx " << sttx.getTransactionID();
<< sttx.getTransactionID();
throw std::runtime_error(msg.str()); throw std::runtime_error(msg.str());
} }
@@ -173,7 +191,7 @@ getNFTokenBurnData(ripple::TxMeta const& txMeta, ripple::STTx const& sttx)
} }
std::stringstream msg; std::stringstream msg;
msg << __func__ << " - could not determine owner at burntime for tx " msg << " - could not determine owner at burntime for tx "
<< sttx.getTransactionID(); << sttx.getTransactionID();
throw std::runtime_error(msg.str()); throw std::runtime_error(msg.str());
} }
@@ -198,7 +216,7 @@ getNFTokenAcceptOfferData(
if (affectedBuyOffer == txMeta.getNodes().end()) if (affectedBuyOffer == txMeta.getNodes().end())
{ {
std::stringstream msg; std::stringstream msg;
msg << __func__ << " - unexpected NFTokenAcceptOffer data in tx " msg << " - unexpected NFTokenAcceptOffer data in tx "
<< sttx.getTransactionID(); << sttx.getTransactionID();
throw std::runtime_error(msg.str()); throw std::runtime_error(msg.str());
} }
@@ -228,7 +246,7 @@ getNFTokenAcceptOfferData(
if (affectedSellOffer == txMeta.getNodes().end()) if (affectedSellOffer == txMeta.getNodes().end())
{ {
std::stringstream msg; std::stringstream msg;
msg << __func__ << " - unexpected NFTokenAcceptOffer data in tx " msg << " - unexpected NFTokenAcceptOffer data in tx "
<< sttx.getTransactionID(); << sttx.getTransactionID();
throw std::runtime_error(msg.str()); throw std::runtime_error(msg.str());
} }
@@ -278,7 +296,7 @@ getNFTokenAcceptOfferData(
} }
std::stringstream msg; std::stringstream msg;
msg << __func__ << " - unexpected NFTokenAcceptOffer data in tx " msg << " - unexpected NFTokenAcceptOffer data in tx "
<< sttx.getTransactionID(); << sttx.getTransactionID();
throw std::runtime_error(msg.str()); throw std::runtime_error(msg.str());
} }

View File

@@ -1,4 +1,26 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <etl/ProbingETLSource.h> #include <etl/ProbingETLSource.h>
#include <log/Logger.h>
using namespace clio;
ProbingETLSource::ProbingETLSource( ProbingETLSource::ProbingETLSource(
clio::Config const& config, clio::Config const& config,
@@ -85,7 +107,8 @@ std::string
ProbingETLSource::toString() const ProbingETLSource::toString() const
{ {
if (!currentSrc_) if (!currentSrc_)
return "{ probing }"; return "{probing... ws: " + plainSrc_->toString() +
", wss: " + sslSrc_->toString() + "}";
return currentSrc_->toString(); return currentSrc_->toString();
} }
@@ -148,9 +171,8 @@ ProbingETLSource::make_SSLHooks() noexcept
{ {
plainSrc_->pause(); plainSrc_->pause();
currentSrc_ = sslSrc_; currentSrc_ = sslSrc_;
BOOST_LOG_TRIVIAL(info) log_.info() << "Selected WSS as the main source: "
<< "Selected WSS as the main source: " << currentSrc_->toString();
<< currentSrc_->toString();
} }
return ETLSourceHooks::Action::PROCEED; return ETLSourceHooks::Action::PROCEED;
}, },
@@ -179,9 +201,8 @@ ProbingETLSource::make_PlainHooks() noexcept
{ {
sslSrc_->pause(); sslSrc_->pause();
currentSrc_ = plainSrc_; currentSrc_ = plainSrc_;
BOOST_LOG_TRIVIAL(info) log_.info() << "Selected Plain WS as the main source: "
<< "Selected Plain WS as the main source: " << currentSrc_->toString();
<< currentSrc_->toString();
} }
return ETLSourceHooks::Action::PROCEED; return ETLSourceHooks::Action::PROCEED;
}, },

View File

@@ -1,5 +1,23 @@
#ifndef RIPPLE_APP_REPORTING_PROBINGETLSOURCE_H_INCLUDED //------------------------------------------------------------------------------
#define RIPPLE_APP_REPORTING_PROBINGETLSOURCE_H_INCLUDED /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/beast/core.hpp> #include <boost/beast/core.hpp>
@@ -11,6 +29,7 @@
#include <config/Config.h> #include <config/Config.h>
#include <etl/ETLSource.h> #include <etl/ETLSource.h>
#include <log/Logger.h>
/// This ETLSource implementation attempts to connect over both secure websocket /// This ETLSource implementation attempts to connect over both secure websocket
/// and plain websocket. First to connect pauses the other and the probing is /// and plain websocket. First to connect pauses the other and the probing is
@@ -18,6 +37,8 @@
/// connection the probing is kickstarted again. /// connection the probing is kickstarted again.
class ProbingETLSource : public ETLSource class ProbingETLSource : public ETLSource
{ {
clio::Logger log_{"ETL"};
std::mutex mtx_; std::mutex mtx_;
boost::asio::ssl::context sslCtx_; boost::asio::ssl::context sslCtx_;
std::shared_ptr<ETLSource> sslSrc_; std::shared_ptr<ETLSource> sslSrc_;
@@ -89,5 +110,3 @@ private:
ETLSourceHooks ETLSourceHooks
make_PlainHooks() noexcept; make_PlainHooks() noexcept;
}; };
#endif

View File

@@ -1,20 +1,45 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/basics/StringUtilities.h> #include <ripple/basics/StringUtilities.h>
#include <ripple/beast/core/CurrentThreadName.h>
#include <backend/DBHelpers.h> #include <backend/DBHelpers.h>
#include <etl/ReportingETL.h> #include <etl/ReportingETL.h>
#include <log/Logger.h>
#include <subscriptions/SubscriptionManager.h>
#include <util/Profiler.h>
#include <ripple/beast/core/CurrentThreadName.h>
#include <boost/asio/connect.hpp> #include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp> #include <boost/asio/ip/tcp.hpp>
#include <boost/beast/core.hpp> #include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp> #include <boost/beast/websocket.hpp>
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <subscriptions/SubscriptionManager.h>
#include <thread> #include <thread>
#include <variant> #include <variant>
namespace detail { using namespace clio;
namespace clio::detail {
/// Convenience function for printing out basic ledger info /// Convenience function for printing out basic ledger info
std::string std::string
toString(ripple::LedgerInfo const& info) toString(ripple::LedgerInfo const& info)
@@ -26,7 +51,7 @@ toString(ripple::LedgerInfo const& info)
<< " ParentHash : " << strHex(info.parentHash) << " }"; << " ParentHash : " << strHex(info.parentHash) << " }";
return ss.str(); return ss.str();
} }
} // namespace detail } // namespace clio::detail
FormattedTransactionsData FormattedTransactionsData
ReportingETL::insertTransactions( ReportingETL::insertTransactions(
@@ -43,9 +68,7 @@ ReportingETL::insertTransactions(
ripple::SerialIter it{raw->data(), raw->size()}; ripple::SerialIter it{raw->data(), raw->size()};
ripple::STTx sttx{it}; ripple::STTx sttx{it};
BOOST_LOG_TRIVIAL(trace) log_.trace() << "Inserting transaction = " << sttx.getTransactionID();
<< __func__ << " : "
<< "Inserting transaction = " << sttx.getTransactionID();
ripple::TxMeta txMeta{ ripple::TxMeta txMeta{
sttx.getTransactionID(), ledger.seq, txn.metadata_blob()}; sttx.getTransactionID(), ledger.seq, txn.metadata_blob()};
@@ -97,8 +120,7 @@ ReportingETL::loadInitialLedger(uint32_t startingSequence)
auto rng = backend_->hardFetchLedgerRangeNoThrow(); auto rng = backend_->hardFetchLedgerRangeNoThrow();
if (rng) if (rng)
{ {
BOOST_LOG_TRIVIAL(fatal) << __func__ << " : " log_.fatal() << "Database is not empty";
<< "Database is not empty";
assert(false); assert(false);
return {}; return {};
} }
@@ -114,56 +136,52 @@ ReportingETL::loadInitialLedger(uint32_t startingSequence)
ripple::LedgerInfo lgrInfo = ripple::LedgerInfo lgrInfo =
deserializeHeader(ripple::makeSlice(ledgerData->ledger_header())); deserializeHeader(ripple::makeSlice(ledgerData->ledger_header()));
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Deserialized ledger header. " << detail::toString(lgrInfo);
<< __func__ << " : "
<< "Deserialized ledger header. " << detail::toString(lgrInfo);
auto start = std::chrono::system_clock::now(); auto timeDiff = util::timed<std::chrono::duration<double>>([&]() {
backend_->startWrites();
backend_->startWrites(); log_.debug() << "Started writes";
BOOST_LOG_TRIVIAL(debug) << __func__ << " started writes"; backend_->writeLedger(
lgrInfo, std::move(*ledgerData->mutable_ledger_header()));
backend_->writeLedger( log_.debug() << "Wrote ledger";
lgrInfo, std::move(*ledgerData->mutable_ledger_header())); FormattedTransactionsData insertTxResult =
insertTransactions(lgrInfo, *ledgerData);
log_.debug() << "Inserted txns";
BOOST_LOG_TRIVIAL(debug) << __func__ << " wrote ledger"; // download the full account state map. This function downloads full
FormattedTransactionsData insertTxResult = // ledger data and pushes the downloaded data into the writeQueue.
insertTransactions(lgrInfo, *ledgerData); // asyncWriter consumes from the queue and inserts the data into the
BOOST_LOG_TRIVIAL(debug) << __func__ << " inserted txns"; // Ledger object. Once the below call returns, all data has been pushed
// into the queue
loadBalancer_->loadInitialLedger(startingSequence);
// download the full account state map. This function downloads full ledger log_.debug() << "Loaded initial ledger";
// data and pushes the downloaded data into the writeQueue. asyncWriter
// consumes from the queue and inserts the data into the Ledger object.
// Once the below call returns, all data has been pushed into the queue
loadBalancer_->loadInitialLedger(startingSequence);
BOOST_LOG_TRIVIAL(debug) << __func__ << " loaded initial ledger"; if (!stopping_)
{
if (!stopping_) backend_->writeAccountTransactions(
{ std::move(insertTxResult.accountTxData));
backend_->writeAccountTransactions( backend_->writeNFTs(std::move(insertTxResult.nfTokensData));
std::move(insertTxResult.accountTxData)); backend_->writeNFTTransactions(
backend_->writeNFTs(std::move(insertTxResult.nfTokensData)); std::move(insertTxResult.nfTokenTxData));
backend_->writeNFTTransactions(std::move(insertTxResult.nfTokenTxData)); }
} backend_->finishWrites(startingSequence);
backend_->finishWrites(startingSequence); });
log_.debug() << "Time to download and store ledger = " << timeDiff;
auto end = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(debug) << "Time to download and store ledger = "
<< ((end - start).count()) / 1000000000.0;
return lgrInfo; return lgrInfo;
} }
void void
ReportingETL::publishLedger(ripple::LedgerInfo const& lgrInfo) ReportingETL::publishLedger(ripple::LedgerInfo const& lgrInfo)
{ {
BOOST_LOG_TRIVIAL(debug) log_.info() << "Publishing ledger " << std::to_string(lgrInfo.seq);
<< __func__ << " - Publishing ledger " << std::to_string(lgrInfo.seq);
if (!writing_) if (!writing_)
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " - Updating cache"; log_.info() << "Updating cache";
std::vector<Backend::LedgerObject> diff = std::vector<Backend::LedgerObject> diff =
Backend::synchronousAndRetryOnTimeout([&](auto yield) { Backend::synchronousAndRetryOnTimeout([&](auto yield) {
@@ -205,12 +223,11 @@ ReportingETL::publishLedger(ripple::LedgerInfo const& lgrInfo)
subscriptions_->pubBookChanges(lgrInfo, transactions); subscriptions_->pubBookChanges(lgrInfo, transactions);
BOOST_LOG_TRIVIAL(info) << __func__ << " - Published ledger " log_.info() << "Published ledger " << std::to_string(lgrInfo.seq);
<< std::to_string(lgrInfo.seq);
} }
else else
BOOST_LOG_TRIVIAL(info) << __func__ << " - Skipping publishing ledger " log_.info() << "Skipping publishing ledger "
<< std::to_string(lgrInfo.seq); << std::to_string(lgrInfo.seq);
setLastPublish(); setLastPublish();
} }
@@ -219,9 +236,7 @@ ReportingETL::publishLedger(
uint32_t ledgerSequence, uint32_t ledgerSequence,
std::optional<uint32_t> maxAttempts) std::optional<uint32_t> maxAttempts)
{ {
BOOST_LOG_TRIVIAL(info) log_.info() << "Attempting to publish ledger = " << ledgerSequence;
<< __func__ << " : "
<< "Attempting to publish ledger = " << ledgerSequence;
size_t numAttempts = 0; size_t numAttempts = 0;
while (!stopping_) while (!stopping_)
{ {
@@ -229,17 +244,15 @@ ReportingETL::publishLedger(
if (!range || range->maxSequence < ledgerSequence) if (!range || range->maxSequence < ledgerSequence)
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " : " log_.debug() << "Trying to publish. Could not find "
<< "Trying to publish. Could not find " "ledger with sequence = "
"ledger with sequence = " << ledgerSequence;
<< ledgerSequence;
// We try maxAttempts times to publish the ledger, waiting one // We try maxAttempts times to publish the ledger, waiting one
// second in between each attempt. // second in between each attempt.
if (maxAttempts && numAttempts >= maxAttempts) if (maxAttempts && numAttempts >= maxAttempts)
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " : " log_.debug() << "Failed to publish ledger after " << numAttempts
<< "Failed to publish ledger after " << " attempts.";
<< numAttempts << " attempts.";
return false; return false;
} }
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
@@ -264,25 +277,19 @@ ReportingETL::publishLedger(
std::optional<org::xrpl::rpc::v1::GetLedgerResponse> std::optional<org::xrpl::rpc::v1::GetLedgerResponse>
ReportingETL::fetchLedgerData(uint32_t seq) ReportingETL::fetchLedgerData(uint32_t seq)
{ {
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Attempting to fetch ledger with sequence = " << seq;
<< __func__ << " : "
<< "Attempting to fetch ledger with sequence = " << seq;
std::optional<org::xrpl::rpc::v1::GetLedgerResponse> response = std::optional<org::xrpl::rpc::v1::GetLedgerResponse> response =
loadBalancer_->fetchLedger(seq, false, false); loadBalancer_->fetchLedger(seq, false, false);
if (response) if (response)
BOOST_LOG_TRIVIAL(trace) log_.trace() << "GetLedger reply = " << response->DebugString();
<< __func__ << " : "
<< "GetLedger reply = " << response->DebugString();
return response; return response;
} }
std::optional<org::xrpl::rpc::v1::GetLedgerResponse> std::optional<org::xrpl::rpc::v1::GetLedgerResponse>
ReportingETL::fetchLedgerDataAndDiff(uint32_t seq) ReportingETL::fetchLedgerDataAndDiff(uint32_t seq)
{ {
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Attempting to fetch ledger with sequence = " << seq;
<< __func__ << " : "
<< "Attempting to fetch ledger with sequence = " << seq;
std::optional<org::xrpl::rpc::v1::GetLedgerResponse> response = std::optional<org::xrpl::rpc::v1::GetLedgerResponse> response =
loadBalancer_->fetchLedger( loadBalancer_->fetchLedger(
@@ -291,47 +298,36 @@ ReportingETL::fetchLedgerDataAndDiff(uint32_t seq)
!backend_->cache().isFull() || !backend_->cache().isFull() ||
backend_->cache().latestLedgerSequence() >= seq); backend_->cache().latestLedgerSequence() >= seq);
if (response) if (response)
BOOST_LOG_TRIVIAL(trace) log_.trace() << "GetLedger reply = " << response->DebugString();
<< __func__ << " : "
<< "GetLedger reply = " << response->DebugString();
return response; return response;
} }
std::pair<ripple::LedgerInfo, bool> std::pair<ripple::LedgerInfo, bool>
ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData) ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " : " log_.debug() << "Beginning ledger update";
<< "Beginning ledger update";
ripple::LedgerInfo lgrInfo = ripple::LedgerInfo lgrInfo =
deserializeHeader(ripple::makeSlice(rawData.ledger_header())); deserializeHeader(ripple::makeSlice(rawData.ledger_header()));
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Deserialized ledger header. " << detail::toString(lgrInfo);
<< __func__ << " : "
<< "Deserialized ledger header. " << detail::toString(lgrInfo);
backend_->startWrites(); backend_->startWrites();
log_.debug() << "started writes";
BOOST_LOG_TRIVIAL(debug) << __func__ << " : "
<< "started writes";
backend_->writeLedger(lgrInfo, std::move(*rawData.mutable_ledger_header())); backend_->writeLedger(lgrInfo, std::move(*rawData.mutable_ledger_header()));
log_.debug() << "wrote ledger header";
BOOST_LOG_TRIVIAL(debug) << __func__ << " : "
<< "wrote ledger header";
// Write successor info, if included from rippled // Write successor info, if included from rippled
if (rawData.object_neighbors_included()) if (rawData.object_neighbors_included())
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " object neighbors included"; log_.debug() << "object neighbors included";
for (auto& obj : *(rawData.mutable_book_successors())) for (auto& obj : *(rawData.mutable_book_successors()))
{ {
auto firstBook = std::move(*obj.mutable_first_book()); auto firstBook = std::move(*obj.mutable_first_book());
if (!firstBook.size()) if (!firstBook.size())
firstBook = uint256ToString(Backend::lastKey); firstBook = uint256ToString(Backend::lastKey);
BOOST_LOG_TRIVIAL(debug) << __func__ << " writing book successor " log_.debug() << "writing book successor "
<< ripple::strHex(obj.book_base()) << " - " << ripple::strHex(obj.book_base()) << " - "
<< ripple::strHex(firstBook); << ripple::strHex(firstBook);
backend_->writeSuccessor( backend_->writeSuccessor(
std::move(*obj.mutable_book_base()), std::move(*obj.mutable_book_base()),
@@ -352,23 +348,20 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
if (obj.mod_type() == if (obj.mod_type() ==
org::xrpl::rpc::v1::RawLedgerObject::DELETED) org::xrpl::rpc::v1::RawLedgerObject::DELETED)
{ {
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Modifying successors for deleted object "
<< __func__ << ripple::strHex(obj.key()) << " - "
<< " modifying successors for deleted object " << ripple::strHex(*predPtr) << " - "
<< ripple::strHex(obj.key()) << " - " << ripple::strHex(*succPtr);
<< ripple::strHex(*predPtr) << " - "
<< ripple::strHex(*succPtr);
backend_->writeSuccessor( backend_->writeSuccessor(
std::move(*predPtr), lgrInfo.seq, std::move(*succPtr)); std::move(*predPtr), lgrInfo.seq, std::move(*succPtr));
} }
else else
{ {
BOOST_LOG_TRIVIAL(debug) log_.debug() << "adding successor for new object "
<< __func__ << " adding successor for new object " << ripple::strHex(obj.key()) << " - "
<< ripple::strHex(obj.key()) << " - " << ripple::strHex(*predPtr) << " - "
<< ripple::strHex(*predPtr) << " - " << ripple::strHex(*succPtr);
<< ripple::strHex(*succPtr);
backend_->writeSuccessor( backend_->writeSuccessor(
std::move(*predPtr), std::move(*predPtr),
@@ -381,8 +374,7 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
} }
} }
else else
BOOST_LOG_TRIVIAL(debug) << __func__ << " object modified " log_.debug() << "object modified " << ripple::strHex(obj.key());
<< ripple::strHex(obj.key());
} }
} }
std::vector<Backend::LedgerObject> cacheUpdates; std::vector<Backend::LedgerObject> cacheUpdates;
@@ -396,15 +388,13 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
assert(key); assert(key);
cacheUpdates.push_back( cacheUpdates.push_back(
{*key, {obj.mutable_data()->begin(), obj.mutable_data()->end()}}); {*key, {obj.mutable_data()->begin(), obj.mutable_data()->end()}});
BOOST_LOG_TRIVIAL(debug) log_.debug() << "key = " << ripple::strHex(*key)
<< __func__ << " key = " << ripple::strHex(*key) << " - mod type = " << obj.mod_type();
<< " - mod type = " << obj.mod_type();
if (obj.mod_type() != org::xrpl::rpc::v1::RawLedgerObject::MODIFIED && if (obj.mod_type() != org::xrpl::rpc::v1::RawLedgerObject::MODIFIED &&
!rawData.object_neighbors_included()) !rawData.object_neighbors_included())
{ {
BOOST_LOG_TRIVIAL(debug) log_.debug() << "object neighbors not included. using cache";
<< __func__ << " object neighbors not included. using cache";
if (!backend_->cache().isFull() || if (!backend_->cache().isFull() ||
backend_->cache().latestLedgerSequence() != lgrInfo.seq - 1) backend_->cache().latestLedgerSequence() != lgrInfo.seq - 1)
throw std::runtime_error( throw std::runtime_error(
@@ -423,9 +413,7 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
checkBookBase = isBookDir(*key, *blob); checkBookBase = isBookDir(*key, *blob);
if (checkBookBase) if (checkBookBase)
{ {
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Is book dir. key = " << ripple::strHex(*key);
<< __func__
<< " is book dir. key = " << ripple::strHex(*key);
auto bookBase = getBookBase(*key); auto bookBase = getBookBase(*key);
auto oldFirstDir = auto oldFirstDir =
backend_->cache().getSuccessor(bookBase, lgrInfo.seq - 1); backend_->cache().getSuccessor(bookBase, lgrInfo.seq - 1);
@@ -435,9 +423,8 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
if ((isDeleted && key == oldFirstDir->key) || if ((isDeleted && key == oldFirstDir->key) ||
(!isDeleted && key < oldFirstDir->key)) (!isDeleted && key < oldFirstDir->key))
{ {
BOOST_LOG_TRIVIAL(debug) log_.debug()
<< __func__ << "Need to recalculate book base successor. base = "
<< " Need to recalculate book base successor. base = "
<< ripple::strHex(bookBase) << ripple::strHex(bookBase)
<< " - key = " << ripple::strHex(*key) << " - key = " << ripple::strHex(*key)
<< " - isDeleted = " << isDeleted << " - isDeleted = " << isDeleted
@@ -458,8 +445,7 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
// rippled didn't send successor information, so use our cache // rippled didn't send successor information, so use our cache
if (!rawData.object_neighbors_included()) if (!rawData.object_neighbors_included())
{ {
BOOST_LOG_TRIVIAL(debug) log_.debug() << "object neighbors not included. using cache";
<< __func__ << " object neighbors not included. using cache";
if (!backend_->cache().isFull() || if (!backend_->cache().isFull() ||
backend_->cache().latestLedgerSequence() != lgrInfo.seq) backend_->cache().latestLedgerSequence() != lgrInfo.seq)
throw std::runtime_error( throw std::runtime_error(
@@ -477,11 +463,10 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
ub = {Backend::lastKey, {}}; ub = {Backend::lastKey, {}};
if (obj.blob.size() == 0) if (obj.blob.size() == 0)
{ {
BOOST_LOG_TRIVIAL(debug) log_.debug() << "writing successor for deleted object "
<< __func__ << " writing successor for deleted object " << ripple::strHex(obj.key) << " - "
<< ripple::strHex(obj.key) << " - " << ripple::strHex(lb->key) << " - "
<< ripple::strHex(lb->key) << " - " << ripple::strHex(ub->key);
<< ripple::strHex(ub->key);
backend_->writeSuccessor( backend_->writeSuccessor(
uint256ToString(lb->key), uint256ToString(lb->key),
@@ -499,11 +484,10 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
lgrInfo.seq, lgrInfo.seq,
uint256ToString(ub->key)); uint256ToString(ub->key));
BOOST_LOG_TRIVIAL(debug) log_.debug() << "writing successor for new object "
<< __func__ << " writing successor for new object " << ripple::strHex(lb->key) << " - "
<< ripple::strHex(lb->key) << " - " << ripple::strHex(obj.key) << " - "
<< ripple::strHex(obj.key) << " - " << ripple::strHex(ub->key);
<< ripple::strHex(ub->key);
} }
} }
for (auto const& base : bookSuccessorsToCalculate) for (auto const& base : bookSuccessorsToCalculate)
@@ -516,10 +500,9 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
lgrInfo.seq, lgrInfo.seq,
uint256ToString(succ->key)); uint256ToString(succ->key));
BOOST_LOG_TRIVIAL(debug) log_.debug()
<< __func__ << " Updating book successor " << "Updating book successor " << ripple::strHex(base)
<< ripple::strHex(base) << " - " << " - " << ripple::strHex(succ->key);
<< ripple::strHex(succ->key);
} }
else else
{ {
@@ -528,42 +511,31 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
lgrInfo.seq, lgrInfo.seq,
uint256ToString(Backend::lastKey)); uint256ToString(Backend::lastKey));
BOOST_LOG_TRIVIAL(debug) log_.debug()
<< __func__ << " Updating book successor " << "Updating book successor " << ripple::strHex(base)
<< ripple::strHex(base) << " - " << " - " << ripple::strHex(Backend::lastKey);
<< ripple::strHex(Backend::lastKey);
} }
} }
} }
BOOST_LOG_TRIVIAL(debug) log_.debug()
<< __func__ << " : "
<< "Inserted/modified/deleted all objects. Number of objects = " << "Inserted/modified/deleted all objects. Number of objects = "
<< rawData.ledger_objects().objects_size(); << rawData.ledger_objects().objects_size();
FormattedTransactionsData insertTxResult = FormattedTransactionsData insertTxResult =
insertTransactions(lgrInfo, rawData); insertTransactions(lgrInfo, rawData);
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Inserted all transactions. Number of transactions = "
<< __func__ << " : " << rawData.transactions_list().transactions_size();
<< "Inserted all transactions. Number of transactions = "
<< rawData.transactions_list().transactions_size();
backend_->writeAccountTransactions(std::move(insertTxResult.accountTxData)); backend_->writeAccountTransactions(std::move(insertTxResult.accountTxData));
backend_->writeNFTs(std::move(insertTxResult.nfTokensData)); backend_->writeNFTs(std::move(insertTxResult.nfTokensData));
backend_->writeNFTTransactions(std::move(insertTxResult.nfTokenTxData)); backend_->writeNFTTransactions(std::move(insertTxResult.nfTokenTxData));
BOOST_LOG_TRIVIAL(debug) << __func__ << " : " log_.debug() << "wrote account_tx";
<< "wrote account_tx";
auto start = std::chrono::system_clock::now();
bool success = backend_->finishWrites(lgrInfo.seq); auto [success, duration] = util::timed<std::chrono::duration<double>>(
[&]() { return backend_->finishWrites(lgrInfo.seq); });
auto end = std::chrono::system_clock::now(); log_.debug() << "Finished writes. took " << std::to_string(duration);
log_.debug() << "Finished ledger update. " << detail::toString(lgrInfo);
auto duration = ((end - start).count()) / 1000000000.0;
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " finished writes. took " << std::to_string(duration);
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " : "
<< "Finished ledger update. " << detail::toString(lgrInfo);
return {lgrInfo, success}; return {lgrInfo, success};
} }
@@ -596,8 +568,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
* optional, returns. * optional, returns.
*/ */
BOOST_LOG_TRIVIAL(debug) << __func__ << " : " log_.debug() << "Starting etl pipeline";
<< "Starting etl pipeline";
writing_ = true; writing_ = true;
auto rng = backend_->hardFetchLedgerRangeNoThrow(); auto rng = backend_->hardFetchLedgerRangeNoThrow();
@@ -647,12 +618,10 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
currentSequence) && currentSequence) &&
!writeConflict && !isStopping()) !writeConflict && !isStopping())
{ {
auto start = std::chrono::system_clock::now(); auto [fetchResponse, time] =
std::optional<org::xrpl::rpc::v1::GetLedgerResponse> util::timed<std::chrono::duration<double>>([&]() {
fetchResponse{fetchLedgerDataAndDiff(currentSequence)}; return fetchLedgerDataAndDiff(currentSequence);
auto end = std::chrono::system_clock::now(); });
auto time = ((end - start).count()) / 1000000000.0;
totalTime += time; totalTime += time;
// if the fetch is unsuccessful, stop. fetchLedger only // if the fetch is unsuccessful, stop. fetchLedger only
@@ -670,13 +639,12 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
fetchResponse->transactions_list().transactions_size() / fetchResponse->transactions_list().transactions_size() /
time; time;
BOOST_LOG_TRIVIAL(info) log_.info() << "Extract phase time = " << time
<< "Extract phase time = " << time << " . Extract phase tps = " << tps
<< " . Extract phase tps = " << tps << " . Avg extract time = "
<< " . Avg extract time = " << totalTime / (currentSequence - startSequence + 1)
<< totalTime / (currentSequence - startSequence + 1) << " . thread num = " << i
<< " . thread num = " << i << " . seq = " << currentSequence;
<< " . seq = " << currentSequence;
transformQueue->push(std::move(fetchResponse)); transformQueue->push(std::move(fetchResponse));
currentSequence += numExtractors; currentSequence += numExtractors;
@@ -720,7 +688,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
auto duration = ((end - start).count()) / 1000000000.0; auto duration = ((end - start).count()) / 1000000000.0;
if (success) if (success)
BOOST_LOG_TRIVIAL(info) log_.info()
<< "Load phase of etl : " << "Load phase of etl : "
<< "Successfully wrote ledger! Ledger info: " << "Successfully wrote ledger! Ledger info: "
<< detail::toString(lgrInfo) << ". txn count = " << numTxns << detail::toString(lgrInfo) << ". txn count = " << numTxns
@@ -729,7 +697,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
<< ". load txns per second = " << numTxns / duration << ". load txns per second = " << numTxns / duration
<< ". load objs per second = " << numObjects / duration; << ". load objs per second = " << numObjects / duration;
else else
BOOST_LOG_TRIVIAL(error) log_.error()
<< "Error writing ledger. " << detail::toString(lgrInfo); << "Error writing ledger. " << detail::toString(lgrInfo);
// success is false if the ledger was already written // success is false if the ledger was already written
if (success) if (success)
@@ -747,7 +715,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
{ {
deleting_ = true; deleting_ = true;
ioContext_.post([this, &minSequence]() { ioContext_.post([this, &minSequence]() {
BOOST_LOG_TRIVIAL(info) << "Running online delete"; log_.info() << "Running online delete";
Backend::synchronous( Backend::synchronous(
[&](boost::asio::yield_context& yield) { [&](boost::asio::yield_context& yield) {
@@ -755,7 +723,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
*onlineDeleteInterval_, yield); *onlineDeleteInterval_, yield);
}); });
BOOST_LOG_TRIVIAL(info) << "Finished online delete"; log_.info() << "Finished online delete";
auto rng = backend_->fetchLedgerRange(); auto rng = backend_->fetchLedgerRange();
minSequence = rng->minSequence; minSequence = rng->minSequence;
deleting_ = false; deleting_ = false;
@@ -774,13 +742,12 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
for (auto& t : extractors) for (auto& t : extractors)
t.join(); t.join();
auto end = std::chrono::system_clock::now(); auto end = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Extracted and wrote "
<< "Extracted and wrote " << *lastPublishedSequence - startSequence << *lastPublishedSequence - startSequence << " in "
<< " in " << ((end - begin).count()) / 1000000000.0; << ((end - begin).count()) / 1000000000.0;
writing_ = false; writing_ = false;
BOOST_LOG_TRIVIAL(debug) << __func__ << " : " log_.debug() << "Stopping etl pipeline";
<< "Stopping etl pipeline";
return lastPublishedSequence; return lastPublishedSequence;
} }
@@ -801,40 +768,34 @@ ReportingETL::monitor()
auto rng = backend_->hardFetchLedgerRangeNoThrow(); auto rng = backend_->hardFetchLedgerRangeNoThrow();
if (!rng) if (!rng)
{ {
BOOST_LOG_TRIVIAL(info) << __func__ << " : " log_.info() << "Database is empty. Will download a ledger "
<< "Database is empty. Will download a ledger " "from the network.";
"from the network.";
std::optional<ripple::LedgerInfo> ledger; std::optional<ripple::LedgerInfo> ledger;
if (startSequence_) if (startSequence_)
{ {
BOOST_LOG_TRIVIAL(info) log_.info() << "ledger sequence specified in config. "
<< __func__ << " : " << "Will begin ETL process starting with ledger "
<< "ledger sequence specified in config. " << *startSequence_;
<< "Will begin ETL process starting with ledger "
<< *startSequence_;
ledger = loadInitialLedger(*startSequence_); ledger = loadInitialLedger(*startSequence_);
} }
else else
{ {
BOOST_LOG_TRIVIAL(info) log_.info()
<< __func__ << " : "
<< "Waiting for next ledger to be validated by network..."; << "Waiting for next ledger to be validated by network...";
std::optional<uint32_t> mostRecentValidated = std::optional<uint32_t> mostRecentValidated =
networkValidatedLedgers_->getMostRecent(); networkValidatedLedgers_->getMostRecent();
if (mostRecentValidated) if (mostRecentValidated)
{ {
BOOST_LOG_TRIVIAL(info) << __func__ << " : " log_.info() << "Ledger " << *mostRecentValidated
<< "Ledger " << *mostRecentValidated << " has been validated. "
<< " has been validated. " << "Downloading...";
<< "Downloading...";
ledger = loadInitialLedger(*mostRecentValidated); ledger = loadInitialLedger(*mostRecentValidated);
} }
else else
{ {
BOOST_LOG_TRIVIAL(info) << __func__ << " : " log_.info() << "The wait for the next validated "
<< "The wait for the next validated " << "ledger has been aborted. "
<< "ledger has been aborted. " << "Exiting monitor loop";
<< "Exiting monitor loop";
return; return;
} }
} }
@@ -842,8 +803,7 @@ ReportingETL::monitor()
rng = backend_->hardFetchLedgerRangeNoThrow(); rng = backend_->hardFetchLedgerRangeNoThrow();
else else
{ {
BOOST_LOG_TRIVIAL(error) log_.error()
<< __func__ << " : "
<< "Failed to load initial ledger. Exiting monitor loop"; << "Failed to load initial ledger. Exiting monitor loop";
return; return;
} }
@@ -852,21 +812,18 @@ ReportingETL::monitor()
{ {
if (startSequence_) if (startSequence_)
{ {
BOOST_LOG_TRIVIAL(warning) log_.warn()
<< "start sequence specified but db is already populated"; << "start sequence specified but db is already populated";
} }
BOOST_LOG_TRIVIAL(info) log_.info()
<< __func__ << " : "
<< "Database already populated. Picking up from the tip of history"; << "Database already populated. Picking up from the tip of history";
loadCache(rng->maxSequence); loadCache(rng->maxSequence);
} }
assert(rng); assert(rng);
uint32_t nextSequence = rng->maxSequence + 1; uint32_t nextSequence = rng->maxSequence + 1;
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Database is populated. "
<< __func__ << " : " << "Starting monitor loop. sequence = " << nextSequence;
<< "Database is populated. "
<< "Starting monitor loop. sequence = " << nextSequence;
while (true) while (true)
{ {
if (auto rng = backend_->hardFetchLedgerRangeNoThrow(); if (auto rng = backend_->hardFetchLedgerRangeNoThrow();
@@ -878,11 +835,9 @@ ReportingETL::monitor()
else if (networkValidatedLedgers_->waitUntilValidatedByNetwork( else if (networkValidatedLedgers_->waitUntilValidatedByNetwork(
nextSequence, 1000)) nextSequence, 1000))
{ {
BOOST_LOG_TRIVIAL(info) log_.info() << "Ledger with sequence = " << nextSequence
<< __func__ << " : " << " has been validated by the network. "
<< "Ledger with sequence = " << nextSequence << "Attempting to find in database and publish";
<< " has been validated by the network. "
<< "Attempting to find in database and publish";
// Attempt to take over responsibility of ETL writer after 10 failed // Attempt to take over responsibility of ETL writer after 10 failed
// attempts to publish the ledger. publishLedger() fails if the // attempts to publish the ledger. publishLedger() fails if the
// ledger that has been validated by the network is not found in the // ledger that has been validated by the network is not found in the
@@ -893,17 +848,13 @@ ReportingETL::monitor()
bool success = publishLedger(nextSequence, timeoutSeconds); bool success = publishLedger(nextSequence, timeoutSeconds);
if (!success) if (!success)
{ {
BOOST_LOG_TRIVIAL(warning) log_.warn() << "Failed to publish ledger with sequence = "
<< __func__ << " : " << nextSequence << " . Beginning ETL";
<< "Failed to publish ledger with sequence = "
<< nextSequence << " . Beginning ETL";
// doContinousETLPipelined returns the most recent sequence // doContinousETLPipelined returns the most recent sequence
// published empty optional if no sequence was published // published empty optional if no sequence was published
std::optional<uint32_t> lastPublished = std::optional<uint32_t> lastPublished =
runETLPipeline(nextSequence, extractorThreads_); runETLPipeline(nextSequence, extractorThreads_);
BOOST_LOG_TRIVIAL(info) log_.info() << "Aborting ETL. Falling back to publishing";
<< __func__ << " : "
<< "Aborting ETL. Falling back to publishing";
// if no ledger was published, don't increment nextSequence // if no ledger was published, don't increment nextSequence
if (lastPublished) if (lastPublished)
nextSequence = *lastPublished + 1; nextSequence = *lastPublished + 1;
@@ -920,8 +871,8 @@ ReportingETL::loadCacheFromClioPeer(
std::string const& port, std::string const& port,
boost::asio::yield_context& yield) boost::asio::yield_context& yield)
{ {
BOOST_LOG_TRIVIAL(info) log_.info() << "Loading cache from peer. ip = " << ip
<< "Loading cache from peer. ip = " << ip << " . port = " << port; << " . port = " << port;
namespace beast = boost::beast; // from <boost/beast.hpp> namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp> namespace http = beast::http; // from <boost/beast/http.hpp>
namespace websocket = beast::websocket; // from namespace websocket = beast::websocket; // from
@@ -933,7 +884,7 @@ ReportingETL::loadCacheFromClioPeer(
// These objects perform our I/O // These objects perform our I/O
tcp::resolver resolver{ioContext_}; tcp::resolver resolver{ioContext_};
BOOST_LOG_TRIVIAL(trace) << __func__ << " Creating websocket"; log_.trace() << "Creating websocket";
auto ws = auto ws =
std::make_unique<websocket::stream<beast::tcp_stream>>(ioContext_); std::make_unique<websocket::stream<beast::tcp_stream>>(ioContext_);
@@ -942,14 +893,13 @@ ReportingETL::loadCacheFromClioPeer(
if (ec) if (ec)
return {}; return {};
BOOST_LOG_TRIVIAL(trace) << __func__ << " Connecting websocket"; log_.trace() << "Connecting websocket";
// Make the connection on the IP address we get from a lookup // Make the connection on the IP address we get from a lookup
ws->next_layer().async_connect(results, yield[ec]); ws->next_layer().async_connect(results, yield[ec]);
if (ec) if (ec)
return false; return false;
BOOST_LOG_TRIVIAL(trace) log_.trace() << "Performing websocket handshake";
<< __func__ << " Performing websocket handshake";
// Perform the websocket handshake // Perform the websocket handshake
ws->async_handshake(ip, "/", yield[ec]); ws->async_handshake(ip, "/", yield[ec]);
if (ec) if (ec)
@@ -957,7 +907,7 @@ ReportingETL::loadCacheFromClioPeer(
std::optional<boost::json::value> marker; std::optional<boost::json::value> marker;
BOOST_LOG_TRIVIAL(trace) << __func__ << " Sending request"; log_.trace() << "Sending request";
auto getRequest = [&](auto marker) { auto getRequest = [&](auto marker) {
boost::json::object request = { boost::json::object request = {
{"command", "ledger_data"}, {"command", "ledger_data"},
@@ -981,8 +931,7 @@ ReportingETL::loadCacheFromClioPeer(
yield[ec]); yield[ec]);
if (ec) if (ec)
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "error writing = " << ec.message();
<< __func__ << " error writing = " << ec.message();
return false; return false;
} }
@@ -990,8 +939,7 @@ ReportingETL::loadCacheFromClioPeer(
ws->async_read(buffer, yield[ec]); ws->async_read(buffer, yield[ec]);
if (ec) if (ec)
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "error reading = " << ec.message();
<< __func__ << " error reading = " << ec.message();
return false; return false;
} }
@@ -1000,36 +948,30 @@ ReportingETL::loadCacheFromClioPeer(
if (!parsed.is_object()) if (!parsed.is_object())
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "Error parsing response: " << raw;
<< __func__ << " Error parsing response: " << raw;
return false; return false;
} }
BOOST_LOG_TRIVIAL(trace) log_.trace() << "Successfully parsed response " << parsed;
<< __func__ << " Successfully parsed response " << parsed;
if (auto const& response = parsed.as_object(); if (auto const& response = parsed.as_object();
response.contains("error")) response.contains("error"))
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "Response contains error: " << response;
<< __func__ << " Response contains error: " << response;
auto const& err = response.at("error"); auto const& err = response.at("error");
if (err.is_string() && err.as_string() == "lgrNotFound") if (err.is_string() && err.as_string() == "lgrNotFound")
{ {
++numAttempts; ++numAttempts;
if (numAttempts >= 5) if (numAttempts >= 5)
{ {
BOOST_LOG_TRIVIAL(error) log_.error()
<< __func__
<< " ledger not found at peer after 5 attempts. " << " ledger not found at peer after 5 attempts. "
"peer = " "peer = "
<< ip << " ledger = " << ledgerIndex << ip << " ledger = " << ledgerIndex
<< ". Check your config and the health of the peer"; << ". Check your config and the health of the peer";
return false; return false;
} }
BOOST_LOG_TRIVIAL(warning) log_.warn() << "Ledger not found. ledger = " << ledgerIndex
<< __func__ << ". Sleeping and trying again";
<< " ledger not found. ledger = " << ledgerIndex
<< ". Sleeping and trying again";
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
continue; continue;
} }
@@ -1041,8 +983,7 @@ ReportingETL::loadCacheFromClioPeer(
if (!response.contains("cache_full") || if (!response.contains("cache_full") ||
!response.at("cache_full").as_bool()) !response.at("cache_full").as_bool())
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "cache not full for clio node. ip = " << ip;
<< __func__ << " cache not full for clio node. ip = " << ip;
return false; return false;
} }
if (response.contains("marker")) if (response.contains("marker"))
@@ -1063,8 +1004,7 @@ ReportingETL::loadCacheFromClioPeer(
if (!stateObject.key.parseHex( if (!stateObject.key.parseHex(
obj.at("index").as_string().c_str())) obj.at("index").as_string().c_str()))
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "failed to parse object id";
<< __func__ << " failed to parse object id";
return false; return false;
} }
boost::algorithm::unhex( boost::algorithm::unhex(
@@ -1075,22 +1015,19 @@ ReportingETL::loadCacheFromClioPeer(
backend_->cache().update(objects, ledgerIndex, true); backend_->cache().update(objects, ledgerIndex, true);
if (marker) if (marker)
BOOST_LOG_TRIVIAL(debug) log_.debug() << "At marker " << *marker;
<< __func__ << " - At marker " << *marker;
} while (marker || !started); } while (marker || !started);
BOOST_LOG_TRIVIAL(info)
<< __func__
<< " Finished downloading ledger from clio node. ip = " << ip;
backend_->cache().setFull();
log_.info() << "Finished downloading ledger from clio node. ip = "
<< ip;
backend_->cache().setFull();
return true; return true;
} }
catch (std::exception const& e) catch (std::exception const& e)
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "Encountered exception : " << e.what()
<< __func__ << " Encountered exception : " << e.what() << " - ip = " << ip;
<< " - ip = " << ip;
return false; return false;
} }
} }
@@ -1101,7 +1038,7 @@ ReportingETL::loadCache(uint32_t seq)
if (cacheLoadStyle_ == CacheLoadStyle::NOT_AT_ALL) if (cacheLoadStyle_ == CacheLoadStyle::NOT_AT_ALL)
{ {
backend_->cache().setDisabled(); backend_->cache().setDisabled();
BOOST_LOG_TRIVIAL(warning) << "Cache is disabled. Not loading"; log_.warn() << "Cache is disabled. Not loading";
return; return;
} }
// sanity check to make sure we are not calling this multiple times // sanity check to make sure we are not calling this multiple times
@@ -1142,12 +1079,11 @@ ReportingETL::loadCache(uint32_t seq)
while (cacheLoadStyle_ == CacheLoadStyle::SYNC && while (cacheLoadStyle_ == CacheLoadStyle::SYNC &&
!backend_->cache().isFull()) !backend_->cache().isFull())
{ {
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Cache not full. Cache size = "
<< "Cache not full. Cache size = " << backend_->cache().size() << backend_->cache().size() << ". Sleeping ...";
<< ". Sleeping ...";
std::this_thread::sleep_for(std::chrono::seconds(10)); std::this_thread::sleep_for(std::chrono::seconds(10));
BOOST_LOG_TRIVIAL(info) log_.info() << "Cache is full. Cache size = "
<< "Cache is full. Cache size = " << backend_->cache().size(); << backend_->cache().size();
} }
} }
@@ -1198,9 +1134,8 @@ ReportingETL::loadCacheFromDb(uint32_t seq)
if (c) if (c)
cursorStr << ripple::strHex(*c) << ", "; cursorStr << ripple::strHex(*c) << ", ";
} }
BOOST_LOG_TRIVIAL(info) log_.info() << "Loading cache. num cursors = " << cursors.size() - 1;
<< "Loading cache. num cursors = " << cursors.size() - 1; log_.trace() << "cursors = " << cursorStr.str();
BOOST_LOG_TRIVIAL(trace) << __func__ << " cursors = " << cursorStr.str();
cacheDownloader_ = std::thread{[this, seq, cursors]() { cacheDownloader_ = std::thread{[this, seq, cursors]() {
auto startTime = std::chrono::system_clock::now(); auto startTime = std::chrono::system_clock::now();
@@ -1221,9 +1156,8 @@ ReportingETL::loadCacheFromDb(uint32_t seq)
std::string cursorStr = cursor.has_value() std::string cursorStr = cursor.has_value()
? ripple::strHex(cursor.value()) ? ripple::strHex(cursor.value())
: ripple::strHex(Backend::firstKey); : ripple::strHex(Backend::firstKey);
BOOST_LOG_TRIVIAL(debug) log_.debug() << "Starting a cursor: " << cursorStr
<< "Starting a cursor: " << cursorStr << " markers = " << *markers;
<< " markers = " << *markers;
while (!stopping_) while (!stopping_)
{ {
@@ -1237,7 +1171,7 @@ ReportingETL::loadCacheFromDb(uint32_t seq)
backend_->cache().update(res.objects, seq, true); backend_->cache().update(res.objects, seq, true);
if (!res.cursor || (end && *(res.cursor) > *end)) if (!res.cursor || (end && *(res.cursor) > *end))
break; break;
BOOST_LOG_TRIVIAL(trace) log_.trace()
<< "Loading cache. cache size = " << "Loading cache. cache size = "
<< backend_->cache().size() << " - cursor = " << backend_->cache().size() << " - cursor = "
<< ripple::strHex(res.cursor.value()) << ripple::strHex(res.cursor.value())
@@ -1254,18 +1188,16 @@ ReportingETL::loadCacheFromDb(uint32_t seq)
auto duration = auto duration =
std::chrono::duration_cast<std::chrono::seconds>( std::chrono::duration_cast<std::chrono::seconds>(
endTime - startTime); endTime - startTime);
BOOST_LOG_TRIVIAL(info) log_.info() << "Finished loading cache. cache size = "
<< "Finished loading cache. cache size = " << backend_->cache().size() << ". Took "
<< backend_->cache().size() << ". Took " << duration.count() << " seconds";
<< duration.count() << " seconds";
backend_->cache().setFull(); backend_->cache().setFull();
} }
else else
{ {
BOOST_LOG_TRIVIAL(info) log_.info() << "Finished a cursor. num remaining = "
<< "Finished a cursor. num remaining = " << *numRemaining << " start = " << cursorStr
<< *numRemaining << " start = " << cursorStr << " markers = " << *markers;
<< " markers = " << *markers;
} }
}); });
} }
@@ -1275,7 +1207,7 @@ ReportingETL::loadCacheFromDb(uint32_t seq)
void void
ReportingETL::monitorReadOnly() ReportingETL::monitorReadOnly()
{ {
BOOST_LOG_TRIVIAL(debug) << "Starting reporting in strict read only mode"; log_.debug() << "Starting reporting in strict read only mode";
auto rng = backend_->hardFetchLedgerRangeNoThrow(); auto rng = backend_->hardFetchLedgerRangeNoThrow();
uint32_t latestSequence; uint32_t latestSequence;
if (!rng) if (!rng)

View File

@@ -1,5 +1,23 @@
#ifndef RIPPLE_APP_REPORTING_REPORTINGETL_H_INCLUDED //------------------------------------------------------------------------------
#define RIPPLE_APP_REPORTING_REPORTINGETL_H_INCLUDED /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/ledger/ReadView.h> #include <ripple/ledger/ReadView.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
@@ -8,6 +26,7 @@
#include <boost/beast/websocket.hpp> #include <boost/beast/websocket.hpp>
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <etl/ETLSource.h> #include <etl/ETLSource.h>
#include <log/Logger.h>
#include <subscriptions/SubscriptionManager.h> #include <subscriptions/SubscriptionManager.h>
#include "org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h" #include "org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h"
@@ -55,6 +74,8 @@ class SubscriptionManager;
class ReportingETL class ReportingETL
{ {
private: private:
clio::Logger log_{"ETL"};
std::shared_ptr<BackendInterface> backend_; std::shared_ptr<BackendInterface> backend_;
std::shared_ptr<SubscriptionManager> subscriptions_; std::shared_ptr<SubscriptionManager> subscriptions_;
std::shared_ptr<ETLLoadBalancer> loadBalancer_; std::shared_ptr<ETLLoadBalancer> loadBalancer_;
@@ -300,7 +321,7 @@ private:
void void
run() run()
{ {
BOOST_LOG_TRIVIAL(info) << "Starting reporting etl"; log_.info() << "Starting reporting etl";
stopping_ = false; stopping_ = false;
doWork(); doWork();
@@ -337,8 +358,8 @@ public:
~ReportingETL() ~ReportingETL()
{ {
BOOST_LOG_TRIVIAL(info) << "onStop called"; log_.info() << "onStop called";
BOOST_LOG_TRIVIAL(debug) << "Stopping Reporting ETL"; log_.debug() << "Stopping Reporting ETL";
stopping_ = true; stopping_ = true;
if (worker_.joinable()) if (worker_.joinable())
@@ -346,7 +367,7 @@ public:
if (cacheDownloader_.joinable()) if (cacheDownloader_.joinable())
cacheDownloader_.join(); cacheDownloader_.join();
BOOST_LOG_TRIVIAL(debug) << "Joined ReportingETL worker thread"; log_.debug() << "Joined ReportingETL worker thread";
} }
boost::json::object boost::json::object
@@ -392,5 +413,3 @@ public:
return now - (rippleEpochStart + closeTime); return now - (rippleEpochStart + closeTime);
} }
}; };
#endif

209
src/log/Logger.cpp Normal file
View File

@@ -0,0 +1,209 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <config/Config.h>
#include <log/Logger.h>
#include <algorithm>
#include <array>
#include <filesystem>
namespace clio {
Logger LogService::general_log_ = Logger{"General"};
Logger LogService::alert_log_ = Logger{"Alert"};
std::ostream&
operator<<(std::ostream& stream, Severity sev)
{
static constexpr std::array<const char*, 6> labels = {
"TRC",
"DBG",
"NFO",
"WRN",
"ERR",
"FTL",
};
return stream << labels.at(static_cast<int>(sev));
}
Severity
tag_invoke(boost::json::value_to_tag<Severity>, boost::json::value const& value)
{
if (not value.is_string())
throw std::runtime_error("`log_level` must be a string");
auto const& logLevel = value.as_string();
if (boost::iequals(logLevel, "trace"))
return Severity::TRC;
else if (boost::iequals(logLevel, "debug"))
return Severity::DBG;
else if (boost::iequals(logLevel, "info"))
return Severity::NFO;
else if (
boost::iequals(logLevel, "warning") || boost::iequals(logLevel, "warn"))
return Severity::WRN;
else if (boost::iequals(logLevel, "error"))
return Severity::ERR;
else if (boost::iequals(logLevel, "fatal"))
return Severity::FTL;
else
throw std::runtime_error(
"Could not parse `log_level`: expected `trace`, `debug`, `info`, "
"`warning`, `error` or `fatal`");
}
void
LogService::init(Config const& config)
{
namespace src = boost::log::sources;
namespace keywords = boost::log::keywords;
namespace sinks = boost::log::sinks;
boost::log::add_common_attributes();
boost::log::register_simple_formatter_factory<Severity, char>("Severity");
auto const defaultFormat =
"%TimeStamp% (%SourceLocation%) [%ThreadID%] %Channel%:%Severity% "
"%Message%";
std::string format =
config.valueOr<std::string>("log_format", defaultFormat);
if (config.valueOr("log_to_console", false))
{
boost::log::add_console_log(std::cout, keywords::format = format);
}
auto logDir = config.maybeValue<std::string>("log_directory");
if (logDir)
{
boost::filesystem::path dirPath{logDir.value()};
if (!boost::filesystem::exists(dirPath))
boost::filesystem::create_directories(dirPath);
auto const rotationSize =
config.valueOr<uint64_t>("log_rotation_size", 2048u) * 1024u *
1024u;
auto const rotationPeriod =
config.valueOr<uint32_t>("log_rotation_hour_interval", 12u);
auto const dirSize =
config.valueOr<uint64_t>("log_directory_max_size", 50u * 1024u) *
1024u * 1024u;
auto fileSink = boost::log::add_file_log(
keywords::file_name = dirPath / "clio.log",
keywords::target_file_name = dirPath / "clio_%Y-%m-%d_%H-%M-%S.log",
keywords::auto_flush = true,
keywords::format = format,
keywords::open_mode = std::ios_base::app,
keywords::rotation_size = rotationSize,
keywords::time_based_rotation =
sinks::file::rotation_at_time_interval(
boost::posix_time::hours(rotationPeriod)));
fileSink->locked_backend()->set_file_collector(
sinks::file::make_collector(
keywords::target = dirPath, keywords::max_size = dirSize));
fileSink->locked_backend()->scan_for_files();
}
// get default severity, can be overridden per channel using
// the `log_channels` array
auto defaultSeverity = config.valueOr<Severity>("log_level", Severity::NFO);
static constexpr std::array<const char*, 7> channels = {
"General",
"WebServer",
"Backend",
"RPC",
"ETL",
"Subscriptions",
"Performance",
};
auto core = boost::log::core::get();
auto min_severity = boost::log::expressions::channel_severity_filter(
log_channel, log_severity);
for (auto const& channel : channels)
min_severity[channel] = defaultSeverity;
min_severity["Alert"] =
Severity::WRN; // Channel for alerts, always warning severity
for (auto const overrides = config.arrayOr("log_channels", {});
auto const& cfg : overrides)
{
auto name = cfg.valueOrThrow<std::string>(
"channel", "Channel name is required");
if (not std::count(std::begin(channels), std::end(channels), name))
throw std::runtime_error(
"Can't override settings for log channel " + name +
": invalid channel");
min_severity[name] =
cfg.valueOr<Severity>("log_level", defaultSeverity);
}
core->set_filter(min_severity);
LogService::info() << "Default log level = " << defaultSeverity;
}
Logger::Pump
Logger::trace(source_location_t const& loc) const
{
return {logger_, Severity::TRC, loc};
};
Logger::Pump
Logger::debug(source_location_t const& loc) const
{
return {logger_, Severity::DBG, loc};
};
Logger::Pump
Logger::info(source_location_t const& loc) const
{
return {logger_, Severity::NFO, loc};
};
Logger::Pump
Logger::warn(source_location_t const& loc) const
{
return {logger_, Severity::WRN, loc};
};
Logger::Pump
Logger::error(source_location_t const& loc) const
{
return {logger_, Severity::ERR, loc};
};
Logger::Pump
Logger::fatal(source_location_t const& loc) const
{
return {logger_, Severity::FTL, loc};
};
std::string
Logger::Pump::pretty_path(source_location_t const& loc, size_t max_depth) const
{
auto const file_path = std::string{loc.file_name()};
auto idx = file_path.size();
while (max_depth-- > 0)
{
idx = file_path.rfind('/', idx - 1);
if (idx == std::string::npos || idx == 0)
break;
}
return file_path.substr(idx == std::string::npos ? 0 : idx + 1) + ':' +
std::to_string(loc.line());
}
} // namespace clio

314
src/log/Logger.h Normal file
View File

@@ -0,0 +1,314 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/json.hpp>
#include <boost/log/core/core.hpp>
#include <boost/log/expressions/predicates/channel_severity_filter.hpp>
#include <boost/log/sinks/unlocked_frontend.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/sources/severity_channel_logger.hpp>
#include <boost/log/sources/severity_feature.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/utility/manipulators/add_value.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/formatter_parser.hpp>
#if defined(HAS_SOURCE_LOCATION) && __has_builtin(__builtin_source_location)
// this is used by fully compatible compilers like gcc
#include <source_location>
#elif defined(HAS_EXPERIMENTAL_SOURCE_LOCATION)
// this is used by clang on linux where source_location is still not out of
// experimental headers
#include <experimental/source_location>
#endif
#include <optional>
#include <string>
namespace clio {
class Config;
#if defined(HAS_SOURCE_LOCATION) && __has_builtin(__builtin_source_location)
using source_location_t = std::source_location;
#define CURRENT_SRC_LOCATION source_location_t::current()
#elif defined(HAS_EXPERIMENTAL_SOURCE_LOCATION)
using source_location_t = std::experimental::source_location;
#define CURRENT_SRC_LOCATION source_location_t::current()
#else
// A workaround for AppleClang that is lacking source_location atm.
// TODO: remove this workaround when all compilers catch up to c++20
class SourceLocation
{
std::string_view file_;
std::size_t line_;
public:
SourceLocation(std::string_view file, std::size_t line)
: file_{file}, line_{line}
{
}
std::string_view
file_name() const
{
return file_;
}
std::size_t
line() const
{
return line_;
}
};
using source_location_t = SourceLocation;
#define CURRENT_SRC_LOCATION \
source_location_t(__builtin_FILE(), __builtin_LINE())
#endif
/**
* @brief Custom severity levels for @ref Logger.
*/
enum class Severity {
TRC,
DBG,
NFO,
WRN,
ERR,
FTL,
};
BOOST_LOG_ATTRIBUTE_KEYWORD(log_severity, "Severity", Severity);
BOOST_LOG_ATTRIBUTE_KEYWORD(log_channel, "Channel", std::string);
/**
* @brief Custom labels for @ref Severity in log output.
*
* @param stream std::ostream The output stream
* @param sev Severity The severity to output to the ostream
* @return std::ostream& The same ostream we were given
*/
std::ostream&
operator<<(std::ostream& stream, Severity sev);
/**
* @brief Custom JSON parser for @ref Severity.
*
* @param value The JSON string to parse
* @return Severity The parsed severity
* @throws std::runtime_error Thrown if severity is not in the right format
*/
Severity
tag_invoke(
boost::json::value_to_tag<Severity>,
boost::json::value const& value);
/**
* @brief A simple thread-safe logger for the channel specified
* in the constructor.
*
* This is cheap to copy and move. Designed to be used as a member variable or
* otherwise. See @ref LogService::init() for setup of the logging core and
* severity levels for each channel.
*/
class Logger final
{
using logger_t =
boost::log::sources::severity_channel_logger_mt<Severity, std::string>;
mutable logger_t logger_;
friend class LogService; // to expose the Pump interface
/**
* @brief Helper that pumps data into a log record via `operator<<`.
*/
class Pump final
{
using pump_opt_t =
std::optional<boost::log::aux::record_pump<logger_t>>;
boost::log::record rec_;
pump_opt_t pump_ = std::nullopt;
public:
~Pump() = default;
Pump(logger_t& logger, Severity sev, source_location_t const& loc)
: rec_{logger.open_record(boost::log::keywords::severity = sev)}
{
if (rec_)
{
pump_.emplace(boost::log::aux::make_record_pump(logger, rec_));
pump_->stream() << boost::log::add_value(
"SourceLocation", pretty_path(loc));
}
}
Pump(Pump&&) = delete;
Pump(Pump const&) = delete;
Pump&
operator=(Pump const&) = delete;
Pump&
operator=(Pump&&) = delete;
/**
* @brief Perfectly forwards any incoming data into the underlying
* boost::log pump if the pump is available. nop otherwise.
*
* @tparam T Type of data to pump
* @param data The data to pump
* @return Pump& Reference to itself for chaining
*/
template <typename T>
[[maybe_unused]] Pump&
operator<<(T&& data)
{
if (pump_)
pump_->stream() << std::forward<T>(data);
return *this;
}
private:
[[nodiscard]] std::string
pretty_path(source_location_t const& loc, size_t max_depth = 3) const;
};
public:
~Logger() = default;
/**
* @brief Construct a new Logger object that produces loglines for the
* specified channel.
*
* See @ref LogService::init() for general setup and configuration of
* severity levels per channel.
*
* @param channel The channel this logger will report into.
*/
Logger(std::string channel)
: logger_{boost::log::keywords::channel = channel}
{
}
Logger(Logger const&) = default;
Logger(Logger&&) = default;
Logger&
operator=(Logger const&) = default;
Logger&
operator=(Logger&&) = default;
/*! Interface for logging at @ref Severity::TRC severity */
[[nodiscard]] Pump
trace(source_location_t const& loc = CURRENT_SRC_LOCATION) const;
/*! Interface for logging at @ref Severity::DBG severity */
[[nodiscard]] Pump
debug(source_location_t const& loc = CURRENT_SRC_LOCATION) const;
/*! Interface for logging at @ref Severity::INFO severity */
[[nodiscard]] Pump
info(source_location_t const& loc = CURRENT_SRC_LOCATION) const;
/*! Interface for logging at @ref Severity::WRN severity */
[[nodiscard]] Pump
warn(source_location_t const& loc = CURRENT_SRC_LOCATION) const;
/*! Interface for logging at @ref Severity::ERR severity */
[[nodiscard]] Pump
error(source_location_t const& loc = CURRENT_SRC_LOCATION) const;
/*! Interface for logging at @ref Severity::FTL severity */
[[nodiscard]] Pump
fatal(source_location_t const& loc = CURRENT_SRC_LOCATION) const;
};
/**
* @brief A global logging service.
*
* Used to initialize and setup the logging core as well as a globally available
* entrypoint for logging into the `General` channel as well as raising alerts.
*/
class LogService
{
static Logger general_log_; /*! Global logger for General channel */
static Logger alert_log_; /*! Global logger for Alerts channel */
public:
LogService() = delete;
/**
* @brief Global log core initialization from a @ref Config
*/
static void
init(Config const& config);
/*! Globally accesible General logger at @ref Severity::TRC severity */
[[nodiscard]] static Logger::Pump
trace(source_location_t const& loc = CURRENT_SRC_LOCATION)
{
return general_log_.trace(loc);
}
/*! Globally accesible General logger at @ref Severity::DBG severity */
[[nodiscard]] static Logger::Pump
debug(source_location_t const& loc = CURRENT_SRC_LOCATION)
{
return general_log_.debug(loc);
}
/*! Globally accesible General logger at @ref Severity::NFO severity */
[[nodiscard]] static Logger::Pump
info(source_location_t const& loc = CURRENT_SRC_LOCATION)
{
return general_log_.info(loc);
}
/*! Globally accesible General logger at @ref Severity::WRN severity */
[[nodiscard]] static Logger::Pump
warn(source_location_t const& loc = CURRENT_SRC_LOCATION)
{
return general_log_.warn(loc);
}
/*! Globally accesible General logger at @ref Severity::ERR severity */
[[nodiscard]] static Logger::Pump
error(source_location_t const& loc = CURRENT_SRC_LOCATION)
{
return general_log_.error(loc);
}
/*! Globally accesible General logger at @ref Severity::FTL severity */
[[nodiscard]] static Logger::Pump
fatal(source_location_t const& loc = CURRENT_SRC_LOCATION)
{
return general_log_.fatal(loc);
}
/*! Globally accesible Alert logger */
[[nodiscard]] static Logger::Pump
alert(source_location_t const& loc = CURRENT_SRC_LOCATION)
{
return alert_log_.warn(loc);
}
};
}; // namespace clio

View File

@@ -1,5 +1,23 @@
#ifndef CLIO_BUILD_INFO_H //------------------------------------------------------------------------------
#define CLIO_BUILD_INFO_H /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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> #include <string>
@@ -12,5 +30,3 @@ std::string const&
getClioFullVersionString(); getClioFullVersionString();
} // namespace Build } // namespace Build
#endif // CLIO_BUILD_INFO_H

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/beast/core/SemanticVersion.h> #include <ripple/beast/core/SemanticVersion.h>
#include <boost/preprocessor/stringize.hpp> #include <boost/preprocessor/stringize.hpp>
#include <algorithm> #include <algorithm>
@@ -12,17 +31,15 @@ namespace Build {
// and follow the format described at http://semver.org/ // and follow the format described at http://semver.org/
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// clang-format off // clang-format off
char const* const versionString = "1.0.2" char const* const versionString = "1.0.4"
// clang-format on // clang-format on
#if defined(DEBUG) || defined(SANITIZER)
"+" "+"
#ifdef CLIO_GIT_COMMIT_HASH #ifdef CLIO_BUILD
CLIO_GIT_COMMIT_HASH CLIO_BUILD
"."
#endif #endif
#ifdef DEBUG #ifdef DEBUG
"DEBUG" ".DEBUG"
#ifdef SANITIZER #ifdef SANITIZER
"." "."
#endif #endif
@@ -31,12 +48,10 @@ char const* const versionString = "1.0.2"
#ifdef SANITIZER #ifdef SANITIZER
BOOST_PP_STRINGIZE(SANITIZER) BOOST_PP_STRINGIZE(SANITIZER)
#endif #endif
#endif
#ifdef PKG #ifdef PKG
"-release" "-release"
#endif #endif
//--------------------------------------------------------------------------
; ;
std::string const& std::string const&

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <grpc/impl/codegen/port_platform.h> #include <grpc/impl/codegen/port_platform.h>
#ifdef GRPC_TSAN_ENABLED #ifdef GRPC_TSAN_ENABLED
#undef GRPC_TSAN_ENABLED #undef GRPC_TSAN_ENABLED
@@ -6,26 +25,22 @@
#undef GRPC_ASAN_ENABLED #undef GRPC_ASAN_ENABLED
#endif #endif
#include <backend/BackendFactory.h>
#include <config/Config.h>
#include <etl/ReportingETL.h>
#include <log/Logger.h>
#include <webserver/Listener.h>
#include <boost/asio/dispatch.hpp> #include <boost/asio/dispatch.hpp>
#include <boost/asio/strand.hpp> #include <boost/asio/strand.hpp>
#include <boost/beast/websocket.hpp> #include <boost/beast/websocket.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp> #include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/json.hpp> #include <boost/json.hpp>
#include <boost/log/core.hpp> #include <boost/program_options.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks/text_file_backend.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <algorithm> #include <algorithm>
#include <backend/BackendFactory.h>
#include <config/Config.h>
#include <cstdlib> #include <cstdlib>
#include <etl/ReportingETL.h>
#include <fstream> #include <fstream>
#include <functional> #include <functional>
#include <iostream> #include <iostream>
@@ -35,12 +50,67 @@
#include <string> #include <string>
#include <thread> #include <thread>
#include <vector> #include <vector>
#include <webserver/Listener.h>
using namespace clio; using namespace clio;
namespace po = boost::program_options;
/**
* @brief Parse command line and return path to configuration file
*
* @param argc
* @param argv
* @return std::string Path to configuration file
*/
std::string
parseCli(int argc, char* argv[])
{
static constexpr char defaultConfigPath[] = "/etc/opt/clio/config.json";
// clang-format off
po::options_description description("Options");
description.add_options()
("help,h", "print help message and exit")
("version,v", "print version and exit")
("conf,c", po::value<std::string>()->default_value(defaultConfigPath), "configuration file")
;
// clang-format on
po::positional_options_description positional;
positional.add("conf", 1);
po::variables_map parsed;
po::store(
po::command_line_parser(argc, argv)
.options(description)
.positional(positional)
.run(),
parsed);
po::notify(parsed);
if (parsed.count("version"))
{
std::cout << Build::getClioFullVersionString() << '\n';
std::exit(EXIT_SUCCESS);
}
if (parsed.count("help"))
{
std::cout << "Clio server " << Build::getClioFullVersionString()
<< "\n\n"
<< description;
std::exit(EXIT_SUCCESS);
}
return parsed["conf"].as<std::string>();
}
/**
* @brief Parse certificates from configuration file
*
* @param config The configuration
* @return std::optional<ssl::context> SSL context if certificates were parsed
*/
std::optional<ssl::context> std::optional<ssl::context>
parse_certs(Config const& config) parseCerts(Config const& config)
{ {
if (!config.contains("ssl_cert_file") || !config.contains("ssl_key_file")) if (!config.contains("ssl_cert_file") || !config.contains("ssl_key_file"))
return {}; return {};
@@ -80,85 +150,12 @@ parse_certs(Config const& config)
return ctx; return ctx;
} }
void /**
initLogging(Config const& config) * @brief Start context threads
{ *
namespace src = boost::log::sources; * @param ioc Context
namespace keywords = boost::log::keywords; * @param numThreads Number of worker threads to start
namespace sinks = boost::log::sinks; */
namespace trivial = boost::log::trivial;
boost::log::add_common_attributes();
std::string format = "[%TimeStamp%] [%ThreadID%] [%Severity%] %Message%";
if (config.valueOr<bool>("log_to_console", true))
{
boost::log::add_console_log(std::cout, keywords::format = format);
}
if (auto logDir = config.maybeValue<std::string>("log_directory"); logDir)
{
boost::filesystem::path dirPath{*logDir};
if (!boost::filesystem::exists(dirPath))
boost::filesystem::create_directories(dirPath);
auto const rotationSize =
config.valueOr<int64_t>("log_rotation_size", 2 * 1024) * 1024 *
1024u;
if (rotationSize <= 0)
throw std::runtime_error(
"log rotation size must be greater than 0");
auto const rotationPeriod =
config.valueOr<int64_t>("log_rotation_hour_interval", 12u);
if (rotationPeriod <= 0)
throw std::runtime_error(
"log rotation time interval must be greater than 0");
auto const dirSize =
config.valueOr<int64_t>("log_directory_max_size", 50 * 1024) *
1024 * 1024u;
if (dirSize <= 0)
throw std::runtime_error(
"log rotation directory max size must be greater than 0");
auto fileSink = boost::log::add_file_log(
keywords::file_name = dirPath / "clio.log",
keywords::target_file_name = dirPath / "clio_%Y-%m-%d_%H-%M-%S.log",
keywords::auto_flush = true,
keywords::format = format,
keywords::open_mode = std::ios_base::app,
keywords::rotation_size = rotationSize,
keywords::time_based_rotation =
sinks::file::rotation_at_time_interval(
boost::posix_time::hours(rotationPeriod)));
fileSink->locked_backend()->set_file_collector(
sinks::file::make_collector(
keywords::target = dirPath, keywords::max_size = dirSize));
fileSink->locked_backend()->scan_for_files();
}
auto const logLevel = config.valueOr<std::string>("log_level", "info");
if (boost::iequals(logLevel, "trace"))
boost::log::core::get()->set_filter(
trivial::severity >= trivial::trace);
else if (boost::iequals(logLevel, "debug"))
boost::log::core::get()->set_filter(
trivial::severity >= trivial::debug);
else if (boost::iequals(logLevel, "info"))
boost::log::core::get()->set_filter(trivial::severity >= trivial::info);
else if (
boost::iequals(logLevel, "warning") || boost::iequals(logLevel, "warn"))
boost::log::core::get()->set_filter(
trivial::severity >= trivial::warning);
else if (boost::iequals(logLevel, "error"))
boost::log::core::get()->set_filter(
trivial::severity >= trivial::error);
else if (boost::iequals(logLevel, "fatal"))
boost::log::core::get()->set_filter(
trivial::severity >= trivial::fatal);
else
{
BOOST_LOG_TRIVIAL(warning) << "Unrecognized log level: " << logLevel
<< ". Setting log level to info";
boost::log::core::get()->set_filter(trivial::severity >= trivial::info);
}
BOOST_LOG_TRIVIAL(info) << "Log level = " << logLevel;
}
void void
start(boost::asio::io_context& ioc, std::uint32_t numThreads) start(boost::asio::io_context& ioc, std::uint32_t numThreads)
{ {
@@ -172,36 +169,21 @@ start(boost::asio::io_context& ioc, std::uint32_t numThreads)
int int
main(int argc, char* argv[]) main(int argc, char* argv[])
try
{ {
// Check command line arguments. auto const configPath = parseCli(argc, argv);
if (argc != 2) auto const config = ConfigReader::open(configPath);
{
std::cerr << "Usage: clio_server "
"<config_file> \n"
<< "Example:\n"
<< " clio_server config.json \n";
return EXIT_FAILURE;
}
if (std::string{argv[1]} == "-v" || std::string{argv[1]} == "--version")
{
std::cout << Build::getClioFullVersionString() << std::endl;
return EXIT_SUCCESS;
}
auto const config = ConfigReader::open(argv[1]);
if (!config) if (!config)
{ {
std::cerr << "Couldnt parse config. Exiting..." << std::endl; std::cerr << "Couldnt parse config '" << configPath << "'."
<< std::endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
initLogging(config); LogService::init(config);
LogService::info() << "Clio version: " << Build::getClioFullVersionString();
BOOST_LOG_TRIVIAL(info) auto ctx = parseCerts(config);
<< "Clio version: " << Build::getClioFullVersionString();
auto ctx = parse_certs(config);
auto ctxRef = ctx auto ctxRef = ctx
? std::optional<std::reference_wrapper<ssl::context>>{ctx.value()} ? std::optional<std::reference_wrapper<ssl::context>>{ctx.value()}
: std::nullopt; : std::nullopt;
@@ -209,30 +191,29 @@ main(int argc, char* argv[])
auto const threads = config.valueOr("io_threads", 2); auto const threads = config.valueOr("io_threads", 2);
if (threads <= 0) if (threads <= 0)
{ {
BOOST_LOG_TRIVIAL(fatal) << "io_threads is less than 0"; LogService::fatal() << "io_threads is less than 0";
return EXIT_FAILURE; return EXIT_FAILURE;
} }
BOOST_LOG_TRIVIAL(info) << "Number of io threads = " << threads; LogService::info() << "Number of io threads = " << threads;
// io context to handle all incoming requests, as well as other things // IO context to handle all incoming requests, as well as other things
// This is not the only io context in the application // This is not the only io context in the application
boost::asio::io_context ioc{threads}; boost::asio::io_context ioc{threads};
// Rate limiter, to prevent abuse // Rate limiter, to prevent abuse
DOSGuard dosGuard{config, ioc}; auto sweepHandler = IntervalSweepHandler{config, ioc};
auto dosGuard = DOSGuard{config, sweepHandler};
// Interface to the database // Interface to the database
std::shared_ptr<BackendInterface> backend{ auto backend = Backend::make_Backend(ioc, config);
Backend::make_Backend(ioc, config)};
// Manages clients subscribed to streams // Manages clients subscribed to streams
std::shared_ptr<SubscriptionManager> subscriptions{ auto subscriptions =
SubscriptionManager::make_SubscriptionManager(config, backend)}; SubscriptionManager::make_SubscriptionManager(config, backend);
// Tracks which ledgers have been validated by the // Tracks which ledgers have been validated by the
// network // network
std::shared_ptr<NetworkValidatedLedgers> ledgers{ auto ledgers = NetworkValidatedLedgers::make_ValidatedLedgers();
NetworkValidatedLedgers::make_ValidatedLedgers()};
// Handles the connection to one or more rippled nodes. // Handles the connection to one or more rippled nodes.
// ETL uses the balancer to extract data. // ETL uses the balancer to extract data.
@@ -258,3 +239,7 @@ main(int argc, char* argv[])
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
catch (std::exception const& e)
{
LogService::fatal() << "Exit on exception: " << e.what();
}

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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/Counters.h> #include <rpc/Counters.h>
#include <rpc/RPC.h> #include <rpc/RPC.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>

View File

@@ -1,5 +1,23 @@
#ifndef RPC_COUNTERS_H //------------------------------------------------------------------------------
#define RPC_COUNTERS_H /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <boost/json.hpp> #include <boost/json.hpp>
#include <chrono> #include <chrono>
@@ -53,5 +71,3 @@ public:
}; };
} // namespace RPC } // namespace RPC
#endif // RPC_COUNTERS_H

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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/Errors.h> #include <rpc/Errors.h>
#include <algorithm> #include <algorithm>
@@ -55,6 +74,13 @@ getErrorInfo(ClioError code)
{ClioError::rpcMALFORMED_CURRENCY, {ClioError::rpcMALFORMED_CURRENCY,
"malformedCurrency", "malformedCurrency",
"Malformed currency."}, "Malformed currency."},
{ClioError::rpcMALFORMED_REQUEST,
"malformedRequest",
"Malformed request."},
{ClioError::rpcMALFORMED_OWNER, "malformedOwner", "Malformed owner."},
{ClioError::rpcMALFORMED_ADDRESS,
"malformedAddress",
"Malformed address."},
}; };
auto matchByCode = [code](auto const& info) { return info.code == code; }; auto matchByCode = [code](auto const& info) { return info.code == code; };
@@ -106,7 +132,7 @@ makeError(Status const& status)
return str.empty() ? nullopt : make_optional(str); return str.empty() ? nullopt : make_optional(str);
}; };
return visit( auto res = visit(
overloadSet{ overloadSet{
[&status, &wrapOptional](RippledError err) { [&status, &wrapOptional](RippledError err) {
if (err == ripple::rpcUNKNOWN) if (err == ripple::rpcUNKNOWN)
@@ -130,6 +156,14 @@ makeError(Status const& status)
}, },
}, },
status.code); status.code);
if (status.extraInfo)
{
for (auto& [key, value] : status.extraInfo.value())
{
res[key] = value;
}
}
return res;
} }
} // namespace RPC } // namespace RPC

View File

@@ -1,5 +1,23 @@
#ifndef REPORTING_RPC_ERRORS_H_INCLUDED //------------------------------------------------------------------------------
#define REPORTING_RPC_ERRORS_H_INCLUDED /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>
@@ -17,6 +35,9 @@ namespace RPC {
*/ */
enum class ClioError { enum class ClioError {
rpcMALFORMED_CURRENCY = 5000, rpcMALFORMED_CURRENCY = 5000,
rpcMALFORMED_REQUEST = 5001,
rpcMALFORMED_OWNER = 5002,
rpcMALFORMED_ADDRESS = 5003,
}; };
/** /**
@@ -50,9 +71,12 @@ struct Status
CombinedError code = RippledError::rpcSUCCESS; CombinedError code = RippledError::rpcSUCCESS;
std::string error = ""; std::string error = "";
std::string message = ""; std::string message = "";
std::optional<boost::json::object> extraInfo;
Status() = default; Status() = default;
/* implicit */ Status(CombinedError code) : code(code){}; /* implicit */ Status(CombinedError code) : code(code){};
Status(CombinedError code, boost::json::object&& extraInfo)
: code(code), extraInfo(std::move(extraInfo)){};
// HACK. Some rippled handlers explicitly specify errors. // HACK. Some rippled handlers explicitly specify errors.
// This means that we have to be able to duplicate this // This means that we have to be able to duplicate this
@@ -81,6 +105,34 @@ struct Status
return *err != RippledError::rpcSUCCESS; return *err != RippledError::rpcSUCCESS;
return true; return true;
} }
/**
* @brief Returns true if the Status contains the desired @ref RippledError
*
* @param other The RippledError to match
* @return bool true if status matches given error; false otherwise
*/
bool
operator==(RippledError other) const
{
if (auto err = std::get_if<RippledError>(&code))
return *err == other;
return false;
}
/**
* @brief Returns true if the Status contains the desired @ref ClioError
*
* @param other The RippledError to match
* @return bool true if status matches given error; false otherwise
*/
bool
operator==(ClioError other) const
{
if (auto err = std::get_if<ClioError>(&code))
return *err == other;
return false;
}
}; };
/** /**
@@ -202,5 +254,3 @@ makeError(
std::optional<std::string_view> customMessage = std::nullopt); std::optional<std::string_view> customMessage = std::nullopt);
} // namespace RPC } // namespace RPC
#endif // REPORTING_RPC_ERRORS_H_INCLUDED

View File

@@ -1,5 +1,23 @@
#ifndef REPORTING_HANDLERS_H_INCLUDED //------------------------------------------------------------------------------
#define REPORTING_HANDLERS_H_INCLUDED /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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/RPC.h> #include <rpc/RPC.h>
@@ -102,4 +120,3 @@ doServerInfo(Context const& context);
Result Result
doRandom(Context const& context); doRandom(Context const& context);
} // namespace RPC } // namespace RPC
#endif

View File

@@ -1,4 +1,24 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <etl/ETLSource.h> #include <etl/ETLSource.h>
#include <log/Logger.h>
#include <rpc/Handlers.h> #include <rpc/Handlers.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <webserver/HttpBase.h> #include <webserver/HttpBase.h>
@@ -9,9 +29,15 @@
#include <unordered_map> #include <unordered_map>
using namespace std; using namespace std;
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gPerfLog{"Performance"};
clio::Logger gLog{"RPC"};
} // namespace
namespace RPC { namespace RPC {
Context::Context( Context::Context(
boost::asio::yield_context& yield_, boost::asio::yield_context& yield_,
string const& command_, string const& command_,
@@ -40,7 +66,7 @@ Context::Context(
, counters(counters_) , counters(counters_)
, clientIp(clientIp_) , clientIp(clientIp_)
{ {
BOOST_LOG_TRIVIAL(debug) << tag() << "new Context created"; gPerfLog.debug() << tag() << "new Context created";
} }
optional<Context> optional<Context>
@@ -314,9 +340,6 @@ buildResponse(Context const& ctx)
if (!res) if (!res)
return Status{RippledError::rpcFAILED_TO_FORWARD}; return Status{RippledError::rpcFAILED_TO_FORWARD};
if (res->contains("result") && res->at("result").is_object())
return res->at("result").as_object();
return *res; return *res;
} }
@@ -325,8 +348,7 @@ buildResponse(Context const& ctx)
if (ctx.backend->isTooBusy()) if (ctx.backend->isTooBusy())
{ {
BOOST_LOG_TRIVIAL(error) gLog.error() << "Database is too busy. Rejecting request";
<< __func__ << " Database is too busy. Rejecting request";
return Status{RippledError::rpcTOO_BUSY}; return Status{RippledError::rpcTOO_BUSY};
} }
@@ -337,18 +359,16 @@ buildResponse(Context const& ctx)
try try
{ {
BOOST_LOG_TRIVIAL(debug) gPerfLog.debug() << ctx.tag() << " start executing rpc `" << ctx.method
<< ctx.tag() << __func__ << " start executing rpc `" << ctx.method << '`';
<< '`';
auto v = (*method)(ctx); auto v = (*method)(ctx);
BOOST_LOG_TRIVIAL(debug) gPerfLog.debug() << ctx.tag() << " finish executing rpc `" << ctx.method
<< ctx.tag() << __func__ << " finish executing rpc `" << ctx.method << '`';
<< '`';
if (auto object = get_if<boost::json::object>(&v); if (auto object = get_if<boost::json::object>(&v);
object && not shouldSuppressValidatedFlag(ctx)) object && not shouldSuppressValidatedFlag(ctx))
{ {
(*object)["validated"] = true; (*object)[JS(validated)] = true;
} }
return v; return v;
@@ -363,13 +383,12 @@ buildResponse(Context const& ctx)
} }
catch (Backend::DatabaseTimeout const& t) catch (Backend::DatabaseTimeout const& t)
{ {
BOOST_LOG_TRIVIAL(error) << __func__ << " Database timeout"; gLog.error() << "Database timeout";
return Status{RippledError::rpcTOO_BUSY}; return Status{RippledError::rpcTOO_BUSY};
} }
catch (exception const& err) catch (exception const& err)
{ {
BOOST_LOG_TRIVIAL(error) gLog.error() << ctx.tag() << " caught exception: " << err.what();
<< ctx.tag() << __func__ << " caught exception : " << err.what();
return Status{RippledError::rpcINTERNAL}; return Status{RippledError::rpcINTERNAL};
} }
} }

View File

@@ -1,15 +1,33 @@
#ifndef REPORTING_RPC_H_INCLUDED //------------------------------------------------------------------------------
#define REPORTING_RPC_H_INCLUDED /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include <backend/BackendInterface.h>
#include <log/Logger.h>
#include <rpc/Counters.h>
#include <rpc/Errors.h> #include <rpc/Errors.h>
#include <util/Taggable.h>
#include <boost/asio/spawn.hpp> #include <boost/asio/spawn.hpp>
#include <boost/json.hpp> #include <boost/json.hpp>
#include <backend/BackendInterface.h>
#include <rpc/Counters.h>
#include <util/Taggable.h>
#include <optional> #include <optional>
#include <string> #include <string>
#include <variant> #include <variant>
@@ -34,6 +52,7 @@ namespace RPC {
struct Context : public util::Taggable struct Context : public util::Taggable
{ {
clio::Logger perfLog_{"Performance"};
boost::asio::yield_context& yield; boost::asio::yield_context& yield;
std::string method; std::string method;
std::uint32_t version; std::uint32_t version;
@@ -72,13 +91,13 @@ struct AccountCursor
std::uint32_t hint; std::uint32_t hint;
std::string std::string
toString() toString() const
{ {
return ripple::strHex(index) + "," + std::to_string(hint); return ripple::strHex(index) + "," + std::to_string(hint);
} }
bool bool
isNonZero() isNonZero() const
{ {
return index.isNonZero() || hint != 0; return index.isNonZero() || hint != 0;
} }
@@ -129,6 +148,7 @@ template <class T>
void void
logDuration(Context const& ctx, T const& dur) logDuration(Context const& ctx, T const& dur)
{ {
static clio::Logger log{"RPC"};
std::stringstream ss; std::stringstream ss;
ss << ctx.tag() << "Request processing duration = " ss << ctx.tag() << "Request processing duration = "
<< std::chrono::duration_cast<std::chrono::milliseconds>(dur).count() << std::chrono::duration_cast<std::chrono::milliseconds>(dur).count()
@@ -136,13 +156,11 @@ logDuration(Context const& ctx, T const& dur)
auto seconds = auto seconds =
std::chrono::duration_cast<std::chrono::seconds>(dur).count(); std::chrono::duration_cast<std::chrono::seconds>(dur).count();
if (seconds > 10) if (seconds > 10)
BOOST_LOG_TRIVIAL(error) << ss.str(); log.error() << ss.str();
else if (seconds > 1) else if (seconds > 1)
BOOST_LOG_TRIVIAL(warning) << ss.str(); log.warn() << ss.str();
else else
BOOST_LOG_TRIVIAL(info) << ss.str(); log.info() << ss.str();
} }
} // namespace RPC } // namespace RPC
#endif // REPORTING_RPC_H_INCLUDED

View File

@@ -1,10 +1,37 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/basics/StringUtilities.h>
#include <backend/BackendInterface.h>
#include <log/Logger.h>
#include <rpc/RPCHelpers.h>
#include <util/Profiler.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/format.hpp> #include <boost/format.hpp>
#include <ripple/basics/StringUtilities.h> using namespace clio;
#include <backend/BackendInterface.h> // local to compilation unit loggers
#include <rpc/RPCHelpers.h> namespace {
clio::Logger gLog{"RPC"};
} // namespace
namespace RPC { namespace RPC {
@@ -74,35 +101,11 @@ getRequiredUInt(boost::json::object const& request, std::string const& field)
throw InvalidParamsError("Missing field " + field); throw InvalidParamsError("Missing field " + field);
} }
bool
isOwnedByAccount(ripple::SLE const& sle, ripple::AccountID const& accountID)
{
if (sle.getType() == ripple::ltRIPPLE_STATE)
{
return (sle.getFieldAmount(ripple::sfLowLimit).getIssuer() ==
accountID) ||
(sle.getFieldAmount(ripple::sfHighLimit).getIssuer() == accountID);
}
else if (sle.isFieldPresent(ripple::sfAccount))
{
return sle.getAccountID(ripple::sfAccount) == accountID;
}
else if (sle.getType() == ripple::ltSIGNER_LIST)
{
ripple::Keylet const accountSignerList =
ripple::keylet::signers(accountID);
return sle.key() == accountSignerList.key;
}
return false;
}
std::optional<AccountCursor> std::optional<AccountCursor>
parseAccountCursor( parseAccountCursor(
BackendInterface const& backend, BackendInterface const& backend,
std::uint32_t seq, std::uint32_t seq,
std::optional<std::string> jsonCursor, std::optional<std::string> jsonCursor,
ripple::AccountID const& accountID,
boost::asio::yield_context& yield) boost::asio::yield_context& yield)
{ {
ripple::uint256 cursorIndex = beast::zero; ripple::uint256 cursorIndex = beast::zero;
@@ -133,19 +136,6 @@ parseAccountCursor(
return {}; return {};
} }
// We then must check if the object pointed to by the marker is actually
// owned by the account in the request.
auto const ownedNode = backend.fetchLedgerObject(cursorIndex, seq, yield);
if (!ownedNode)
return {};
ripple::SerialIter it{ownedNode->data(), ownedNode->size()};
ripple::SLE sle{it, cursorIndex};
if (!isOwnedByAccount(sle, accountID))
return {};
return AccountCursor({cursorIndex, startHint}); return AccountCursor({cursorIndex, startHint});
} }
@@ -412,12 +402,11 @@ deserializeTxPlusMeta(Backend::TransactionAndMetadata const& blobs)
blobs.metadata.begin(), blobs.metadata.begin(),
blobs.metadata.end(), blobs.metadata.end(),
std::ostream_iterator<unsigned char>(meta)); std::ostream_iterator<unsigned char>(meta));
BOOST_LOG_TRIVIAL(error) gLog.error() << "Failed to deserialize transaction. txn = " << txn.str()
<< __func__ << " - meta = " << meta.str() << " txn length = "
<< " Failed to deserialize transaction. txn = " << txn.str() << std::to_string(blobs.transaction.size())
<< " - meta = " << meta.str() << " meta length = "
<< " txn length = " << std::to_string(blobs.transaction.size()) << std::to_string(blobs.metadata.size());
<< " meta length = " << std::to_string(blobs.metadata.size());
throw e; throw e;
} }
} }
@@ -657,13 +646,11 @@ traverseOwnedNodes(
ripple::keylet::account(accountID).key, sequence, yield)) ripple::keylet::account(accountID).key, sequence, yield))
return Status{RippledError::rpcACT_NOT_FOUND}; return Status{RippledError::rpcACT_NOT_FOUND};
auto parsedCursor = auto maybeCursor = parseAccountCursor(backend, sequence, jsonCursor, yield);
parseAccountCursor(backend, sequence, jsonCursor, accountID, yield); if (!maybeCursor)
if (!parsedCursor)
return Status(ripple::rpcINVALID_PARAMS, "Malformed cursor"); return Status(ripple::rpcINVALID_PARAMS, "Malformed cursor");
auto [hexCursor, startHint] = *parsedCursor; auto [hexCursor, startHint] = *maybeCursor;
return traverseOwnedNodes( return traverseOwnedNodes(
backend, backend,
@@ -709,22 +696,21 @@ traverseOwnedNodes(
auto hintDir = auto hintDir =
backend.fetchLedgerObject(hintIndex.key, sequence, yield); backend.fetchLedgerObject(hintIndex.key, sequence, yield);
if (hintDir) if (!hintDir)
{ return Status(ripple::rpcINVALID_PARAMS, "Invalid marker");
ripple::SerialIter it{hintDir->data(), hintDir->size()};
ripple::SLE sle{it, hintIndex.key};
for (auto const& key : sle.getFieldV256(ripple::sfIndexes)) ripple::SerialIter it{hintDir->data(), hintDir->size()};
{ ripple::SLE sle{it, hintIndex.key};
if (key == hexMarker)
{ if (auto const& indexes = sle.getFieldV256(ripple::sfIndexes);
// We found the hint, we can start here std::find(std::begin(indexes), std::end(indexes), hexMarker) ==
currentIndex = hintIndex; std::end(indexes))
break; {
} // result in empty dataset
} return AccountCursor({beast::zero, 0});
} }
currentIndex = hintIndex;
bool found = false; bool found = false;
for (;;) for (;;)
{ {
@@ -807,21 +793,17 @@ traverseOwnedNodes(
} }
auto end = std::chrono::system_clock::now(); auto end = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(debug) gLog.debug() << "Time loading owned directories: "
<< "Time loading owned directories: " << std::chrono::duration_cast<std::chrono::milliseconds>(
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start) end - start)
.count() .count()
<< " milliseconds"; << " milliseconds";
start = std::chrono::system_clock::now(); auto [objects, timeDiff] = util::timed(
auto objects = backend.fetchLedgerObjects(keys, sequence, yield); [&]() { return backend.fetchLedgerObjects(keys, sequence, yield); });
end = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(debug) gLog.debug() << "Time loading owned entries: " << timeDiff
<< "Time loading owned entries: " << " milliseconds";
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
.count()
<< " milliseconds";
for (auto i = 0; i < objects.size(); ++i) for (auto i = 0; i < objects.size(); ++i)
{ {
@@ -1341,7 +1323,7 @@ postProcessOrderBook(
} }
catch (std::exception const& e) catch (std::exception const& e)
{ {
BOOST_LOG_TRIVIAL(error) << "caught exception: " << e.what(); gLog.error() << "caught exception: " << e.what();
} }
} }
return jsonOffers; return jsonOffers;
@@ -1351,46 +1333,48 @@ std::variant<Status, ripple::Book>
parseBook(boost::json::object const& request) parseBook(boost::json::object const& request)
{ {
if (!request.contains("taker_pays")) if (!request.contains("taker_pays"))
return Status{RippledError::rpcBAD_MARKET, "missingTakerPays"}; return Status{
RippledError::rpcINVALID_PARAMS, "Missing field 'taker_pays'"};
if (!request.contains("taker_gets")) if (!request.contains("taker_gets"))
return Status{RippledError::rpcINVALID_PARAMS, "missingTakerGets"}; return Status{
RippledError::rpcINVALID_PARAMS, "Missing field 'taker_gets'"};
if (!request.at("taker_pays").is_object()) if (!request.at("taker_pays").is_object())
return Status{RippledError::rpcINVALID_PARAMS, "takerPaysNotObject"}; return Status{
RippledError::rpcINVALID_PARAMS,
"Field 'taker_pays' is not an object"};
if (!request.at("taker_gets").is_object()) if (!request.at("taker_gets").is_object())
return Status{RippledError::rpcINVALID_PARAMS, "takerGetsNotObject"}; return Status{
RippledError::rpcINVALID_PARAMS,
"Field 'taker_gets' is not an object"};
auto taker_pays = request.at("taker_pays").as_object(); auto taker_pays = request.at("taker_pays").as_object();
if (!taker_pays.contains("currency")) if (!taker_pays.contains("currency"))
return Status{ return Status{RippledError::rpcSRC_CUR_MALFORMED};
RippledError::rpcINVALID_PARAMS, "missingTakerPaysCurrency"};
if (!taker_pays.at("currency").is_string()) if (!taker_pays.at("currency").is_string())
return Status{ return Status{RippledError::rpcSRC_CUR_MALFORMED};
RippledError::rpcINVALID_PARAMS, "takerPaysCurrencyNotString"};
auto taker_gets = request.at("taker_gets").as_object(); auto taker_gets = request.at("taker_gets").as_object();
if (!taker_gets.contains("currency")) if (!taker_gets.contains("currency"))
return Status{ return Status{RippledError::rpcDST_AMT_MALFORMED};
RippledError::rpcINVALID_PARAMS, "missingTakerGetsCurrency"};
if (!taker_gets.at("currency").is_string()) if (!taker_gets.at("currency").is_string())
return Status{ return Status{
RippledError::rpcINVALID_PARAMS, "takerGetsCurrencyNotString"}; RippledError::rpcDST_AMT_MALFORMED,
};
ripple::Currency pay_currency; ripple::Currency pay_currency;
if (!ripple::to_currency( if (!ripple::to_currency(
pay_currency, taker_pays.at("currency").as_string().c_str())) pay_currency, taker_pays.at("currency").as_string().c_str()))
return Status{ return Status{RippledError::rpcSRC_CUR_MALFORMED};
RippledError::rpcSRC_CUR_MALFORMED, "badTakerPaysCurrency"};
ripple::Currency get_currency; ripple::Currency get_currency;
if (!ripple::to_currency( if (!ripple::to_currency(
get_currency, taker_gets["currency"].as_string().c_str())) get_currency, taker_gets["currency"].as_string().c_str()))
return Status{ return Status{RippledError::rpcDST_AMT_MALFORMED};
RippledError::rpcDST_AMT_MALFORMED, "badTakerGetsCurrency"};
ripple::AccountID pay_issuer; ripple::AccountID pay_issuer;
if (taker_pays.contains("issuer")) if (taker_pays.contains("issuer"))
@@ -1401,13 +1385,10 @@ parseBook(boost::json::object const& request)
if (!ripple::to_issuer( if (!ripple::to_issuer(
pay_issuer, taker_pays.at("issuer").as_string().c_str())) pay_issuer, taker_pays.at("issuer").as_string().c_str()))
return Status{ return Status{RippledError::rpcSRC_ISR_MALFORMED};
RippledError::rpcSRC_ISR_MALFORMED, "badTakerPaysIssuer"};
if (pay_issuer == ripple::noAccount()) if (pay_issuer == ripple::noAccount())
return Status{ return Status{RippledError::rpcSRC_ISR_MALFORMED};
RippledError::rpcSRC_ISR_MALFORMED,
"badTakerPaysIssuerAccountOne"};
} }
else else
{ {
@@ -1573,47 +1554,52 @@ traverseTransactions(
} }
auto minIndex = context.range.minSequence; auto minIndex = context.range.minSequence;
auto maxIndex = context.range.maxSequence;
std::optional<int64_t> min;
std::optional<int64_t> max;
if (request.contains(JS(ledger_index_min))) if (request.contains(JS(ledger_index_min)))
{ {
auto& min = request.at(JS(ledger_index_min)); if (!request.at(JS(ledger_index_min)).is_int64())
{
if (!min.is_int64())
return Status{ return Status{
RippledError::rpcINVALID_PARAMS, "ledgerSeqMinNotNumber"}; RippledError::rpcINVALID_PARAMS, "ledgerSeqMinNotNumber"};
}
if (min.as_int64() != -1) min = request.at(JS(ledger_index_min)).as_int64();
if (*min != -1)
{ {
if (context.range.maxSequence < min.as_int64() || if (context.range.maxSequence < *min ||
context.range.minSequence > min.as_int64()) context.range.minSequence > *min)
return Status{ return Status{
RippledError::rpcLGR_IDX_MALFORMED, RippledError::rpcLGR_IDX_MALFORMED,
"ledgerSeqMinOutOfRange"}; "ledgerSeqMinOutOfRange"};
else else
minIndex = boost::json::value_to<std::uint32_t>(min); minIndex = static_cast<uint32_t>(*min);
} }
if (forward && !cursor) if (forward && !cursor)
cursor = {minIndex, 0}; cursor = {minIndex, 0};
} }
auto maxIndex = context.range.maxSequence;
if (request.contains(JS(ledger_index_max))) if (request.contains(JS(ledger_index_max)))
{ {
auto& max = request.at(JS(ledger_index_max)); if (!request.at(JS(ledger_index_max)).is_int64())
{
if (!max.is_int64())
return Status{ return Status{
RippledError::rpcINVALID_PARAMS, "ledgerSeqMaxNotNumber"}; RippledError::rpcINVALID_PARAMS, "ledgerSeqMaxNotNumber"};
}
if (max.as_int64() != -1) max = request.at(JS(ledger_index_max)).as_int64();
if (*max != -1)
{ {
if (context.range.maxSequence < max.as_int64() || if (context.range.maxSequence < *max ||
context.range.minSequence > max.as_int64()) context.range.minSequence > *max)
return Status{ return Status{RippledError::rpcLGR_IDXS_INVALID};
RippledError::rpcLGR_IDX_MALFORMED,
"ledgerSeqMaxOutOfRange"};
else else
maxIndex = boost::json::value_to<std::uint32_t>(max); maxIndex = static_cast<uint32_t>(*max);
} }
if (minIndex > maxIndex) if (minIndex > maxIndex)
@@ -1623,6 +1609,11 @@ traverseTransactions(
cursor = {maxIndex, INT32_MAX}; cursor = {maxIndex, INT32_MAX};
} }
if (max && min && *max < *min)
{
return Status{RippledError::rpcLGR_IDXS_INVALID, "lgrIdxsInvalid"};
}
if (request.contains(JS(ledger_index)) || request.contains(JS(ledger_hash))) if (request.contains(JS(ledger_index)) || request.contains(JS(ledger_hash)))
{ {
if (request.contains(JS(ledger_index_max)) || if (request.contains(JS(ledger_index_max)) ||
@@ -1656,64 +1647,59 @@ traverseTransactions(
boost::json::array txns; boost::json::array txns;
auto [blobs, retCursor] = transactionFetcher( auto [blobs, retCursor] = transactionFetcher(
context.backend, limit, forward, cursor, context.yield); context.backend, limit, forward, cursor, context.yield);
auto serializationStart = std::chrono::system_clock::now(); auto timeDiff = util::timed([&, &retCursor = retCursor, &blobs = blobs]() {
if (retCursor)
if (retCursor)
{
boost::json::object cursorJson;
cursorJson[JS(ledger)] = retCursor->ledgerSequence;
cursorJson[JS(seq)] = retCursor->transactionIndex;
response[JS(marker)] = cursorJson;
}
for (auto const& txnPlusMeta : blobs)
{
if ((txnPlusMeta.ledgerSequence < minIndex && !forward) ||
(txnPlusMeta.ledgerSequence > maxIndex && forward))
{ {
response.erase(JS(marker)); boost::json::object cursorJson;
break; cursorJson[JS(ledger)] = retCursor->ledgerSequence;
} cursorJson[JS(seq)] = retCursor->transactionIndex;
else if (txnPlusMeta.ledgerSequence > maxIndex && !forward) response[JS(marker)] = cursorJson;
{
BOOST_LOG_TRIVIAL(debug)
<< __func__
<< " skipping over transactions from incomplete ledger";
continue;
} }
boost::json::object obj; for (auto const& txnPlusMeta : blobs)
if (!binary)
{ {
auto [txn, meta] = toExpandedJson(txnPlusMeta); if ((txnPlusMeta.ledgerSequence < minIndex && !forward) ||
obj[JS(meta)] = meta; (txnPlusMeta.ledgerSequence > maxIndex && forward))
obj[JS(tx)] = txn; {
obj[JS(tx)].as_object()[JS(ledger_index)] = response.erase(JS(marker));
txnPlusMeta.ledgerSequence; break;
obj[JS(tx)].as_object()[JS(date)] = txnPlusMeta.date; }
} else if (txnPlusMeta.ledgerSequence > maxIndex && !forward)
else {
{ gLog.debug()
obj[JS(meta)] = ripple::strHex(txnPlusMeta.metadata); << "Skipping over transactions from incomplete ledger";
obj[JS(tx_blob)] = ripple::strHex(txnPlusMeta.transaction); continue;
obj[JS(ledger_index)] = txnPlusMeta.ledgerSequence; }
obj[JS(date)] = txnPlusMeta.date;
}
obj[JS(validated)] = true;
txns.push_back(obj);
}
response[JS(ledger_index_min)] = minIndex; boost::json::object obj;
response[JS(ledger_index_max)] = maxIndex;
response[JS(transactions)] = txns;
BOOST_LOG_TRIVIAL(info) if (!binary)
<< __func__ << " serialization took " {
<< std::chrono::duration_cast<std::chrono::milliseconds>( auto [txn, meta] = toExpandedJson(txnPlusMeta);
std::chrono::system_clock::now() - serializationStart) obj[JS(meta)] = meta;
.count() obj[JS(tx)] = txn;
<< " milliseconds"; obj[JS(tx)].as_object()[JS(ledger_index)] =
txnPlusMeta.ledgerSequence;
obj[JS(tx)].as_object()[JS(date)] = txnPlusMeta.date;
}
else
{
obj[JS(meta)] = ripple::strHex(txnPlusMeta.metadata);
obj[JS(tx_blob)] = ripple::strHex(txnPlusMeta.transaction);
obj[JS(ledger_index)] = txnPlusMeta.ledgerSequence;
obj[JS(date)] = txnPlusMeta.date;
}
obj[JS(validated)] = true;
txns.push_back(obj);
}
response[JS(ledger_index_min)] = minIndex;
response[JS(ledger_index_max)] = maxIndex;
response[JS(transactions)] = txns;
});
gLog.info() << "serialization took " << timeDiff
<< " milliseconds";
return response; return response;
} }

View File

@@ -1,6 +1,24 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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
#ifndef XRPL_REPORTING_RPCHELPERS_H_INCLUDED
#define XRPL_REPORTING_RPCHELPERS_H_INCLUDED
/* /*
* This file contains a variety of utility functions used when executing * This file contains a variety of utility functions used when executing
* the handlers * the handlers
@@ -36,7 +54,6 @@ parseAccountCursor(
BackendInterface const& backend, BackendInterface const& backend,
std::uint32_t seq, std::uint32_t seq,
std::optional<std::string> jsonCursor, std::optional<std::string> jsonCursor,
ripple::AccountID const& accountID,
boost::asio::yield_context& yield); boost::asio::yield_context& yield);
// TODO this function should probably be in a different file and namespace // TODO this function should probably be in a different file and namespace
@@ -273,4 +290,3 @@ computeBookChanges(
std::vector<Backend::TransactionAndMetadata> const& transactions); std::vector<Backend::TransactionAndMetadata> const& transactions);
} // namespace RPC } // namespace RPC
#endif

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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/WorkQueue.h> #include <rpc/WorkQueue.h>
WorkQueue::WorkQueue(std::uint32_t numWorkers, uint32_t maxSize) WorkQueue::WorkQueue(std::uint32_t numWorkers, uint32_t maxSize)

View File

@@ -1,10 +1,29 @@
#ifndef CLIO_WORK_QUEUE_H //------------------------------------------------------------------------------
#define CLIO_WORK_QUEUE_H /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <log/Logger.h>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/asio/spawn.hpp> #include <boost/asio/spawn.hpp>
#include <boost/json.hpp> #include <boost/json.hpp>
#include <boost/log/trivial.hpp>
#include <memory> #include <memory>
#include <optional> #include <optional>
@@ -20,6 +39,7 @@ class WorkQueue
std::atomic_uint64_t curSize_ = 0; std::atomic_uint64_t curSize_ = 0;
uint32_t maxSize_ = std::numeric_limits<uint32_t>::max(); uint32_t maxSize_ = std::numeric_limits<uint32_t>::max();
clio::Logger log_{"RPC"};
public: public:
WorkQueue(std::uint32_t numWorkers, uint32_t maxSize = 0); WorkQueue(std::uint32_t numWorkers, uint32_t maxSize = 0);
@@ -30,10 +50,8 @@ public:
{ {
if (curSize_ >= maxSize_ && !isWhiteListed) if (curSize_ >= maxSize_ && !isWhiteListed)
{ {
BOOST_LOG_TRIVIAL(warning) log_.warn() << "Queue is full. rejecting job. current size = "
<< __func__ << curSize_ << " max size = " << maxSize_;
<< " queue is full. rejecting job. current size = " << curSize_
<< " max size = " << maxSize_;
return false; return false;
} }
++curSize_; ++curSize_;
@@ -52,8 +70,8 @@ public:
// durationUs_ // durationUs_
++queued_; ++queued_;
durationUs_ += wait; durationUs_ += wait;
BOOST_LOG_TRIVIAL(debug) << "WorkQueue wait time = " << wait log_.info() << "WorkQueue wait time = " << wait
<< " queue size = " << curSize_; << " queue size = " << curSize_;
f(yield); f(yield);
--curSize_; --curSize_;
}); });
@@ -77,5 +95,3 @@ private:
boost::asio::io_context ioc_ = {}; boost::asio::io_context ioc_ = {};
std::optional<boost::asio::io_context::work> work_{ioc_}; std::optional<boost::asio::io_context::work> work_{ioc_};
}; };
#endif // CLIO_WORK_QUEUE_H

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/app/ledger/Ledger.h> #include <ripple/app/ledger/Ledger.h>
#include <ripple/basics/StringUtilities.h> #include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/app/ledger/Ledger.h> #include <ripple/app/ledger/Ledger.h>
#include <ripple/basics/StringUtilities.h> #include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h> #include <ripple/protocol/STLedgerEntry.h>
#include <boost/json.hpp> #include <boost/json.hpp>
@@ -50,17 +69,12 @@ doAccountInfo(Context const& context)
if (!accountID) if (!accountID)
return Status{RippledError::rpcACT_MALFORMED}; return Status{RippledError::rpcACT_MALFORMED};
assert(accountID.has_value());
auto key = ripple::keylet::account(accountID.value()); auto key = ripple::keylet::account(accountID.value());
std::optional<std::vector<unsigned char>> dbResponse = std::optional<std::vector<unsigned char>> dbResponse =
context.backend->fetchLedgerObject(key.key, lgrInfo.seq, context.yield); context.backend->fetchLedgerObject(key.key, lgrInfo.seq, context.yield);
if (!dbResponse) if (!dbResponse)
{
return Status{RippledError::rpcACT_NOT_FOUND}; return Status{RippledError::rpcACT_NOT_FOUND};
}
ripple::STLedgerEntry sle{ ripple::STLedgerEntry sle{
ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key.key}; ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key.key};
@@ -68,12 +82,6 @@ doAccountInfo(Context const& context)
if (!key.check(sle)) if (!key.check(sle))
return Status{RippledError::rpcDB_DESERIALIZATION}; return Status{RippledError::rpcDB_DESERIALIZATION};
// if (!binary)
// response[JS(account_data)] = getJson(sle);
// else
// response[JS(account_data)] = ripple::strHex(*dbResponse);
// response[JS(db_time)] = time;
response[JS(account_data)] = toJson(sle); response[JS(account_data)] = toJson(sle);
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response[JS(ledger_index)] = lgrInfo.seq; response[JS(ledger_index)] = lgrInfo.seq;

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/app/ledger/Ledger.h> #include <ripple/app/ledger/Ledger.h>
#include <ripple/app/paths/TrustLine.h> #include <ripple/app/paths/TrustLine.h>
#include <ripple/basics/StringUtilities.h> #include <ripple/basics/StringUtilities.h>

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/app/ledger/Ledger.h> #include <ripple/app/ledger/Ledger.h>
#include <ripple/app/paths/TrustLine.h> #include <ripple/app/paths/TrustLine.h>
#include <ripple/app/tx/impl/details/NFTokenUtils.h> #include <ripple/app/tx/impl/details/NFTokenUtils.h>
@@ -202,8 +221,7 @@ doAccountObjects(Context const& context)
if (auto status = std::get_if<RPC::Status>(&next)) if (auto status = std::get_if<RPC::Status>(&next))
return *status; return *status;
auto nextMarker = std::get<RPC::AccountCursor>(next); auto const& nextMarker = std::get<RPC::AccountCursor>(next);
if (nextMarker.isNonZero()) if (nextMarker.isNonZero())
response[JS(marker)] = nextMarker.toString(); response[JS(marker)] = nextMarker.toString();

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/app/ledger/Ledger.h> #include <ripple/app/ledger/Ledger.h>
#include <ripple/app/paths/TrustLine.h> #include <ripple/app/paths/TrustLine.h>
#include <ripple/basics/StringUtilities.h> #include <ripple/basics/StringUtilities.h>

View File

@@ -1,4 +1,32 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <log/Logger.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <util/Profiler.h>
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gLog{"RPC"};
} // namespace
namespace RPC { namespace RPC {
@@ -18,15 +46,13 @@ doAccountTx(Context const& context)
bool const forward, bool const forward,
std::optional<Backend::TransactionsCursor> const& cursorIn, std::optional<Backend::TransactionsCursor> const& cursorIn,
boost::asio::yield_context& yield) { boost::asio::yield_context& yield) {
auto const start = std::chrono::system_clock::now(); auto [txnsAndCursor, timeDiff] = util::timed([&]() {
auto const txnsAndCursor = backend->fetchAccountTransactions( return backend->fetchAccountTransactions(
accountID, limit, forward, cursorIn, yield); accountID, limit, forward, cursorIn, yield);
BOOST_LOG_TRIVIAL(info) });
<< outerFuncName << " db fetch took " gLog.info() << outerFuncName << " db fetch took " << timeDiff
<< std::chrono::duration_cast<std::chrono::milliseconds>( << " milliseconds - num blobs = "
std::chrono::system_clock::now() - start) << txnsAndCursor.txns.size();
.count()
<< " milliseconds - num blobs = " << txnsAndCursor.txns.size();
return txnsAndCursor; return txnsAndCursor;
}); });

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/app/ledger/Ledger.h> #include <ripple/app/ledger/Ledger.h>
#include <ripple/basics/ToString.h> #include <ripple/basics/ToString.h>

View File

@@ -1,15 +1,43 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/app/ledger/Ledger.h> #include <ripple/app/ledger/Ledger.h>
#include <ripple/basics/StringUtilities.h> #include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h> #include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/jss.h> #include <ripple/protocol/jss.h>
#include <boost/json.hpp>
#include <algorithm>
#include <rpc/RPCHelpers.h>
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <backend/DBHelpers.h> #include <backend/DBHelpers.h>
#include <log/Logger.h>
#include <rpc/RPCHelpers.h>
#include <boost/json.hpp>
#include <algorithm>
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gLog{"RPC"};
} // namespace
namespace RPC { namespace RPC {
@@ -64,11 +92,11 @@ doBookOffers(Context const& context)
bookBase, lgrInfo.seq, limit, marker, context.yield); bookBase, lgrInfo.seq, limit, marker, context.yield);
auto end = std::chrono::system_clock::now(); auto end = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(warning) gLog.warn() << "Time loading books: "
<< "Time loading books: " << std::chrono::duration_cast<std::chrono::milliseconds>(
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start) end - start)
.count() .count()
<< " milliseconds - request = " << request; << " milliseconds - request = " << request;
response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); response[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
response[JS(ledger_index)] = lgrInfo.seq; response[JS(ledger_index)] = lgrInfo.seq;
@@ -78,11 +106,11 @@ doBookOffers(Context const& context)
auto end2 = std::chrono::system_clock::now(); auto end2 = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(warning) gLog.warn() << "Time transforming to json: "
<< "Time transforming to json: " << std::chrono::duration_cast<std::chrono::milliseconds>(
<< std::chrono::duration_cast<std::chrono::milliseconds>(end2 - end) end2 - end)
.count() .count()
<< " milliseconds - request = " << request; << " milliseconds - request = " << request;
if (retMarker) if (retMarker)
response["marker"] = ripple::strHex(*retMarker); response["marker"] = ripple::strHex(*retMarker);

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/basics/StringUtilities.h> #include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/PayChan.h> #include <ripple/protocol/PayChan.h>

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/basics/StringUtilities.h> #include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/PayChan.h> #include <ripple/protocol/PayChan.h>

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
@@ -25,7 +44,7 @@ doGatewayBalances(Context const& context)
std::map<ripple::AccountID, std::vector<ripple::STAmount>> frozenBalances; std::map<ripple::AccountID, std::vector<ripple::STAmount>> frozenBalances;
std::set<ripple::AccountID> hotWallets; std::set<ripple::AccountID> hotWallets;
if (request.contains("hot_wallet")) if (request.contains(JS(hotwallet)))
{ {
auto getAccountID = auto getAccountID =
[](auto const& j) -> std::optional<ripple::AccountID> { [](auto const& j) -> std::optional<ripple::AccountID> {
@@ -44,7 +63,7 @@ doGatewayBalances(Context const& context)
return {}; return {};
}; };
auto const& hw = request.at("hot_wallet"); auto const& hw = request.at(JS(hotwallet));
bool valid = true; bool valid = true;
// null is treated as a valid 0-sized array of hotwallet // null is treated as a valid 0-sized array of hotwallet
@@ -110,7 +129,7 @@ doGatewayBalances(Context const& context)
if (hotWallets.count(peer) > 0) if (hotWallets.count(peer) > 0)
{ {
// This is a specified hot wallet // This is a specified hot wallet
hotBalances[peer].push_back(balance); hotBalances[peer].push_back(-balance);
} }
else if (balSign > 0) else if (balSign > 0)
{ {
@@ -120,7 +139,7 @@ doGatewayBalances(Context const& context)
else if (freeze) else if (freeze)
{ {
// An obligation the gateway has frozen // An obligation the gateway has frozen
frozenBalances[peer].push_back(balance); frozenBalances[peer].push_back(-balance);
} }
else else
{ {

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>

View File

@@ -1,9 +1,30 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/app/ledger/LedgerToJson.h> #include <ripple/app/ledger/LedgerToJson.h>
#include <ripple/protocol/STLedgerEntry.h> #include <ripple/protocol/STLedgerEntry.h>
#include <backend/BackendInterface.h>
#include <log/Logger.h>
#include <rpc/RPCHelpers.h>
#include <boost/json.hpp> #include <boost/json.hpp>
#include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h>
// Get state nodes from a ledger // Get state nodes from a ledger
// Inputs: // Inputs:
// limit: integer, maximum number of entries // limit: integer, maximum number of entries
@@ -18,6 +39,13 @@
// //
// //
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gLog{"RPC"};
} // namespace
namespace RPC { namespace RPC {
using boost::json::value_to; using boost::json::value_to;
@@ -65,7 +93,7 @@ doLedgerData(Context const& context)
} }
else else
{ {
BOOST_LOG_TRIVIAL(debug) << __func__ << " : parsing marker"; gLog.debug() << "Parsing marker";
marker = ripple::uint256{}; marker = ripple::uint256{};
if (!marker->parseHex(request.at(JS(marker)).as_string().c_str())) if (!marker->parseHex(request.at(JS(marker)).as_string().c_str()))
@@ -98,7 +126,6 @@ doLedgerData(Context const& context)
header[JS(close_time_human)] = ripple::to_string(lgrInfo.closeTime); header[JS(close_time_human)] = ripple::to_string(lgrInfo.closeTime);
header[JS(close_time_resolution)] = header[JS(close_time_resolution)] =
lgrInfo.closeTimeResolution.count(); lgrInfo.closeTimeResolution.count();
header[JS(closed)] = true;
header[JS(hash)] = ripple::strHex(lgrInfo.hash); header[JS(hash)] = ripple::strHex(lgrInfo.hash);
header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
header[JS(ledger_index)] = std::to_string(lgrInfo.seq); header[JS(ledger_index)] = std::to_string(lgrInfo.seq);
@@ -111,6 +138,7 @@ doLedgerData(Context const& context)
header[JS(transaction_hash)] = ripple::strHex(lgrInfo.txHash); header[JS(transaction_hash)] = ripple::strHex(lgrInfo.txHash);
} }
header[JS(closed)] = true;
response[JS(ledger)] = header; response[JS(ledger)] = header;
} }
else else
@@ -168,9 +196,8 @@ doLedgerData(Context const& context)
std::chrono::duration_cast<std::chrono::microseconds>(end - start) std::chrono::duration_cast<std::chrono::microseconds>(end - start)
.count(); .count();
BOOST_LOG_TRIVIAL(debug) gLog.debug() << "Number of results = " << results.size() << " fetched in "
<< __func__ << " number of results = " << results.size() << time << " microseconds";
<< " fetched in " << time << " microseconds";
boost::json::array objects; boost::json::array objects;
objects.reserve(results.size()); objects.reserve(results.size());
for (auto const& [key, object] : results) for (auto const& [key, object] : results)
@@ -194,9 +221,8 @@ doLedgerData(Context const& context)
time = std::chrono::duration_cast<std::chrono::microseconds>(end2 - end) time = std::chrono::duration_cast<std::chrono::microseconds>(end2 - end)
.count(); .count();
BOOST_LOG_TRIVIAL(debug) gLog.debug() << "Number of results = " << results.size()
<< __func__ << " number of results = " << results.size() << " serialized in " << time << " microseconds";
<< " serialized in " << time << " microseconds";
return response; return response;
} }

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h> #include <ripple/protocol/STLedgerEntry.h>
#include <boost/json.hpp> #include <boost/json.hpp>
@@ -29,13 +48,16 @@ doLedgerEntry(Context const& context)
auto lgrInfo = std::get<ripple::LedgerInfo>(v); auto lgrInfo = std::get<ripple::LedgerInfo>(v);
ripple::uint256 key; ripple::uint256 key;
// Note: according to docs, only 1 of the below should be specified at any
// time. see https://xrpl.org/ledger_entry.html#ledger_entry
if (request.contains(JS(index))) if (request.contains(JS(index)))
{ {
if (!request.at(JS(index)).is_string()) if (!request.at(JS(index)).is_string())
return Status{RippledError::rpcINVALID_PARAMS, "indexNotString"}; return Status{RippledError::rpcINVALID_PARAMS, "indexNotString"};
if (!key.parseHex(request.at(JS(index)).as_string().c_str())) if (!key.parseHex(request.at(JS(index)).as_string().c_str()))
return Status{RippledError::rpcINVALID_PARAMS, "malformedIndex"}; return Status{ClioError::rpcMALFORMED_REQUEST};
} }
else if (request.contains(JS(account_root))) else if (request.contains(JS(account_root)))
{ {
@@ -46,7 +68,7 @@ doLedgerEntry(Context const& context)
auto const account = ripple::parseBase58<ripple::AccountID>( auto const account = ripple::parseBase58<ripple::AccountID>(
request.at(JS(account_root)).as_string().c_str()); request.at(JS(account_root)).as_string().c_str());
if (!account || account->isZero()) if (!account || account->isZero())
return Status{RippledError::rpcINVALID_PARAMS, "malformedAddress"}; return Status{ClioError::rpcMALFORMED_ADDRESS};
else else
key = ripple::keylet::account(*account).key; key = ripple::keylet::account(*account).key;
} }
@@ -172,8 +194,7 @@ doLedgerEntry(Context const& context)
if (!ownerID) if (!ownerID)
{ {
return Status{ return Status{ClioError::rpcMALFORMED_ADDRESS};
RippledError::rpcINVALID_PARAMS, "malformedAddress"};
} }
else else
{ {
@@ -219,8 +240,7 @@ doLedgerEntry(Context const& context)
.c_str()); .c_str());
if (!id) if (!id)
return Status{ return Status{ClioError::rpcMALFORMED_ADDRESS};
RippledError::rpcINVALID_PARAMS, "malformedAddress"};
else else
{ {
std::uint32_t seq = std::uint32_t seq =
@@ -256,8 +276,7 @@ doLedgerEntry(Context const& context)
offer.at(JS(account)).as_string().c_str()); offer.at(JS(account)).as_string().c_str());
if (!id) if (!id)
return Status{ return Status{ClioError::rpcMALFORMED_ADDRESS};
RippledError::rpcINVALID_PARAMS, "malformedAddress"};
else else
{ {
std::uint32_t seq = std::uint32_t seq =
@@ -310,7 +329,7 @@ doLedgerEntry(Context const& context)
if (!id1 || !id2) if (!id1 || !id2)
return Status{ return Status{
RippledError::rpcINVALID_PARAMS, "malformedAddresses"}; ClioError::rpcMALFORMED_ADDRESS, "malformedAddresses"};
else if (!ripple::to_currency( else if (!ripple::to_currency(
currency, state.at(JS(currency)).as_string().c_str())) currency, state.at(JS(currency)).as_string().c_str()))
@@ -325,24 +344,24 @@ doLedgerEntry(Context const& context)
{ {
if (!request.at(JS(ticket)).is_string()) if (!request.at(JS(ticket)).is_string())
return Status{ return Status{
RippledError::rpcINVALID_PARAMS, "ticketNotString"}; ClioError::rpcMALFORMED_REQUEST, "ticketNotString"};
if (!key.parseHex(request.at(JS(ticket)).as_string().c_str())) if (!key.parseHex(request.at(JS(ticket)).as_string().c_str()))
return Status{ return Status{
RippledError::rpcINVALID_PARAMS, "malformedTicket"}; ClioError::rpcMALFORMED_REQUEST, "malformedTicket"};
} }
else if ( else if (
!request.at(JS(ticket)).as_object().contains(JS(owner)) || !request.at(JS(ticket)).as_object().contains(JS(owner)) ||
!request.at(JS(ticket)).as_object().at(JS(owner)).is_string()) !request.at(JS(ticket)).as_object().at(JS(owner)).is_string())
{ {
return Status{RippledError::rpcINVALID_PARAMS, "malformedOwner"}; return Status{ClioError::rpcMALFORMED_REQUEST};
} }
else if ( else if (
!request.at(JS(ticket)).as_object().contains(JS(ticket_seq)) || !request.at(JS(ticket)).as_object().contains(JS(ticket_seq)) ||
!request.at(JS(ticket)).as_object().at(JS(ticket_seq)).is_int64()) !request.at(JS(ticket)).as_object().at(JS(ticket_seq)).is_int64())
{ {
return Status{ return Status{
RippledError::rpcINVALID_PARAMS, "malformedTicketSeq"}; ClioError::rpcMALFORMED_REQUEST, "malformedTicketSeq"};
} }
else else
{ {
@@ -354,8 +373,7 @@ doLedgerEntry(Context const& context)
.c_str()); .c_str());
if (!id) if (!id)
return Status{ return Status{ClioError::rpcMALFORMED_OWNER};
RippledError::rpcINVALID_PARAMS, "malformedOwner"};
else else
{ {
std::uint32_t seq = request.at(JS(offer)) std::uint32_t seq = request.at(JS(offer))

View File

@@ -1,3 +1,21 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>

View File

@@ -1,4 +1,32 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <log/Logger.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <util/Profiler.h>
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gLog{"RPC"};
} // namespace
namespace RPC { namespace RPC {
@@ -20,15 +48,14 @@ doNFTHistory(Context const& context)
std::optional<Backend::TransactionsCursor> const& cursorIn, std::optional<Backend::TransactionsCursor> const& cursorIn,
boost::asio::yield_context& yield) boost::asio::yield_context& yield)
-> Backend::TransactionsAndCursor { -> Backend::TransactionsAndCursor {
auto const start = std::chrono::system_clock::now(); auto const [txnsAndCursor, timeDiff] =
auto const txnsAndCursor = backend->fetchNFTTransactions( util::timed([&, &tokenID = tokenID]() {
tokenID, limit, forward, cursorIn, yield); return backend->fetchNFTTransactions(
BOOST_LOG_TRIVIAL(info) tokenID, limit, forward, cursorIn, yield);
<< outerFuncName << " db fetch took " });
<< std::chrono::duration_cast<std::chrono::milliseconds>( gLog.info() << outerFuncName << " db fetch took " << timeDiff
std::chrono::system_clock::now() - start) << " milliseconds - num blobs = "
.count() << txnsAndCursor.txns.size();
<< " milliseconds - num blobs = " << txnsAndCursor.txns.size();
return txnsAndCursor; return txnsAndCursor;
}); });

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/app/tx/impl/details/NFTokenUtils.h> #include <ripple/app/tx/impl/details/NFTokenUtils.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
#include <boost/json.hpp> #include <boost/json.hpp>

View File

@@ -1,13 +1,34 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/app/ledger/Ledger.h> #include <ripple/app/ledger/Ledger.h>
#include <ripple/basics/StringUtilities.h> #include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h> #include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/jss.h> #include <ripple/protocol/jss.h>
#include <boost/json.hpp>
#include <algorithm>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <boost/json.hpp>
#include <algorithm>
namespace json = boost::json; namespace json = boost::json;
namespace ripple { namespace ripple {

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ripple/protocol/TxFlags.h> #include <ripple/protocol/TxFlags.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>

View File

@@ -1,8 +1,27 @@
// rngfill.h doesn't compile without this include //------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <cassert> #include <cassert>
#include <ripple/beast/utility/rngfill.h> #include <ripple/beast/utility/rngfill.h>
#include <ripple/crypto/csprng.h> #include <ripple/crypto/csprng.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
namespace RPC { namespace RPC {

View File

@@ -1,3 +1,21 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <etl/ETLSource.h> #include <etl/ETLSource.h>

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <boost/json.hpp> #include <boost/json.hpp>
#include <subscriptions/SubscriptionManager.h> #include <subscriptions/SubscriptionManager.h>
#include <webserver/WsBase.h> #include <webserver/WsBase.h>
@@ -442,8 +461,7 @@ doUnsubscribe(Context const& context)
if (request.contains("books")) if (request.contains("books"))
unsubscribeToBooks(books, context.session, *context.subscriptions); unsubscribeToBooks(books, context.session, *context.subscriptions);
boost::json::object response = {{"status", "success"}}; return boost::json::object{};
return response;
} }
} // namespace RPC } // namespace RPC

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
namespace RPC { namespace RPC {

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
@@ -31,6 +50,17 @@ doTx(Context const& context)
binary = request.at(JS(binary)).as_bool(); binary = request.at(JS(binary)).as_bool();
} }
auto minLedger = getUInt(request, JS(min_ledger));
auto maxLedger = getUInt(request, JS(max_ledger));
bool rangeSupplied = minLedger && maxLedger;
if (rangeSupplied)
{
if (*minLedger > *maxLedger)
return Status{RippledError::rpcINVALID_LGR_RANGE};
if (*maxLedger - *minLedger > 1000)
return Status{RippledError::rpcEXCESSIVE_LGR_RANGE};
}
auto range = context.backend->fetchLedgerRange(); auto range = context.backend->fetchLedgerRange();
if (!range) if (!range)
@@ -38,7 +68,17 @@ doTx(Context const& context)
auto dbResponse = context.backend->fetchTransaction(hash, context.yield); auto dbResponse = context.backend->fetchTransaction(hash, context.yield);
if (!dbResponse) if (!dbResponse)
{
if (rangeSupplied)
{
bool searchedAll = range->maxSequence >= *maxLedger &&
range->minSequence <= *minLedger;
boost::json::object extra;
extra["searched_all"] = searchedAll;
return Status{RippledError::rpcTXN_NOT_FOUND, std::move(extra)};
}
return Status{RippledError::rpcTXN_NOT_FOUND}; return Status{RippledError::rpcTXN_NOT_FOUND};
}
if (!binary) if (!binary)
{ {

View File

@@ -1,5 +1,23 @@
#ifndef CLIO_SUBSCRIPTION_MESSAGE_H //------------------------------------------------------------------------------
#define CLIO_SUBSCRIPTION_MESSAGE_H /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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> #include <string>
@@ -36,5 +54,3 @@ public:
return message_.size(); return message_.size();
} }
}; };
#endif // CLIO_SUBSCRIPTION_MESSAGE_H

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <subscriptions/SubscriptionManager.h> #include <subscriptions/SubscriptionManager.h>
#include <webserver/WsBase.h> #include <webserver/WsBase.h>
@@ -155,7 +174,9 @@ SubscriptionManager::subLedger(
boost::asio::yield_context& yield, boost::asio::yield_context& yield,
std::shared_ptr<WsBase> session) std::shared_ptr<WsBase> session)
{ {
ledgerSubscribers_.subscribe(session); subscribeHelper(session, ledgerSubscribers_, [this](session_ptr session) {
unsubLedger(session);
});
auto ledgerRange = backend_->fetchLedgerRange(); auto ledgerRange = backend_->fetchLedgerRange();
assert(ledgerRange); assert(ledgerRange);
@@ -185,7 +206,9 @@ SubscriptionManager::unsubLedger(std::shared_ptr<WsBase> session)
void void
SubscriptionManager::subTransactions(std::shared_ptr<WsBase> session) SubscriptionManager::subTransactions(std::shared_ptr<WsBase> session)
{ {
txSubscribers_.subscribe(session); subscribeHelper(session, txSubscribers_, [this](session_ptr session) {
unsubTransactions(session);
});
} }
void void
@@ -199,12 +222,13 @@ SubscriptionManager::subAccount(
ripple::AccountID const& account, ripple::AccountID const& account,
std::shared_ptr<WsBase>& session) std::shared_ptr<WsBase>& session)
{ {
accountSubscribers_.subscribe(session, account); subscribeHelper(
session,
std::unique_lock lk(cleanupMtx_); account,
cleanupFuncs_[session].emplace_back([this, account](session_ptr session) { accountSubscribers_,
unsubAccount(account, session); [this, account](session_ptr session) {
}); unsubAccount(account, session);
});
} }
void void
@@ -220,11 +244,10 @@ SubscriptionManager::subBook(
ripple::Book const& book, ripple::Book const& book,
std::shared_ptr<WsBase> session) std::shared_ptr<WsBase> session)
{ {
bookSubscribers_.subscribe(session, book); subscribeHelper(
session, book, bookSubscribers_, [this, book](session_ptr session) {
std::unique_lock lk(cleanupMtx_); unsubBook(book, session);
cleanupFuncs_[session].emplace_back( });
[this, book](session_ptr session) { unsubBook(book, session); });
} }
void void
@@ -238,11 +261,10 @@ SubscriptionManager::unsubBook(
void void
SubscriptionManager::subBookChanges(std::shared_ptr<WsBase> session) SubscriptionManager::subBookChanges(std::shared_ptr<WsBase> session)
{ {
bookChangesSubscribers_.subscribe(session); subscribeHelper(
session, bookChangesSubscribers_, [this](session_ptr session) {
std::unique_lock lk(cleanupMtx_); unsubBookChanges(session);
cleanupFuncs_[session].emplace_back( });
[this](session_ptr session) { unsubBookChanges(session); });
} }
void void
@@ -408,13 +430,21 @@ SubscriptionManager::subProposedAccount(
ripple::AccountID const& account, ripple::AccountID const& account,
std::shared_ptr<WsBase> session) std::shared_ptr<WsBase> session)
{ {
accountProposedSubscribers_.subscribe(session, account); subscribeHelper(
session,
account,
accountProposedSubscribers_,
[this, account](session_ptr session) {
unsubProposedAccount(account, session);
});
} }
void void
SubscriptionManager::subManifest(std::shared_ptr<WsBase> session) SubscriptionManager::subManifest(std::shared_ptr<WsBase> session)
{ {
manifestSubscribers_.subscribe(session); subscribeHelper(session, manifestSubscribers_, [this](session_ptr session) {
unsubManifest(session);
});
} }
void void
@@ -426,7 +456,10 @@ SubscriptionManager::unsubManifest(std::shared_ptr<WsBase> session)
void void
SubscriptionManager::subValidation(std::shared_ptr<WsBase> session) SubscriptionManager::subValidation(std::shared_ptr<WsBase> session)
{ {
validationsSubscribers_.subscribe(session); subscribeHelper(
session, validationsSubscribers_, [this](session_ptr session) {
unsubValidation(session);
});
} }
void void
@@ -446,7 +479,10 @@ SubscriptionManager::unsubProposedAccount(
void void
SubscriptionManager::subProposedTransactions(std::shared_ptr<WsBase> session) SubscriptionManager::subProposedTransactions(std::shared_ptr<WsBase> session)
{ {
txProposedSubscribers_.subscribe(session); subscribeHelper(
session, txProposedSubscribers_, [this](session_ptr session) {
unsubProposedTransactions(session);
});
} }
void void
@@ -454,6 +490,28 @@ SubscriptionManager::unsubProposedTransactions(std::shared_ptr<WsBase> session)
{ {
txProposedSubscribers_.unsubscribe(session); txProposedSubscribers_.unsubscribe(session);
} }
void
SubscriptionManager::subscribeHelper(
std::shared_ptr<WsBase>& session,
Subscription& subs,
CleanupFunction&& func)
{
subs.subscribe(session);
std::unique_lock lk(cleanupMtx_);
cleanupFuncs_[session].push_back(std::move(func));
}
template <typename Key>
void
SubscriptionManager::subscribeHelper(
std::shared_ptr<WsBase>& session,
Key const& k,
SubscriptionMap<Key>& subs,
CleanupFunction&& func)
{
subs.subscribe(session, k);
std::unique_lock lk(cleanupMtx_);
cleanupFuncs_[session].push_back(std::move(func));
}
void void
SubscriptionManager::cleanup(std::shared_ptr<WsBase> session) SubscriptionManager::cleanup(std::shared_ptr<WsBase> session)

View File

@@ -1,11 +1,31 @@
#ifndef SUBSCRIPTION_MANAGER_H //------------------------------------------------------------------------------
#define SUBSCRIPTION_MANAGER_H /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include <backend/BackendInterface.h> #include <backend/BackendInterface.h>
#include <config/Config.h> #include <config/Config.h>
#include <memory> #include <log/Logger.h>
#include <subscriptions/Message.h> #include <subscriptions/Message.h>
#include <memory>
class WsBase; class WsBase;
class Subscription class Subscription
@@ -87,6 +107,7 @@ public:
class SubscriptionManager class SubscriptionManager
{ {
using session_ptr = std::shared_ptr<WsBase>; using session_ptr = std::shared_ptr<WsBase>;
clio::Logger log_{"Subscriptions"};
std::vector<std::thread> workers_; std::vector<std::thread> workers_;
boost::asio::io_context ioc_; boost::asio::io_context ioc_;
@@ -134,8 +155,8 @@ public:
// We will eventually want to clamp this to be the number of strands, // We will eventually want to clamp this to be the number of strands,
// since adding more threads than we have strands won't see any // since adding more threads than we have strands won't see any
// performance benefits // performance benefits
BOOST_LOG_TRIVIAL(info) << "Starting subscription manager with " log_.info() << "Starting subscription manager with " << numThreads
<< numThreads << " workers"; << " workers";
workers_.reserve(numThreads); workers_.reserve(numThreads);
for (auto i = numThreads; i > 0; --i) for (auto i = numThreads; i > 0; --i)
@@ -256,16 +277,29 @@ private:
void void
sendAll(std::string const& pubMsg, std::unordered_set<session_ptr>& subs); sendAll(std::string const& pubMsg, std::unordered_set<session_ptr>& subs);
using CleanupFunction = std::function<void(session_ptr)>;
void
subscribeHelper(
std::shared_ptr<WsBase>& session,
Subscription& subs,
CleanupFunction&& func);
template <typename Key>
void
subscribeHelper(
std::shared_ptr<WsBase>& session,
Key const& k,
SubscriptionMap<Key>& subs,
CleanupFunction&& func);
/** /**
* This is how we chose to cleanup subscriptions that have been closed. * This is how we chose to cleanup subscriptions that have been closed.
* Each time we add a subscriber, we add the opposite lambda that * Each time we add a subscriber, we add the opposite lambda that
* unsubscribes that subscriber when cleanup is called with the session that * unsubscribes that subscriber when cleanup is called with the session that
* closed. * closed.
*/ */
using CleanupFunction = std::function<void(session_ptr)>;
std::mutex cleanupMtx_; std::mutex cleanupMtx_;
std::unordered_map<session_ptr, std::vector<CleanupFunction>> std::unordered_map<session_ptr, std::vector<CleanupFunction>>
cleanupFuncs_ = {}; cleanupFuncs_ = {};
}; };
#endif // SUBSCRIPTION_MANAGER_H

59
src/util/Profiler.h Normal file
View File

@@ -0,0 +1,59 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <chrono>
#include <type_traits>
#include <utility>
namespace util {
/**
* @brief Profiler function to measure the time consuming
* @param func function object, can be a lamdba or function wrapper
* @return return a pair if function wrapper has return value: result of
* function wrapper and the elapsed time(ms) during executing the given
* function only return the elapsed time if function wrapper does not have
* return value
*/
template <typename U = std::chrono::milliseconds, typename F>
[[nodiscard]] auto
timed(F&& func)
{
auto start = std::chrono::system_clock::now();
if constexpr (std::is_same_v<decltype(func()), void>)
{
func();
return std::chrono::duration_cast<U>(
std::chrono::system_clock::now() - start)
.count();
}
else
{
auto ret = func();
auto elapsed = std::chrono::duration_cast<U>(
std::chrono::system_clock::now() - start)
.count();
return std::make_pair(ret, elapsed);
}
}
} // namespace util

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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/Taggable.h> #include <util/Taggable.h>
#include <boost/json.hpp> #include <boost/json.hpp>

View File

@@ -1,5 +1,23 @@
#ifndef RIPPLE_UTIL_TAGDECORATOR_H //------------------------------------------------------------------------------
#define RIPPLE_UTIL_TAGDECORATOR_H /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/json.hpp> #include <boost/json.hpp>
@@ -209,7 +227,8 @@ private:
friend Type friend Type
tag_invoke(boost::json::value_to_tag<Type>, boost::json::value const& value) tag_invoke(boost::json::value_to_tag<Type>, boost::json::value const& value)
{ {
assert(value.is_string()); if (not value.is_string())
throw std::runtime_error("`log_tag_style` must be a string");
auto const& style = value.as_string(); auto const& style = value.as_string();
if (boost::iequals(style, "int") || boost::iequals(style, "uint")) if (boost::iequals(style, "int") || boost::iequals(style, "uint"))
@@ -258,5 +277,3 @@ public:
}; };
} // namespace util } // namespace util
#endif // RIPPLE_UTIL_TAGDECORATOR_H

View File

@@ -1,93 +1,239 @@
#ifndef RIPPLE_REPORTING_DOS_GUARD_H //------------------------------------------------------------------------------
#define RIPPLE_REPORTING_DOS_GUARD_H /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/iterator/transform_iterator.hpp>
#include <chrono>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <config/Config.h> #include <config/Config.h>
class DOSGuard namespace clio {
class BaseDOSGuard
{ {
boost::asio::io_context& ctx_; public:
std::mutex mtx_; // protects ipFetchCount_ virtual ~BaseDOSGuard() = default;
std::unordered_map<std::string, std::uint32_t> ipFetchCount_;
virtual void
clear() noexcept = 0;
};
/**
* @brief A simple denial of service guard used for rate limiting.
*
* @tparam SweepHandler Type of the sweep handler
*/
template <typename SweepHandler>
class BasicDOSGuard : public BaseDOSGuard
{
// Accumulated state per IP, state will be reset accordingly
struct ClientState
{
// accumulated transfered byte
std::uint32_t transferedByte = 0;
// accumulated served requests count
std::uint32_t requestsCount = 0;
};
mutable std::mutex mtx_;
// accumulated states map
std::unordered_map<std::string, ClientState> ipState_;
std::unordered_map<std::string, std::uint32_t> ipConnCount_;
std::unordered_set<std::string> const whitelist_; std::unordered_set<std::string> const whitelist_;
std::uint32_t const maxFetches_; std::uint32_t const maxFetches_;
std::uint32_t const sweepInterval_; std::uint32_t const maxConnCount_;
std::uint32_t const maxRequestCount_;
clio::Logger log_{"RPC"};
public: public:
DOSGuard(clio::Config const& config, boost::asio::io_context& ctx) /**
: ctx_{ctx} * @brief Constructs a new DOS guard.
, whitelist_{getWhitelist(config)} *
, maxFetches_{config.valueOr("dos_guard.max_fetches", 100u)} * @param config Clio config
, sweepInterval_{config.valueOr("dos_guard.sweep_interval", 1u)} * @param sweepHandler Sweep handler that implements the sweeping behaviour
*/
BasicDOSGuard(clio::Config const& config, SweepHandler& sweepHandler)
: whitelist_{getWhitelist(config)}
, maxFetches_{config.valueOr("dos_guard.max_fetches", 100000000u)}
, maxConnCount_{config.valueOr("dos_guard.max_connections", 1u)}
, maxRequestCount_{config.valueOr("dos_guard.max_requests", 10u)}
{ {
createTimer(); sweepHandler.setup(this);
} }
void /**
createTimer() * @brief Check whether an ip address is in the whitelist or not
{ *
auto wait = std::chrono::seconds(sweepInterval_); * @param ip The ip address to check
std::shared_ptr<boost::asio::steady_timer> timer = * @return true
std::make_shared<boost::asio::steady_timer>( * @return false
ctx_, std::chrono::steady_clock::now() + wait); */
timer->async_wait( [[nodiscard]] bool
[timer, this](const boost::system::error_code& error) { isWhiteListed(std::string const& ip) const noexcept
clear();
createTimer();
});
}
bool
isWhiteListed(std::string const& ip)
{ {
return whitelist_.contains(ip); return whitelist_.contains(ip);
} }
bool /**
isOk(std::string const& ip) * @brief Check whether an ip address is currently rate limited or not
{ *
if (whitelist_.contains(ip)) * @param ip The ip address to check
return true; * @return true If not rate limited
* @return false If rate limited and the request should not be processed
std::unique_lock lck(mtx_); */
auto it = ipFetchCount_.find(ip); [[nodiscard]] bool
if (it == ipFetchCount_.end()) isOk(std::string const& ip) const noexcept
return true;
return it->second < maxFetches_;
}
bool
add(std::string const& ip, uint32_t numObjects)
{ {
if (whitelist_.contains(ip)) if (whitelist_.contains(ip))
return true; return true;
{ {
std::unique_lock lck(mtx_); std::unique_lock lck(mtx_);
auto it = ipFetchCount_.find(ip); if (ipState_.find(ip) != ipState_.end())
if (it == ipFetchCount_.end()) {
ipFetchCount_[ip] = numObjects; auto [transferedByte, requests] = ipState_.at(ip);
else if (transferedByte > maxFetches_ || requests > maxRequestCount_)
it->second += numObjects; {
log_.warn()
<< "Dosguard:Client surpassed the rate limit. ip = "
<< ip << " Transfered Byte:" << transferedByte
<< " Requests:" << requests;
return false;
}
}
auto it = ipConnCount_.find(ip);
if (it != ipConnCount_.end())
{
if (it->second > maxConnCount_)
{
log_.warn()
<< "Dosguard:Client surpassed the rate limit. ip = "
<< ip << " Concurrent connection:" << it->second;
return false;
}
}
}
return true;
}
/**
* @brief Increment connection count for the given ip address
*
* @param ip
*/
void
increment(std::string const& ip) noexcept
{
if (whitelist_.contains(ip))
return;
std::unique_lock lck{mtx_};
ipConnCount_[ip]++;
}
/**
* @brief Decrement connection count for the given ip address
*
* @param ip
*/
void
decrement(std::string const& ip) noexcept
{
if (whitelist_.contains(ip))
return;
std::unique_lock lck{mtx_};
assert(ipConnCount_[ip] > 0);
ipConnCount_[ip]--;
if (ipConnCount_[ip] == 0)
ipConnCount_.erase(ip);
}
/**
* @brief Adds numObjects of usage for the given ip address.
*
* If the total sums up to a value equal or larger than maxFetches_
* the operation is no longer allowed and false is returned; true is
* returned otherwise.
*
* @param ip
* @param numObjects
* @return true
* @return false
*/
[[maybe_unused]] bool
add(std::string const& ip, uint32_t numObjects) noexcept
{
if (whitelist_.contains(ip))
return true;
{
std::unique_lock lck(mtx_);
ipState_[ip].transferedByte += numObjects;
} }
return isOk(ip); return isOk(ip);
} }
/**
* @brief Adds one request for the given ip address.
*
* If the total sums up to a value equal or larger than maxRequestCount_
* the operation is no longer allowed and false is returned; true is
* returned otherwise.
*
* @param ip
* @return true
* @return false
*/
[[maybe_unused]] bool
request(std::string const& ip) noexcept
{
if (whitelist_.contains(ip))
return true;
{
std::unique_lock lck(mtx_);
ipState_[ip].requestsCount++;
}
return isOk(ip);
}
/**
* @brief Instantly clears all fetch counters added by @see add(std::string
* const&, uint32_t)
*/
void void
clear() clear() noexcept override
{ {
std::unique_lock lck(mtx_); std::unique_lock lck(mtx_);
ipFetchCount_.clear(); ipState_.clear();
} }
private: private:
std::unordered_set<std::string> const [[nodiscard]] std::unordered_set<std::string> const
getWhitelist(clio::Config const& config) const getWhitelist(clio::Config const& config) const
{ {
using T = std::unordered_set<std::string> const; using T = std::unordered_set<std::string> const;
@@ -100,4 +246,72 @@ private:
boost::transform_iterator(std::end(whitelist), transform)}; boost::transform_iterator(std::end(whitelist), transform)};
} }
}; };
#endif
/**
* @brief Sweep handler using a steady_timer and boost::asio::io_context.
*/
class IntervalSweepHandler
{
std::chrono::milliseconds sweepInterval_;
std::reference_wrapper<boost::asio::io_context> ctx_;
BaseDOSGuard* dosGuard_ = nullptr;
boost::asio::steady_timer timer_{ctx_.get()};
public:
/**
* @brief Construct a new interval-based sweep handler
*
* @param config Clio config
* @param ctx The boost::asio::io_context
*/
IntervalSweepHandler(
clio::Config const& config,
boost::asio::io_context& ctx)
: sweepInterval_{std::max(
1u,
static_cast<uint32_t>(
config.valueOr("dos_guard.sweep_interval", 10.0) * 1000.0))}
, ctx_{std::ref(ctx)}
{
}
~IntervalSweepHandler()
{
timer_.cancel();
}
/**
* @brief This setup member function is called by @ref BasicDOSGuard during
* its initialization.
*
* @param guard Pointer to the dos guard
*/
void
setup(BaseDOSGuard* guard)
{
assert(dosGuard_ == nullptr);
dosGuard_ = guard;
assert(dosGuard_ != nullptr);
createTimer();
}
private:
void
createTimer()
{
timer_.expires_after(sweepInterval_);
timer_.async_wait([this](boost::system::error_code const& error) {
if (error == boost::asio::error::operation_aborted)
return;
dosGuard_->clear();
createTimer();
});
}
};
using DOSGuard = BasicDOSGuard<IntervalSweepHandler>;
} // namespace clio

View File

@@ -1,5 +1,23 @@
#ifndef RIPPLE_REPORTING_HTTP_BASE_SESSION_H //------------------------------------------------------------------------------
#define RIPPLE_REPORTING_HTTP_BASE_SESSION_H /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <boost/asio/dispatch.hpp> #include <boost/asio/dispatch.hpp>
#include <boost/asio/spawn.hpp> #include <boost/asio/spawn.hpp>
@@ -19,14 +37,17 @@
#include <thread> #include <thread>
#include <etl/ReportingETL.h> #include <etl/ReportingETL.h>
#include <log/Logger.h>
#include <main/Build.h> #include <main/Build.h>
#include <rpc/Counters.h> #include <rpc/Counters.h>
#include <rpc/RPC.h> #include <rpc/RPC.h>
#include <rpc/WorkQueue.h> #include <rpc/WorkQueue.h>
#include <util/Profiler.h>
#include <util/Taggable.h> #include <util/Taggable.h>
#include <vector> #include <vector>
#include <webserver/DOSGuard.h> #include <webserver/DOSGuard.h>
// TODO: consider removing those - visible to anyone including this header
namespace http = boost::beast::http; namespace http = boost::beast::http;
namespace net = boost::asio; namespace net = boost::asio;
namespace ssl = boost::asio::ssl; namespace ssl = boost::asio::ssl;
@@ -95,13 +116,15 @@ class HttpBase : public util::Taggable
std::shared_ptr<ETLLoadBalancer> balancer_; std::shared_ptr<ETLLoadBalancer> balancer_;
std::shared_ptr<ReportingETL const> etl_; std::shared_ptr<ReportingETL const> etl_;
util::TagDecoratorFactory const& tagFactory_; util::TagDecoratorFactory const& tagFactory_;
DOSGuard& dosGuard_; clio::DOSGuard& dosGuard_;
RPC::Counters& counters_; RPC::Counters& counters_;
WorkQueue& workQueue_; WorkQueue& workQueue_;
send_lambda lambda_; send_lambda lambda_;
protected: protected:
clio::Logger perfLog_{"Performance"};
boost::beast::flat_buffer buffer_; boost::beast::flat_buffer buffer_;
bool upgraded_ = false;
bool bool
dead() dead()
@@ -135,8 +158,7 @@ protected:
if (!ec_ && ec != boost::asio::error::operation_aborted) if (!ec_ && ec != boost::asio::error::operation_aborted)
{ {
ec_ = ec; ec_ = ec;
BOOST_LOG_TRIVIAL(info) perfLog_.info() << tag() << ": " << what << ": " << ec.message();
<< tag() << __func__ << ": " << what << ": " << ec.message();
boost::beast::get_lowest_layer(derived().stream()) boost::beast::get_lowest_layer(derived().stream())
.socket() .socket()
.close(ec); .close(ec);
@@ -151,7 +173,7 @@ public:
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard, clio::DOSGuard& dosGuard,
RPC::Counters& counters, RPC::Counters& counters,
WorkQueue& queue, WorkQueue& queue,
boost::beast::flat_buffer buffer) boost::beast::flat_buffer buffer)
@@ -168,11 +190,18 @@ public:
, lambda_(*this) , lambda_(*this)
, buffer_(std::move(buffer)) , buffer_(std::move(buffer))
{ {
BOOST_LOG_TRIVIAL(debug) << tag() << "http session created"; perfLog_.debug() << tag() << "http session created";
} }
virtual ~HttpBase() virtual ~HttpBase()
{ {
BOOST_LOG_TRIVIAL(debug) << tag() << "http session closed"; perfLog_.debug() << tag() << "http session closed";
}
clio::DOSGuard&
dosGuard()
{
return dosGuard_;
} }
void void
@@ -209,14 +238,37 @@ public:
if (ec) if (ec)
return httpFail(ec, "read"); return httpFail(ec, "read");
auto ip = derived().ip();
if (!ip)
{
return;
}
auto const httpResponse = [&](http::status status,
std::string content_type,
std::string message) {
http::response<http::string_body> res{status, req_.version()};
res.set(
http::field::server,
"clio-server-" + Build::getClioVersionString());
res.set(http::field::content_type, content_type);
res.keep_alive(req_.keep_alive());
res.body() = std::string(message);
res.prepare_payload();
return res;
};
if (boost::beast::websocket::is_upgrade(req_)) if (boost::beast::websocket::is_upgrade(req_))
{ {
upgraded_ = true;
// Disable the timeout. // Disable the timeout.
// The websocket::stream uses its own timeout settings. // The websocket::stream uses its own timeout settings.
boost::beast::get_lowest_layer(derived().stream()).expires_never(); boost::beast::get_lowest_layer(derived().stream()).expires_never();
return make_websocket_session( return make_websocket_session(
ioc_, ioc_,
derived().release_stream(), derived().release_stream(),
derived().ip(),
std::move(req_), std::move(req_),
std::move(buffer_), std::move(buffer_),
backend_, backend_,
@@ -229,14 +281,19 @@ public:
workQueue_); workQueue_);
} }
auto ip = derived().ip(); // to avoid overwhelm work queue, the request limit check should be
// before posting to queue the web socket creation will be guarded via
// connection limit
if (!dosGuard_.request(ip.value()))
{
return lambda_(httpResponse(
http::status::service_unavailable,
"text/plain",
"Server is overloaded"));
}
if (!ip) perfLog_.debug() << tag() << "Received request from ip = " << *ip
return; << " - posting to WorkQueue";
BOOST_LOG_TRIVIAL(debug) << tag() << "http::" << __func__
<< " received request from ip = " << *ip
<< " - posting to WorkQueue";
auto session = derived().shared_from_this(); auto session = derived().shared_from_this();
@@ -256,23 +313,18 @@ public:
dosGuard_, dosGuard_,
counters_, counters_,
*ip, *ip,
session); session,
perfLog_);
}, },
dosGuard_.isWhiteListed(*ip))) dosGuard_.isWhiteListed(*ip)))
{ {
// Non-whitelist connection rejected due to full connection // Non-whitelist connection rejected due to full connection
// queue // queue
http::response<http::string_body> res{ lambda_(httpResponse(
http::status::ok, req_.version()}; http::status::ok,
res.set( "application/json",
http::field::server, boost::json::serialize(
"clio-server-" + Build::getClioVersionString()); RPC::makeError(RPC::RippledError::rpcTOO_BUSY))));
res.set(http::field::content_type, "application/json");
res.keep_alive(req_.keep_alive());
res.body() = boost::json::serialize(
RPC::makeError(RPC::RippledError::rpcTOO_BUSY));
res.prepare_payload();
lambda_(std::move(res));
} }
} }
@@ -318,10 +370,11 @@ handle_request(
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard, clio::DOSGuard& dosGuard,
RPC::Counters& counters, RPC::Counters& counters,
std::string const& ip, std::string const& ip,
std::shared_ptr<Session> http) std::shared_ptr<Session> http,
clio::Logger& perfLog)
{ {
auto const httpResponse = [&req]( auto const httpResponse = [&req](
http::status status, http::status status,
@@ -348,17 +401,11 @@ handle_request(
return send(httpResponse( return send(httpResponse(
http::status::bad_request, "text/html", "Expected a POST request")); http::status::bad_request, "text/html", "Expected a POST request"));
if (!dosGuard.isOk(ip))
return send(httpResponse(
http::status::service_unavailable,
"text/plain",
"Server is overloaded"));
try try
{ {
BOOST_LOG_TRIVIAL(debug) perfLog.debug() << http->tag()
<< http->tag() << "http received request from work queue: "
<< "http received request from work queue: " << req.body(); << req.body();
boost::json::object request; boost::json::object request;
std::string responseStr = ""; std::string responseStr = "";
@@ -405,14 +452,11 @@ handle_request(
boost::json::serialize( boost::json::serialize(
RPC::makeError(RPC::RippledError::rpcBAD_SYNTAX)))); RPC::makeError(RPC::RippledError::rpcBAD_SYNTAX))));
boost::json::object response{{"result", boost::json::object{}}}; boost::json::object response;
boost::json::object& result = response["result"].as_object(); auto [v, timeDiff] =
util::timed([&]() { return RPC::buildResponse(*context); });
auto start = std::chrono::system_clock::now(); auto us = std::chrono::duration<int, std::milli>(timeDiff);
auto v = RPC::buildResponse(*context);
auto end = std::chrono::system_clock::now();
auto us =
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
RPC::logDuration(*context, us); RPC::logDuration(*context, us);
if (auto status = std::get_if<RPC::Status>(&v)) if (auto status = std::get_if<RPC::Status>(&v))
@@ -420,10 +464,10 @@ handle_request(
counters.rpcErrored(context->method); counters.rpcErrored(context->method);
auto error = RPC::makeError(*status); auto error = RPC::makeError(*status);
error["request"] = request; error["request"] = request;
result = error; response["result"] = error;
BOOST_LOG_TRIVIAL(debug) << http->tag() << __func__ perfLog.debug()
<< " Encountered error: " << responseStr; << http->tag() << "Encountered error: " << responseStr;
} }
else else
{ {
@@ -431,10 +475,15 @@ handle_request(
// requests as successful. // requests as successful.
counters.rpcComplete(context->method, us); counters.rpcComplete(context->method, us);
result = std::get<boost::json::object>(v);
auto result = std::get<boost::json::object>(v);
if (result.contains("result") && result.at("result").is_object())
result = result.at("result").as_object();
if (!result.contains("error")) if (!result.contains("error"))
result["status"] = "success"; result["status"] = "success";
response["result"] = result;
} }
boost::json::array warnings; boost::json::array warnings;
@@ -457,8 +506,7 @@ handle_request(
} }
catch (std::exception const& e) catch (std::exception const& e)
{ {
BOOST_LOG_TRIVIAL(error) perfLog.error() << http->tag() << "Caught exception : " << e.what();
<< http->tag() << __func__ << " Caught exception : " << e.what();
return send(httpResponse( return send(httpResponse(
http::status::internal_server_error, http::status::internal_server_error,
"application/json", "application/json",
@@ -466,5 +514,3 @@ handle_request(
RPC::makeError(RPC::RippledError::rpcINTERNAL)))); RPC::makeError(RPC::RippledError::rpcINTERNAL))));
} }
} }
#endif // RIPPLE_REPORTING_HTTP_BASE_SESSION_H

View File

@@ -1,5 +1,23 @@
#ifndef RIPPLE_REPORTING_HTTP_SESSION_H //------------------------------------------------------------------------------
#define RIPPLE_REPORTING_HTTP_SESSION_H /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <webserver/HttpBase.h> #include <webserver/HttpBase.h>
@@ -13,6 +31,7 @@ class HttpSession : public HttpBase<HttpSession>,
public std::enable_shared_from_this<HttpSession> public std::enable_shared_from_this<HttpSession>
{ {
boost::beast::tcp_stream stream_; boost::beast::tcp_stream stream_;
std::optional<std::string> ip_;
public: public:
// Take ownership of the socket // Take ownership of the socket
@@ -24,7 +43,7 @@ public:
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard, clio::DOSGuard& dosGuard,
RPC::Counters& counters, RPC::Counters& counters,
WorkQueue& queue, WorkQueue& queue,
boost::beast::flat_buffer buffer) boost::beast::flat_buffer buffer)
@@ -41,6 +60,21 @@ public:
std::move(buffer)) std::move(buffer))
, stream_(std::move(socket)) , stream_(std::move(socket))
{ {
try
{
ip_ = stream_.socket().remote_endpoint().address().to_string();
}
catch (std::exception const&)
{
}
if (ip_)
HttpBase::dosGuard().increment(*ip_);
}
~HttpSession()
{
if (ip_ and not upgraded_)
HttpBase::dosGuard().decrement(*ip_);
} }
boost::beast::tcp_stream& boost::beast::tcp_stream&
@@ -57,14 +91,7 @@ public:
std::optional<std::string> std::optional<std::string>
ip() ip()
{ {
try return ip_;
{
return stream_.socket().remote_endpoint().address().to_string();
}
catch (std::exception const&)
{
return {};
}
} }
// Start the asynchronous operation // Start the asynchronous operation
@@ -91,5 +118,3 @@ public:
// At this point the connection is closed gracefully // At this point the connection is closed gracefully
} }
}; };
#endif // RIPPLE_REPORTING_HTTP_SESSION_H

View File

@@ -1,10 +1,25 @@
#ifndef LISTENER_H //------------------------------------------------------------------------------
#define LISTENER_H /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, the clio developers.
#include <boost/asio/dispatch.hpp> Permission to use, copy, modify, and distribute this software for any
#include <boost/beast/core.hpp> purpose with or without fee is hereby granted, provided that the above
#include <boost/beast/websocket.hpp> 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 <log/Logger.h>
#include <subscriptions/SubscriptionManager.h> #include <subscriptions/SubscriptionManager.h>
#include <util/Taggable.h> #include <util/Taggable.h>
#include <webserver/HttpSession.h> #include <webserver/HttpSession.h>
@@ -12,6 +27,10 @@
#include <webserver/SslHttpSession.h> #include <webserver/SslHttpSession.h>
#include <webserver/SslWsSession.h> #include <webserver/SslWsSession.h>
#include <boost/asio/dispatch.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <iostream> #include <iostream>
class SubscriptionManager; class SubscriptionManager;
@@ -23,6 +42,7 @@ class Detector
using std::enable_shared_from_this< using std::enable_shared_from_this<
Detector<PlainSession, SslSession>>::shared_from_this; Detector<PlainSession, SslSession>>::shared_from_this;
clio::Logger log_{"WebServer"};
boost::asio::io_context& ioc_; boost::asio::io_context& ioc_;
boost::beast::tcp_stream stream_; boost::beast::tcp_stream stream_;
std::optional<std::reference_wrapper<ssl::context>> ctx_; std::optional<std::reference_wrapper<ssl::context>> ctx_;
@@ -31,7 +51,7 @@ class Detector
std::shared_ptr<ETLLoadBalancer> balancer_; std::shared_ptr<ETLLoadBalancer> balancer_;
std::shared_ptr<ReportingETL const> etl_; std::shared_ptr<ReportingETL const> etl_;
util::TagDecoratorFactory const& tagFactory_; util::TagDecoratorFactory const& tagFactory_;
DOSGuard& dosGuard_; clio::DOSGuard& dosGuard_;
RPC::Counters& counters_; RPC::Counters& counters_;
WorkQueue& queue_; WorkQueue& queue_;
boost::beast::flat_buffer buffer_; boost::beast::flat_buffer buffer_;
@@ -46,7 +66,7 @@ public:
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard, clio::DOSGuard& dosGuard,
RPC::Counters& counters, RPC::Counters& counters,
WorkQueue& queue) WorkQueue& queue)
: ioc_(ioc) : ioc_(ioc)
@@ -69,8 +89,7 @@ public:
if (ec == net::ssl::error::stream_truncated) if (ec == net::ssl::error::stream_truncated)
return; return;
BOOST_LOG_TRIVIAL(info) log_.info() << "Detector failed (" << message << "): " << ec.message();
<< "Detector failed: " << message << ec.message() << std::endl;
} }
// Launch the detector // Launch the detector
@@ -137,6 +156,7 @@ void
make_websocket_session( make_websocket_session(
boost::asio::io_context& ioc, boost::asio::io_context& ioc,
boost::beast::tcp_stream stream, boost::beast::tcp_stream stream,
std::optional<std::string> const& ip,
http::request<http::string_body> req, http::request<http::string_body> req,
boost::beast::flat_buffer buffer, boost::beast::flat_buffer buffer,
std::shared_ptr<BackendInterface const> backend, std::shared_ptr<BackendInterface const> backend,
@@ -144,13 +164,14 @@ make_websocket_session(
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard, clio::DOSGuard& dosGuard,
RPC::Counters& counters, RPC::Counters& counters,
WorkQueue& queue) WorkQueue& queue)
{ {
std::make_shared<WsUpgrader>( std::make_shared<WsUpgrader>(
ioc, ioc,
std::move(stream), std::move(stream),
ip,
backend, backend,
subscriptions, subscriptions,
balancer, balancer,
@@ -168,6 +189,7 @@ void
make_websocket_session( make_websocket_session(
boost::asio::io_context& ioc, boost::asio::io_context& ioc,
boost::beast::ssl_stream<boost::beast::tcp_stream> stream, boost::beast::ssl_stream<boost::beast::tcp_stream> stream,
std::optional<std::string> const& ip,
http::request<http::string_body> req, http::request<http::string_body> req,
boost::beast::flat_buffer buffer, boost::beast::flat_buffer buffer,
std::shared_ptr<BackendInterface const> backend, std::shared_ptr<BackendInterface const> backend,
@@ -175,13 +197,14 @@ make_websocket_session(
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard, clio::DOSGuard& dosGuard,
RPC::Counters& counters, RPC::Counters& counters,
WorkQueue& queue) WorkQueue& queue)
{ {
std::make_shared<SslWsUpgrader>( std::make_shared<SslWsUpgrader>(
ioc, ioc,
std::move(stream), std::move(stream),
ip,
backend, backend,
subscriptions, subscriptions,
balancer, balancer,
@@ -202,6 +225,7 @@ class Listener
using std::enable_shared_from_this< using std::enable_shared_from_this<
Listener<PlainSession, SslSession>>::shared_from_this; Listener<PlainSession, SslSession>>::shared_from_this;
clio::Logger log_{"WebServer"};
boost::asio::io_context& ioc_; boost::asio::io_context& ioc_;
std::optional<std::reference_wrapper<ssl::context>> ctx_; std::optional<std::reference_wrapper<ssl::context>> ctx_;
tcp::acceptor acceptor_; tcp::acceptor acceptor_;
@@ -210,7 +234,7 @@ class Listener
std::shared_ptr<ETLLoadBalancer> balancer_; std::shared_ptr<ETLLoadBalancer> balancer_;
std::shared_ptr<ReportingETL const> etl_; std::shared_ptr<ReportingETL const> etl_;
util::TagDecoratorFactory tagFactory_; util::TagDecoratorFactory tagFactory_;
DOSGuard& dosGuard_; clio::DOSGuard& dosGuard_;
WorkQueue queue_; WorkQueue queue_;
RPC::Counters counters_; RPC::Counters counters_;
@@ -226,7 +250,7 @@ public:
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory tagFactory, util::TagDecoratorFactory tagFactory,
DOSGuard& dosGuard) clio::DOSGuard& dosGuard)
: ioc_(ioc) : ioc_(ioc)
, ctx_(ctx) , ctx_(ctx)
, acceptor_(net::make_strand(ioc)) , acceptor_(net::make_strand(ioc))
@@ -255,9 +279,8 @@ public:
acceptor_.bind(endpoint, ec); acceptor_.bind(endpoint, ec);
if (ec) if (ec)
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "Failed to bind to endpoint: " << endpoint
<< "Failed to bind to endpoint: " << endpoint << ". message: " << ec.message();
<< ". message: " << ec.message();
throw std::runtime_error("Failed to bind to specified endpoint"); throw std::runtime_error("Failed to bind to specified endpoint");
} }
@@ -265,9 +288,8 @@ public:
acceptor_.listen(net::socket_base::max_listen_connections, ec); acceptor_.listen(net::socket_base::max_listen_connections, ec);
if (ec) if (ec)
{ {
BOOST_LOG_TRIVIAL(error) log_.error() << "Failed to listen at endpoint: " << endpoint
<< "Failed to listen at endpoint: " << endpoint << ". message: " << ec.message();
<< ". message: " << ec.message();
throw std::runtime_error("Failed to listen at specified endpoint"); throw std::runtime_error("Failed to listen at specified endpoint");
} }
} }
@@ -334,8 +356,9 @@ make_HttpServer(
std::shared_ptr<SubscriptionManager> subscriptions, std::shared_ptr<SubscriptionManager> subscriptions,
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
DOSGuard& dosGuard) clio::DOSGuard& dosGuard)
{ {
static clio::Logger log{"WebServer"};
if (!config.contains("server")) if (!config.contains("server"))
return nullptr; return nullptr;
@@ -348,8 +371,8 @@ make_HttpServer(
auto const maxQueueSize = auto const maxQueueSize =
serverConfig.valueOr<uint32_t>("max_queue_size", 0); // 0 is no limit serverConfig.valueOr<uint32_t>("max_queue_size", 0); // 0 is no limit
BOOST_LOG_TRIVIAL(info) << __func__ << " Number of workers = " << numThreads log.info() << "Number of workers = " << numThreads
<< ". Max queue size = " << maxQueueSize; << ". Max queue size = " << maxQueueSize;
auto server = std::make_shared<HttpServer>( auto server = std::make_shared<HttpServer>(
ioc, ioc,
@@ -368,5 +391,3 @@ make_HttpServer(
return server; return server;
} }
} // namespace Server } // namespace Server
#endif // LISTENER_H

View File

@@ -1,5 +1,23 @@
#ifndef RIPPLE_REPORTING_WS_SESSION_H //------------------------------------------------------------------------------
#define RIPPLE_REPORTING_WS_SESSION_H /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <boost/asio/dispatch.hpp> #include <boost/asio/dispatch.hpp>
#include <boost/beast/core.hpp> #include <boost/beast/core.hpp>
@@ -32,17 +50,19 @@ public:
explicit PlainWsSession( explicit PlainWsSession(
boost::asio::io_context& ioc, boost::asio::io_context& ioc,
boost::asio::ip::tcp::socket&& socket, boost::asio::ip::tcp::socket&& socket,
std::optional<std::string> ip,
std::shared_ptr<BackendInterface const> backend, std::shared_ptr<BackendInterface const> backend,
std::shared_ptr<SubscriptionManager> subscriptions, std::shared_ptr<SubscriptionManager> subscriptions,
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard, clio::DOSGuard& dosGuard,
RPC::Counters& counters, RPC::Counters& counters,
WorkQueue& queue, WorkQueue& queue,
boost::beast::flat_buffer&& buffer) boost::beast::flat_buffer&& buffer)
: WsSession( : WsSession(
ioc, ioc,
ip,
backend, backend,
subscriptions, subscriptions,
balancer, balancer,
@@ -65,19 +85,7 @@ public:
std::optional<std::string> std::optional<std::string>
ip() ip()
{ {
try return ip_;
{
return ws()
.next_layer()
.socket()
.remote_endpoint()
.address()
.to_string();
}
catch (std::exception const&)
{
return {};
}
} }
~PlainWsSession() = default; ~PlainWsSession() = default;
@@ -94,21 +102,23 @@ class WsUpgrader : public std::enable_shared_from_this<WsUpgrader>
std::shared_ptr<ETLLoadBalancer> balancer_; std::shared_ptr<ETLLoadBalancer> balancer_;
std::shared_ptr<ReportingETL const> etl_; std::shared_ptr<ReportingETL const> etl_;
util::TagDecoratorFactory const& tagFactory_; util::TagDecoratorFactory const& tagFactory_;
DOSGuard& dosGuard_; clio::DOSGuard& dosGuard_;
RPC::Counters& counters_; RPC::Counters& counters_;
WorkQueue& queue_; WorkQueue& queue_;
http::request<http::string_body> req_; http::request<http::string_body> req_;
std::optional<std::string> ip_;
public: public:
WsUpgrader( WsUpgrader(
boost::asio::io_context& ioc, boost::asio::io_context& ioc,
boost::asio::ip::tcp::socket&& socket, boost::asio::ip::tcp::socket&& socket,
std::optional<std::string> ip,
std::shared_ptr<BackendInterface const> backend, std::shared_ptr<BackendInterface const> backend,
std::shared_ptr<SubscriptionManager> subscriptions, std::shared_ptr<SubscriptionManager> subscriptions,
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard, clio::DOSGuard& dosGuard,
RPC::Counters& counters, RPC::Counters& counters,
WorkQueue& queue, WorkQueue& queue,
boost::beast::flat_buffer&& b) boost::beast::flat_buffer&& b)
@@ -123,17 +133,19 @@ public:
, dosGuard_(dosGuard) , dosGuard_(dosGuard)
, counters_(counters) , counters_(counters)
, queue_(queue) , queue_(queue)
, ip_(ip)
{ {
} }
WsUpgrader( WsUpgrader(
boost::asio::io_context& ioc, boost::asio::io_context& ioc,
boost::beast::tcp_stream&& stream, boost::beast::tcp_stream&& stream,
std::optional<std::string> ip,
std::shared_ptr<BackendInterface const> backend, std::shared_ptr<BackendInterface const> backend,
std::shared_ptr<SubscriptionManager> subscriptions, std::shared_ptr<SubscriptionManager> subscriptions,
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard, clio::DOSGuard& dosGuard,
RPC::Counters& counters, RPC::Counters& counters,
WorkQueue& queue, WorkQueue& queue,
boost::beast::flat_buffer&& b, boost::beast::flat_buffer&& b,
@@ -150,6 +162,7 @@ public:
, counters_(counters) , counters_(counters)
, queue_(queue) , queue_(queue)
, req_(std::move(req)) , req_(std::move(req))
, ip_(ip)
{ {
} }
@@ -198,6 +211,7 @@ private:
std::make_shared<PlainWsSession>( std::make_shared<PlainWsSession>(
ioc_, ioc_,
http_.release_socket(), http_.release_socket(),
ip_,
backend_, backend_,
subscriptions_, subscriptions_,
balancer_, balancer_,
@@ -210,5 +224,3 @@ private:
->run(std::move(req_)); ->run(std::move(req_));
} }
}; };
#endif // RIPPLE_REPORTING_WS_SESSION_H

View File

@@ -1,24 +1,23 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of rippled: https://github.com/ripple/rippled This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2021 Ripple Labs Inc. Copyright (c) 2022, the clio developers.
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies. copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
//============================================================================== //==============================================================================
#ifndef REPORTING_SSL_H #pragma once
#define REPORTING_SSL_H
#include <boost/asio/ssl.hpp> #include <boost/asio/ssl.hpp>
@@ -62,5 +61,3 @@ parse_certs(const char* certFilename, const char* keyFilename)
return ctx; return ctx;
} }
#endif // REPORTING_SSL_H

View File

@@ -1,5 +1,23 @@
#ifndef RIPPLE_REPORTING_HTTPS_SESSION_H //------------------------------------------------------------------------------
#define RIPPLE_REPORTING_HTTPS_SESSION_H /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <webserver/HttpBase.h> #include <webserver/HttpBase.h>
@@ -13,6 +31,7 @@ class SslHttpSession : public HttpBase<SslHttpSession>,
public std::enable_shared_from_this<SslHttpSession> public std::enable_shared_from_this<SslHttpSession>
{ {
boost::beast::ssl_stream<boost::beast::tcp_stream> stream_; boost::beast::ssl_stream<boost::beast::tcp_stream> stream_;
std::optional<std::string> ip_;
public: public:
// Take ownership of the socket // Take ownership of the socket
@@ -25,7 +44,7 @@ public:
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard, clio::DOSGuard& dosGuard,
RPC::Counters& counters, RPC::Counters& counters,
WorkQueue& queue, WorkQueue& queue,
boost::beast::flat_buffer buffer) boost::beast::flat_buffer buffer)
@@ -42,6 +61,25 @@ public:
std::move(buffer)) std::move(buffer))
, stream_(std::move(socket), ctx) , stream_(std::move(socket), ctx)
{ {
try
{
ip_ = stream_.next_layer()
.socket()
.remote_endpoint()
.address()
.to_string();
}
catch (std::exception const&)
{
}
if (ip_)
HttpBase::dosGuard().increment(*ip_);
}
~SslHttpSession()
{
if (ip_ and not upgraded_)
HttpBase::dosGuard().decrement(*ip_);
} }
boost::beast::ssl_stream<boost::beast::tcp_stream>& boost::beast::ssl_stream<boost::beast::tcp_stream>&
@@ -58,18 +96,7 @@ public:
std::optional<std::string> std::optional<std::string>
ip() ip()
{ {
try return ip_;
{
return stream_.next_layer()
.socket()
.remote_endpoint()
.address()
.to_string();
}
catch (std::exception const&)
{
return {};
}
} }
// Start the asynchronous operation // Start the asynchronous operation
@@ -126,5 +153,3 @@ public:
// At this point the connection is closed gracefully // At this point the connection is closed gracefully
} }
}; };
#endif // RIPPLE_REPORTING_HTTPS_SESSION_H

View File

@@ -1,5 +1,23 @@
#ifndef RIPPLE_REPORTING_SSL_WS_SESSION_H //------------------------------------------------------------------------------
#define RIPPLE_REPORTING_SSL_WS_SESSION_H /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <boost/asio/dispatch.hpp> #include <boost/asio/dispatch.hpp>
#include <boost/beast/core.hpp> #include <boost/beast/core.hpp>
@@ -30,17 +48,19 @@ public:
explicit SslWsSession( explicit SslWsSession(
boost::asio::io_context& ioc, boost::asio::io_context& ioc,
boost::beast::ssl_stream<boost::beast::tcp_stream>&& stream, boost::beast::ssl_stream<boost::beast::tcp_stream>&& stream,
std::optional<std::string> ip,
std::shared_ptr<BackendInterface const> backend, std::shared_ptr<BackendInterface const> backend,
std::shared_ptr<SubscriptionManager> subscriptions, std::shared_ptr<SubscriptionManager> subscriptions,
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard, clio::DOSGuard& dosGuard,
RPC::Counters& counters, RPC::Counters& counters,
WorkQueue& queue, WorkQueue& queue,
boost::beast::flat_buffer&& b) boost::beast::flat_buffer&& b)
: WsSession( : WsSession(
ioc, ioc,
ip,
backend, backend,
subscriptions, subscriptions,
balancer, balancer,
@@ -63,20 +83,7 @@ public:
std::optional<std::string> std::optional<std::string>
ip() ip()
{ {
try return ip_;
{
return ws()
.next_layer()
.next_layer()
.socket()
.remote_endpoint()
.address()
.to_string();
}
catch (std::exception const&)
{
return {};
}
} }
}; };
@@ -86,12 +93,13 @@ class SslWsUpgrader : public std::enable_shared_from_this<SslWsUpgrader>
boost::beast::ssl_stream<boost::beast::tcp_stream> https_; boost::beast::ssl_stream<boost::beast::tcp_stream> https_;
boost::optional<http::request_parser<http::string_body>> parser_; boost::optional<http::request_parser<http::string_body>> parser_;
boost::beast::flat_buffer buffer_; boost::beast::flat_buffer buffer_;
std::optional<std::string> ip_;
std::shared_ptr<BackendInterface const> backend_; std::shared_ptr<BackendInterface const> backend_;
std::shared_ptr<SubscriptionManager> subscriptions_; std::shared_ptr<SubscriptionManager> subscriptions_;
std::shared_ptr<ETLLoadBalancer> balancer_; std::shared_ptr<ETLLoadBalancer> balancer_;
std::shared_ptr<ReportingETL const> etl_; std::shared_ptr<ReportingETL const> etl_;
util::TagDecoratorFactory const& tagFactory_; util::TagDecoratorFactory const& tagFactory_;
DOSGuard& dosGuard_; clio::DOSGuard& dosGuard_;
RPC::Counters& counters_; RPC::Counters& counters_;
WorkQueue& queue_; WorkQueue& queue_;
http::request<http::string_body> req_; http::request<http::string_body> req_;
@@ -99,6 +107,7 @@ class SslWsUpgrader : public std::enable_shared_from_this<SslWsUpgrader>
public: public:
SslWsUpgrader( SslWsUpgrader(
boost::asio::io_context& ioc, boost::asio::io_context& ioc,
std::optional<std::string> ip,
boost::asio::ip::tcp::socket&& socket, boost::asio::ip::tcp::socket&& socket,
ssl::context& ctx, ssl::context& ctx,
std::shared_ptr<BackendInterface const> backend, std::shared_ptr<BackendInterface const> backend,
@@ -106,13 +115,14 @@ public:
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard, clio::DOSGuard& dosGuard,
RPC::Counters& counters, RPC::Counters& counters,
WorkQueue& queue, WorkQueue& queue,
boost::beast::flat_buffer&& b) boost::beast::flat_buffer&& b)
: ioc_(ioc) : ioc_(ioc)
, https_(std::move(socket), ctx) , https_(std::move(socket), ctx)
, buffer_(std::move(b)) , buffer_(std::move(b))
, ip_(ip)
, backend_(backend) , backend_(backend)
, subscriptions_(subscriptions) , subscriptions_(subscriptions)
, balancer_(balancer) , balancer_(balancer)
@@ -126,12 +136,13 @@ public:
SslWsUpgrader( SslWsUpgrader(
boost::asio::io_context& ioc, boost::asio::io_context& ioc,
boost::beast::ssl_stream<boost::beast::tcp_stream> stream, boost::beast::ssl_stream<boost::beast::tcp_stream> stream,
std::optional<std::string> ip,
std::shared_ptr<BackendInterface const> backend, std::shared_ptr<BackendInterface const> backend,
std::shared_ptr<SubscriptionManager> subscriptions, std::shared_ptr<SubscriptionManager> subscriptions,
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard, clio::DOSGuard& dosGuard,
RPC::Counters& counters, RPC::Counters& counters,
WorkQueue& queue, WorkQueue& queue,
boost::beast::flat_buffer&& b, boost::beast::flat_buffer&& b,
@@ -139,6 +150,7 @@ public:
: ioc_(ioc) : ioc_(ioc)
, https_(std::move(stream)) , https_(std::move(stream))
, buffer_(std::move(b)) , buffer_(std::move(b))
, ip_(ip)
, backend_(backend) , backend_(backend)
, subscriptions_(subscriptions) , subscriptions_(subscriptions)
, balancer_(balancer) , balancer_(balancer)
@@ -211,6 +223,7 @@ private:
std::make_shared<SslWsSession>( std::make_shared<SslWsSession>(
ioc_, ioc_,
std::move(https_), std::move(https_),
ip_,
backend_, backend_,
subscriptions_, subscriptions_,
balancer_, balancer_,
@@ -223,5 +236,3 @@ private:
->run(std::move(req_)); ->run(std::move(req_));
} }
}; };
#endif // RIPPLE_REPORTING_SSL_WS_SESSION_H

View File

@@ -1,5 +1,36 @@
#ifndef RIPPLE_REPORTING_WS_BASE_SESSION_H //------------------------------------------------------------------------------
#define RIPPLE_REPORTING_WS_BASE_SESSION_H /*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include <backend/BackendInterface.h>
#include <etl/ETLSource.h>
#include <etl/ReportingETL.h>
#include <log/Logger.h>
#include <rpc/Counters.h>
#include <rpc/RPC.h>
#include <rpc/WorkQueue.h>
#include <subscriptions/Message.h>
#include <subscriptions/SubscriptionManager.h>
#include <util/Profiler.h>
#include <util/Taggable.h>
#include <webserver/DOSGuard.h>
#include <boost/beast/core.hpp> #include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp> #include <boost/beast/websocket.hpp>
@@ -7,17 +38,7 @@
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <backend/BackendInterface.h> // TODO: Consider removing these. Visible to anyone including this header.
#include <etl/ETLSource.h>
#include <etl/ReportingETL.h>
#include <rpc/Counters.h>
#include <rpc/RPC.h>
#include <rpc/WorkQueue.h>
#include <subscriptions/Message.h>
#include <subscriptions/SubscriptionManager.h>
#include <util/Taggable.h>
#include <webserver/DOSGuard.h>
namespace http = boost::beast::http; namespace http = boost::beast::http;
namespace net = boost::asio; namespace net = boost::asio;
namespace ssl = boost::asio::ssl; namespace ssl = boost::asio::ssl;
@@ -27,8 +48,8 @@ using tcp = boost::asio::ip::tcp;
inline void inline void
logError(boost::beast::error_code ec, char const* what) logError(boost::beast::error_code ec, char const* what)
{ {
BOOST_LOG_TRIVIAL(debug) static clio::Logger log{"WebServer"};
<< __func__ << " : " << what << ": " << ec.message() << "\n"; log.debug() << what << ": " << ec.message() << "\n";
} }
inline boost::json::object inline boost::json::object
@@ -38,7 +59,6 @@ getDefaultWsResponse(boost::json::value const& id)
if (!id.is_null()) if (!id.is_null())
defaultResp["id"] = id; defaultResp["id"] = id;
defaultResp["result"] = boost::json::object_kind;
defaultResp["status"] = "success"; defaultResp["status"] = "success";
defaultResp["type"] = "response"; defaultResp["type"] = "response";
@@ -48,6 +68,8 @@ getDefaultWsResponse(boost::json::value const& id)
class WsBase : public util::Taggable class WsBase : public util::Taggable
{ {
protected: protected:
clio::Logger log_{"WebServer"};
clio::Logger perfLog_{"Performance"};
boost::system::error_code ec_; boost::system::error_code ec_;
public: public:
@@ -100,7 +122,7 @@ class WsSession : public WsBase,
std::shared_ptr<ETLLoadBalancer> balancer_; std::shared_ptr<ETLLoadBalancer> balancer_;
std::shared_ptr<ReportingETL const> etl_; std::shared_ptr<ReportingETL const> etl_;
util::TagDecoratorFactory const& tagFactory_; util::TagDecoratorFactory const& tagFactory_;
DOSGuard& dosGuard_; clio::DOSGuard& dosGuard_;
RPC::Counters& counters_; RPC::Counters& counters_;
WorkQueue& queue_; WorkQueue& queue_;
std::mutex mtx_; std::mutex mtx_;
@@ -108,14 +130,16 @@ class WsSession : public WsBase,
bool sending_ = false; bool sending_ = false;
std::queue<std::shared_ptr<Message>> messages_; std::queue<std::shared_ptr<Message>> messages_;
protected:
std::optional<std::string> ip_;
void void
wsFail(boost::beast::error_code ec, char const* what) wsFail(boost::beast::error_code ec, char const* what)
{ {
if (!ec_ && ec != boost::asio::error::operation_aborted) if (!ec_ && ec != boost::asio::error::operation_aborted)
{ {
ec_ = ec; ec_ = ec;
BOOST_LOG_TRIVIAL(info) perfLog_.info() << tag() << ": " << what << ": " << ec.message();
<< tag() << __func__ << ": " << what << ": " << ec.message();
boost::beast::get_lowest_layer(derived().ws()).socket().close(ec); boost::beast::get_lowest_layer(derived().ws()).socket().close(ec);
if (auto manager = subscriptions_.lock(); manager) if (auto manager = subscriptions_.lock(); manager)
@@ -126,12 +150,13 @@ class WsSession : public WsBase,
public: public:
explicit WsSession( explicit WsSession(
boost::asio::io_context& ioc, boost::asio::io_context& ioc,
std::optional<std::string> ip,
std::shared_ptr<BackendInterface const> backend, std::shared_ptr<BackendInterface const> backend,
std::shared_ptr<SubscriptionManager> subscriptions, std::shared_ptr<SubscriptionManager> subscriptions,
std::shared_ptr<ETLLoadBalancer> balancer, std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl, std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory, util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard, clio::DOSGuard& dosGuard,
RPC::Counters& counters, RPC::Counters& counters,
WorkQueue& queue, WorkQueue& queue,
boost::beast::flat_buffer&& buffer) boost::beast::flat_buffer&& buffer)
@@ -146,12 +171,16 @@ public:
, dosGuard_(dosGuard) , dosGuard_(dosGuard)
, counters_(counters) , counters_(counters)
, queue_(queue) , queue_(queue)
, ip_(ip)
{ {
BOOST_LOG_TRIVIAL(info) << tag() << "session created"; perfLog_.info() << tag() << "session created";
} }
virtual ~WsSession() virtual ~WsSession()
{ {
BOOST_LOG_TRIVIAL(info) << tag() << "session closed"; perfLog_.info() << tag() << "session closed";
if (ip_)
dosGuard_.decrement(*ip_);
} }
// Access the derived class, this is part of // Access the derived class, this is part of
@@ -244,7 +273,7 @@ public:
if (ec) if (ec)
return wsFail(ec, "accept"); return wsFail(ec, "accept");
BOOST_LOG_TRIVIAL(info) << tag() << "accepting new connection"; perfLog_.info() << tag() << "accepting new connection";
// Read a message // Read a message
do_read(); do_read();
@@ -287,7 +316,7 @@ public:
try try
{ {
BOOST_LOG_TRIVIAL(debug) perfLog_.debug()
<< tag() << "ws received request from work queue : " << request; << tag() << "ws received request from work queue : " << request;
auto range = backend_->fetchLedgerRange(); auto range = backend_->fetchLedgerRange();
@@ -309,18 +338,16 @@ public:
if (!context) if (!context)
{ {
BOOST_LOG_TRIVIAL(warning) perfLog_.warn() << tag() << "Could not create RPC context";
<< tag() << " could not create RPC context";
return sendError(RPC::RippledError::rpcBAD_SYNTAX); return sendError(RPC::RippledError::rpcBAD_SYNTAX);
} }
response = getDefaultWsResponse(id); response = getDefaultWsResponse(id);
auto start = std::chrono::system_clock::now(); auto [v, timeDiff] =
auto v = RPC::buildResponse(*context); util::timed([&]() { return RPC::buildResponse(*context); });
auto end = std::chrono::system_clock::now();
auto us = std::chrono::duration_cast<std::chrono::microseconds>( auto us = std::chrono::duration<int, std::milli>(timeDiff);
end - start);
logDuration(*context, us); logDuration(*context, us);
if (auto status = std::get_if<RPC::Status>(&v)) if (auto status = std::get_if<RPC::Status>(&v))
@@ -339,13 +366,23 @@ public:
{ {
counters_.rpcComplete(context->method, us); counters_.rpcComplete(context->method, us);
response["result"] = std::get<boost::json::object>(v); auto const& result = std::get<boost::json::object>(v);
auto const isForwarded = result.contains("forwarded") &&
result.at("forwarded").is_bool() &&
result.at("forwarded").as_bool();
// if the result is forwarded - just use it as is
// but keep all default fields in the response too.
if (isForwarded)
for (auto const& [k, v] : result)
response.insert_or_assign(k, v);
else
response["result"] = result;
} }
} }
catch (std::exception const& e) catch (std::exception const& e)
{ {
BOOST_LOG_TRIVIAL(error) perfLog_.error() << tag() << "Caught exception : " << e.what();
<< tag() << __func__ << " caught exception : " << e.what();
return sendError(RPC::RippledError::rpcINTERNAL); return sendError(RPC::RippledError::rpcINTERNAL);
} }
@@ -385,8 +422,7 @@ public:
if (!ip) if (!ip)
return; return;
BOOST_LOG_TRIVIAL(info) << tag() << "ws::" << __func__ perfLog_.info() << tag() << "Received request from ip = " << *ip;
<< " received request from ip = " << *ip;
auto sendError = [this, ip]( auto sendError = [this, ip](
auto error, auto error,
@@ -399,7 +435,7 @@ public:
e["request"] = request; e["request"] = request;
auto responseStr = boost::json::serialize(e); auto responseStr = boost::json::serialize(e);
BOOST_LOG_TRIVIAL(trace) << __func__ << " : " << responseStr; log_.trace() << responseStr;
dosGuard_.add(*ip, responseStr.size()); dosGuard_.add(*ip, responseStr.size());
send(std::move(responseStr)); send(std::move(responseStr));
}; };
@@ -416,21 +452,23 @@ public:
}(std::move(msg)); }(std::move(msg));
boost::json::object request; boost::json::object request;
if (!raw.is_object()) // dosGuard served request++ and check ip address
return sendError( // dosGuard should check before any request, even invalid request
RPC::RippledError::rpcINVALID_PARAMS, nullptr, request); if (!dosGuard_.request(*ip))
request = raw.as_object();
auto id = request.contains("id") ? request.at("id") : nullptr;
if (!dosGuard_.isOk(*ip))
{ {
sendError(RPC::RippledError::rpcSLOW_DOWN, id, request); sendError(RPC::RippledError::rpcSLOW_DOWN, nullptr, request);
}
else if (!raw.is_object())
{
// handle invalid request and async read again
sendError(RPC::RippledError::rpcINVALID_PARAMS, nullptr, request);
} }
else else
{ {
BOOST_LOG_TRIVIAL(debug) request = raw.as_object();
<< tag() << __func__ << " adding to work queue";
auto id = request.contains("id") ? request.at("id") : nullptr;
perfLog_.debug() << tag() << "Adding to work queue";
if (!queue_.postCoro( if (!queue_.postCoro(
[shared_this = shared_from_this(), [shared_this = shared_from_this(),
@@ -445,5 +483,3 @@ public:
do_read(); do_read();
} }
}; };
#endif // RIPPLE_REPORTING_WS_BASE_SESSION_H

View File

@@ -1,17 +1,42 @@
#include <algorithm> //------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <backend/BackendFactory.h>
#include <backend/BackendInterface.h>
#include <backend/DBHelpers.h> #include <backend/DBHelpers.h>
#include <config/Config.h>
#include <etl/ReportingETL.h> #include <etl/ReportingETL.h>
#include <gtest/gtest.h> #include <log/Logger.h>
#include <rpc/RPCHelpers.h> #include <rpc/RPCHelpers.h>
#include <util/Fixtures.h>
#include <boost/log/core.hpp> #include <boost/log/core.hpp>
#include <boost/log/expressions.hpp> #include <boost/log/expressions.hpp>
#include <boost/log/trivial.hpp> #include <gtest/gtest.h>
#include <backend/BackendFactory.h>
#include <backend/BackendInterface.h>
#include <config/Config.h>
TEST(BackendTest, Basic) #include <algorithm>
class BackendTest : public NoLoggerFixture
{
};
TEST_F(BackendTest, Basic)
{ {
boost::asio::io_context ioc; boost::asio::io_context ioc;
std::optional<boost::asio::io_context::work> work; std::optional<boost::asio::io_context::work> work;
@@ -21,7 +46,7 @@ TEST(BackendTest, Basic)
boost::asio::spawn( boost::asio::spawn(
ioc, [&done, &work, &ioc](boost::asio::yield_context yield) { ioc, [&done, &work, &ioc](boost::asio::yield_context yield) {
boost::log::core::get()->set_filter( boost::log::core::get()->set_filter(
boost::log::trivial::severity >= boost::log::trivial::warning); clio::log_severity >= clio::Severity::WRN);
std::string keyspace = "clio_test_" + std::string keyspace = "clio_test_" +
std::to_string(std::chrono::system_clock::now() std::to_string(std::chrono::system_clock::now()
.time_since_epoch() .time_since_epoch()
@@ -41,7 +66,6 @@ TEST(BackendTest, Basic)
std::vector<boost::json::object> configs = {cassandraConfig}; std::vector<boost::json::object> configs = {cassandraConfig};
for (auto& config : configs) for (auto& config : configs)
{ {
std::cout << keyspace << std::endl;
auto backend = Backend::make_Backend(ioc, clio::Config{config}); auto backend = Backend::make_Backend(ioc, clio::Config{config});
std::string rawHeader = std::string rawHeader =
@@ -107,10 +131,8 @@ TEST(BackendTest, Basic)
} }
{ {
std::cout << "fetching ledger by sequence" << std::endl;
auto retLgr = auto retLgr =
backend->fetchLedgerBySequence(lgrInfo.seq, yield); backend->fetchLedgerBySequence(lgrInfo.seq, yield);
std::cout << "fetched ledger by sequence" << std::endl;
ASSERT_TRUE(retLgr.has_value()); ASSERT_TRUE(retLgr.has_value());
EXPECT_EQ(retLgr->seq, lgrInfo.seq); EXPECT_EQ(retLgr->seq, lgrInfo.seq);
EXPECT_EQ( EXPECT_EQ(
@@ -147,10 +169,8 @@ TEST(BackendTest, Basic)
EXPECT_EQ(seq, lgrInfoNext.seq); EXPECT_EQ(seq, lgrInfoNext.seq);
} }
{ {
std::cout << "fetching ledger by sequence" << std::endl;
auto retLgr = auto retLgr =
backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); backend->fetchLedgerBySequence(lgrInfoNext.seq, yield);
std::cout << "fetched ledger by sequence" << std::endl;
EXPECT_TRUE(retLgr.has_value()); EXPECT_TRUE(retLgr.has_value());
EXPECT_EQ(retLgr->seq, lgrInfoNext.seq); EXPECT_EQ(retLgr->seq, lgrInfoNext.seq);
EXPECT_EQ( EXPECT_EQ(
@@ -802,9 +822,6 @@ TEST(BackendTest, Basic)
auto objs, auto objs,
auto accountTx, auto accountTx,
auto state) { auto state) {
std::cout
<< "writing ledger = " << std::to_string(lgrInfo.seq)
<< std::endl;
backend->startWrites(); backend->startWrites();
backend->writeLedger( backend->writeLedger(
@@ -978,11 +995,6 @@ TEST(BackendTest, Basic)
uint32_t limit = 10; uint32_t limit = 10;
page = backend->fetchLedgerPage( page = backend->fetchLedgerPage(
page.cursor, seq, limit, false, yield); page.cursor, seq, limit, false, yield);
std::cout << "fetched a page " << page.objects.size()
<< std::endl;
if (page.cursor)
std::cout << ripple::strHex(*page.cursor)
<< std::endl;
// if (page.cursor) // if (page.cursor)
// EXPECT_EQ(page.objects.size(), limit); // EXPECT_EQ(page.objects.size(), limit);
retObjs.insert( retObjs.insert(
@@ -1007,8 +1019,7 @@ TEST(BackendTest, Basic)
} }
} }
if (found != (obj.second.size() != 0)) if (found != (obj.second.size() != 0))
std::cout << ripple::strHex(obj.first) << std::endl; ASSERT_EQ(found, obj.second.size() != 0);
ASSERT_EQ(found, obj.second.size() != 0);
} }
}; };
@@ -1148,15 +1159,12 @@ TEST(BackendTest, Basic)
for (auto [seq, diff] : state) for (auto [seq, diff] : state)
{ {
std::cout << "flatteneing" << std::endl;
auto flat = flatten(seq); auto flat = flatten(seq);
std::cout << "flattened" << std::endl;
checkLedger( checkLedger(
lgrInfos[seq], lgrInfos[seq],
allTxns[seq], allTxns[seq],
flat, flat,
flattenAccountTx(seq)); flattenAccountTx(seq));
std::cout << "checked" << std::endl;
} }
} }
@@ -1168,11 +1176,11 @@ TEST(BackendTest, Basic)
EXPECT_EQ(done, true); EXPECT_EQ(done, true);
} }
TEST(Backend, cache) TEST_F(BackendTest, cache)
{ {
using namespace Backend; using namespace Backend;
boost::log::core::get()->set_filter( boost::log::core::get()->set_filter(
boost::log::trivial::severity >= boost::log::trivial::warning); clio::log_severity >= clio::Severity::WRN);
SimpleCache cache; SimpleCache cache;
ASSERT_FALSE(cache.isFull()); ASSERT_FALSE(cache.isFull());
cache.setFull(); cache.setFull();
@@ -1410,11 +1418,11 @@ TEST(Backend, cache)
} }
} }
TEST(Backend, cacheBackground) TEST_F(BackendTest, cacheBackground)
{ {
using namespace Backend; using namespace Backend;
boost::log::core::get()->set_filter( boost::log::core::get()->set_filter(
boost::log::trivial::severity >= boost::log::trivial::warning); clio::log_severity >= clio::Severity::WRN);
SimpleCache cache; SimpleCache cache;
ASSERT_FALSE(cache.isFull()); ASSERT_FALSE(cache.isFull());
ASSERT_EQ(cache.size(), 0); ASSERT_EQ(cache.size(), 0);
@@ -1814,7 +1822,7 @@ TEST(Backend, cacheBackground)
ASSERT_EQ(idx, allObjs.size()); ASSERT_EQ(idx, allObjs.size());
} }
TEST(Backend, cacheIntegration) TEST_F(BackendTest, cacheIntegration)
{ {
boost::asio::io_context ioc; boost::asio::io_context ioc;
std::optional<boost::asio::io_context::work> work; std::optional<boost::asio::io_context::work> work;
@@ -1824,7 +1832,7 @@ TEST(Backend, cacheIntegration)
boost::asio::spawn( boost::asio::spawn(
ioc, [&ioc, &done, &work](boost::asio::yield_context yield) { ioc, [&ioc, &done, &work](boost::asio::yield_context yield) {
boost::log::core::get()->set_filter( boost::log::core::get()->set_filter(
boost::log::trivial::severity >= boost::log::trivial::warning); clio::log_severity >= clio::Severity::WRN);
std::string keyspace = "clio_test_" + std::string keyspace = "clio_test_" +
std::to_string(std::chrono::system_clock::now() std::to_string(std::chrono::system_clock::now()
.time_since_epoch() .time_since_epoch()
@@ -1844,7 +1852,6 @@ TEST(Backend, cacheIntegration)
std::vector<boost::json::object> configs = {cassandraConfig}; std::vector<boost::json::object> configs = {cassandraConfig};
for (auto& config : configs) for (auto& config : configs)
{ {
std::cout << keyspace << std::endl;
auto backend = Backend::make_Backend(ioc, clio::Config{config}); auto backend = Backend::make_Backend(ioc, clio::Config{config});
backend->cache().setFull(); backend->cache().setFull();
@@ -1927,10 +1934,8 @@ TEST(Backend, cacheIntegration)
} }
{ {
std::cout << "fetching ledger by sequence" << std::endl;
auto retLgr = auto retLgr =
backend->fetchLedgerBySequence(lgrInfo.seq, yield); backend->fetchLedgerBySequence(lgrInfo.seq, yield);
std::cout << "fetched ledger by sequence" << std::endl;
ASSERT_TRUE(retLgr.has_value()); ASSERT_TRUE(retLgr.has_value());
EXPECT_EQ(retLgr->seq, lgrInfo.seq); EXPECT_EQ(retLgr->seq, lgrInfo.seq);
EXPECT_EQ( EXPECT_EQ(
@@ -1967,10 +1972,8 @@ TEST(Backend, cacheIntegration)
EXPECT_EQ(seq, lgrInfoNext.seq); EXPECT_EQ(seq, lgrInfoNext.seq);
} }
{ {
std::cout << "fetching ledger by sequence" << std::endl;
auto retLgr = auto retLgr =
backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); backend->fetchLedgerBySequence(lgrInfoNext.seq, yield);
std::cout << "fetched ledger by sequence" << std::endl;
EXPECT_TRUE(retLgr.has_value()); EXPECT_TRUE(retLgr.has_value());
EXPECT_EQ(retLgr->seq, lgrInfoNext.seq); EXPECT_EQ(retLgr->seq, lgrInfoNext.seq);
EXPECT_EQ( EXPECT_EQ(
@@ -2226,8 +2229,6 @@ TEST(Backend, cacheIntegration)
return lgrInfo; return lgrInfo;
}; };
auto writeLedger = [&](auto lgrInfo, auto objs, auto state) { auto writeLedger = [&](auto lgrInfo, auto objs, auto state) {
std::cout << "writing ledger = "
<< std::to_string(lgrInfo.seq);
backend->startWrites(); backend->startWrites();
backend->writeLedger( backend->writeLedger(
@@ -2344,11 +2345,6 @@ TEST(Backend, cacheIntegration)
uint32_t limit = 10; uint32_t limit = 10;
page = backend->fetchLedgerPage( page = backend->fetchLedgerPage(
page.cursor, seq, limit, false, yield); page.cursor, seq, limit, false, yield);
std::cout << "fetched a page " << page.objects.size()
<< std::endl;
if (page.cursor)
std::cout << ripple::strHex(*page.cursor)
<< std::endl;
// if (page.cursor) // if (page.cursor)
// EXPECT_EQ(page.objects.size(), limit); // EXPECT_EQ(page.objects.size(), limit);
retObjs.insert( retObjs.insert(
@@ -2372,8 +2368,7 @@ TEST(Backend, cacheIntegration)
} }
} }
if (found != (obj.second.size() != 0)) if (found != (obj.second.size() != 0))
std::cout << ripple::strHex(obj.first) << std::endl; ASSERT_EQ(found, obj.second.size() != 0);
ASSERT_EQ(found, obj.second.size() != 0);
} }
}; };
@@ -2437,11 +2432,8 @@ TEST(Backend, cacheIntegration)
for (auto [seq, diff] : state) for (auto [seq, diff] : state)
{ {
std::cout << "flatteneing" << std::endl;
auto flat = flatten(seq); auto flat = flatten(seq);
std::cout << "flattened" << std::endl;
checkLedger(lgrInfos[seq], flat); checkLedger(lgrInfos[seq], flat);
std::cout << "checked" << std::endl;
} }
} }

View File

@@ -1,9 +1,27 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <config/Config.h> #include <config/Config.h>
#include <util/Fixtures.h>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/json/parse.hpp> #include <boost/json/parse.hpp>
#include <boost/log/core.hpp>
#include <gtest/gtest.h>
#include <cstdio> #include <cstdio>
#include <exception> #include <exception>
@@ -34,17 +52,10 @@ constexpr static auto JSONData = R"JSON(
} }
)JSON"; )JSON";
class ConfigTest : public ::testing::Test class ConfigTest : public NoLoggerFixture
{ {
protected: protected:
Config cfg{json::parse(JSONData)}; Config cfg{json::parse(JSONData)};
void
SetUp() override
{
// disable logging in test
core::get()->set_logging_enabled(false);
}
}; };
TEST_F(ConfigTest, SanityCheck) TEST_F(ConfigTest, SanityCheck)

177
unittests/DOSGuard.cpp Normal file
View File

@@ -0,0 +1,177 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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/Fixtures.h>
#include <config/Config.h>
#include <webserver/DOSGuard.h>
#include <boost/json/parse.hpp>
#include <gmock/gmock.h>
using namespace testing;
using namespace clio;
using namespace std;
namespace json = boost::json;
namespace {
constexpr static auto JSONData = R"JSON(
{
"dos_guard": {
"max_fetches": 100,
"sweep_interval": 1,
"max_connections": 2,
"max_requests": 3,
"whitelist": ["127.0.0.1"]
}
}
)JSON";
constexpr static auto JSONData2 = R"JSON(
{
"dos_guard": {
"max_fetches": 100,
"sweep_interval": 0.1,
"max_connections": 2,
"whitelist": ["127.0.0.1"]
}
}
)JSON";
constexpr static auto IP = "127.0.0.2";
class FakeSweepHandler
{
private:
using guard_type = BasicDOSGuard<FakeSweepHandler>;
guard_type* dosGuard_;
public:
void
setup(guard_type* guard)
{
dosGuard_ = guard;
}
void
sweep()
{
dosGuard_->clear();
}
};
}; // namespace
class DOSGuardTest : public NoLoggerFixture
{
protected:
Config cfg{json::parse(JSONData)};
FakeSweepHandler sweepHandler;
BasicDOSGuard<FakeSweepHandler> guard{cfg, sweepHandler};
};
TEST_F(DOSGuardTest, Whitelisting)
{
EXPECT_TRUE(guard.isWhiteListed("127.0.0.1"));
EXPECT_FALSE(guard.isWhiteListed(IP));
}
TEST_F(DOSGuardTest, ConnectionCount)
{
EXPECT_TRUE(guard.isOk(IP));
guard.increment(IP); // one connection
EXPECT_TRUE(guard.isOk(IP));
guard.increment(IP); // two connections
EXPECT_TRUE(guard.isOk(IP));
guard.increment(IP); // > two connections, can't connect more
EXPECT_FALSE(guard.isOk(IP));
guard.decrement(IP);
EXPECT_TRUE(guard.isOk(IP)); // can connect again
}
TEST_F(DOSGuardTest, FetchCount)
{
EXPECT_TRUE(guard.add(IP, 50)); // half of allowence
EXPECT_TRUE(guard.add(IP, 50)); // now fully charged
EXPECT_FALSE(guard.add(IP, 1)); // can't add even 1 anymore
EXPECT_FALSE(guard.isOk(IP));
guard.clear(); // force clear the above fetch count
EXPECT_TRUE(guard.isOk(IP)); // can fetch again
}
TEST_F(DOSGuardTest, ClearFetchCountOnTimer)
{
EXPECT_TRUE(guard.add(IP, 50)); // half of allowence
EXPECT_TRUE(guard.add(IP, 50)); // now fully charged
EXPECT_FALSE(guard.add(IP, 1)); // can't add even 1 anymore
EXPECT_FALSE(guard.isOk(IP));
sweepHandler.sweep(); // pretend sweep called from timer
EXPECT_TRUE(guard.isOk(IP)); // can fetch again
}
TEST_F(DOSGuardTest, RequestLimit)
{
EXPECT_TRUE(guard.request(IP));
EXPECT_TRUE(guard.request(IP));
EXPECT_TRUE(guard.request(IP));
EXPECT_TRUE(guard.isOk(IP));
EXPECT_FALSE(guard.request(IP));
EXPECT_FALSE(guard.isOk(IP));
guard.clear();
EXPECT_TRUE(guard.isOk(IP)); // can request again
}
TEST_F(DOSGuardTest, RequestLimitOnTimer)
{
EXPECT_TRUE(guard.request(IP));
EXPECT_TRUE(guard.request(IP));
EXPECT_TRUE(guard.request(IP));
EXPECT_TRUE(guard.isOk(IP));
EXPECT_FALSE(guard.request(IP));
EXPECT_FALSE(guard.isOk(IP));
sweepHandler.sweep();
EXPECT_TRUE(guard.isOk(IP)); // can request again
}
template <typename SweepHandler>
struct BasicDOSGuardMock : public BaseDOSGuard
{
BasicDOSGuardMock(SweepHandler& handler)
{
handler.setup(this);
}
MOCK_METHOD(void, clear, (), (noexcept, override));
};
class DOSGuardIntervalSweepHandlerTest : public SyncAsioContextTest
{
protected:
Config cfg{json::parse(JSONData2)};
IntervalSweepHandler sweepHandler{cfg, ctx};
BasicDOSGuardMock<IntervalSweepHandler> guard{sweepHandler};
};
TEST_F(DOSGuardIntervalSweepHandlerTest, SweepAfterInterval)
{
EXPECT_CALL(guard, clear()).Times(Exactly(2));
ctx.run_for(std::chrono::milliseconds(210));
}

68
unittests/Logger.cpp Normal file
View File

@@ -0,0 +1,68 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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/Fixtures.h>
using namespace clio;
// Used as a fixture for tests with enabled logging
class LoggerTest : public LoggerFixture
{
};
// Used as a fixture for tests with disabled logging
class NoLoggerTest : public NoLoggerFixture
{
};
TEST_F(LoggerTest, Basic)
{
Logger log{"General"};
log.info() << "Info line logged";
checkEqual("General:NFO Info line logged");
LogService::debug() << "Debug line with numbers " << 12345;
checkEqual("General:DBG Debug line with numbers 12345");
LogService::warn() << "Warning is logged";
checkEqual("General:WRN Warning is logged");
}
TEST_F(LoggerTest, Filtering)
{
Logger log{"General"};
log.trace() << "Should not be logged";
checkEmpty();
log.warn() << "Warning is logged";
checkEqual("General:WRN Warning is logged");
Logger tlog{"Trace"};
tlog.trace() << "Trace line logged for 'Trace' component";
checkEqual("Trace:TRC Trace line logged for 'Trace' component");
}
TEST_F(NoLoggerTest, Basic)
{
Logger log{"Trace"};
log.trace() << "Nothing";
checkEmpty();
LogService::fatal() << "Still nothing";
checkEmpty();
}

108
unittests/ProfilerTest.cpp Normal file
View File

@@ -0,0 +1,108 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <gtest/gtest.h>
#include <thread>
#include <util/Profiler.h>
using namespace util;
TEST(TimedTest, HasReturnValue)
{
auto [ret, time] = timed([]() {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
return 8;
});
ASSERT_EQ(ret, 8);
ASSERT_NE(time, 0);
}
TEST(TimedTest, ReturnVoid)
{
auto time = timed(
[]() { std::this_thread::sleep_for(std::chrono::milliseconds(5)); });
ASSERT_NE(time, 0);
}
struct FunctorTest
{
void
operator()() const
{
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
};
TEST(TimedTest, Functor)
{
auto time = timed(FunctorTest());
ASSERT_NE(time, 0);
}
TEST(TimedTest, MovedLambda)
{
auto f = []() {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
return 8;
};
auto [ret, time] = timed(std::move(f));
ASSERT_EQ(ret, 8);
ASSERT_NE(time, 0);
}
TEST(TimedTest, ChangeToNs)
{
auto f = []() {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
return 8;
};
auto [ret, time] = timed<std::chrono::nanoseconds>(std::move(f));
ASSERT_EQ(ret, 8);
ASSERT_GE(time, 5 * 1000000);
}
TEST(TimedTest, NestedLambda)
{
double timeNested;
auto f = [&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
timeNested = timed([]() {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
});
return 8;
};
auto [ret, time] = timed<std::chrono::nanoseconds>(std::move(f));
ASSERT_EQ(ret, 8);
ASSERT_GE(timeNested, 5);
ASSERT_GE(time, 10 * 1000000);
}
TEST(TimedTest, FloatSec)
{
auto f = []() {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
return 8;
};
auto [ret, time] = timed<std::chrono::duration<double>>(std::move(f));
ASSERT_EQ(ret, 8);
ASSERT_GE(time, 0);
}

View File

@@ -1,3 +1,22 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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/RPC.h> #include <rpc/RPC.h>
#include <boost/json.hpp> #include <boost/json.hpp>

156
unittests/util/Fixtures.h Normal file
View File

@@ -0,0 +1,156 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022, 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 <ios>
#include <mutex>
#include <thread>
#include <boost/asio.hpp>
#include <gtest/gtest.h>
#include <log/Logger.h>
/**
* @brief Fixture with LogService support.
*/
class LoggerFixture : public ::testing::Test
{
/**
* @brief A simple string buffer that can be used to mock std::cout for
* console logging.
*/
class FakeBuffer final : public std::stringbuf
{
public:
std::string
getStrAndReset()
{
auto value = str();
str("");
return value;
}
};
FakeBuffer buffer_;
std::ostream stream_ = std::ostream{&buffer_};
protected:
// Simulates the `LogService::init(config)` call
void
SetUp() override
{
static std::once_flag once_;
std::call_once(once_, [] {
boost::log::add_common_attributes();
boost::log::register_simple_formatter_factory<clio::Severity, char>(
"Severity");
});
namespace src = boost::log::sources;
namespace keywords = boost::log::keywords;
namespace sinks = boost::log::sinks;
namespace expr = boost::log::expressions;
auto core = boost::log::core::get();
core->remove_all_sinks();
boost::log::add_console_log(
stream_, keywords::format = "%Channel%:%Severity% %Message%");
auto min_severity = expr::channel_severity_filter(
clio::log_channel, clio::log_severity);
min_severity["General"] = clio::Severity::DBG;
min_severity["Trace"] = clio::Severity::TRC;
core->set_filter(min_severity);
core->set_logging_enabled(true);
}
void
checkEqual(std::string expected)
{
auto value = buffer_.getStrAndReset();
ASSERT_EQ(value, expected + '\n');
}
void
checkEmpty()
{
ASSERT_TRUE(buffer_.getStrAndReset().empty());
}
};
/**
* @brief Fixture with LogService support but completely disabled logging.
*
* This is meant to be used as a base for other fixtures.
*/
class NoLoggerFixture : public LoggerFixture
{
protected:
void
SetUp() override
{
LoggerFixture::SetUp();
boost::log::core::get()->set_logging_enabled(false);
}
};
/**
* @brief Fixture with an embedded boost::asio context running on a thread
*
* This is meant to be used as a base for other fixtures.
*/
struct AsyncAsioContextTest : public NoLoggerFixture
{
AsyncAsioContextTest()
{
work.emplace(ctx); // make sure ctx does not stop on its own
}
~AsyncAsioContextTest()
{
work.reset();
ctx.stop();
runner.join();
}
protected:
boost::asio::io_context ctx;
private:
std::optional<boost::asio::io_service::work> work;
std::thread runner{[this] { ctx.run(); }};
};
/**
* @brief Fixture with an embedded boost::asio context that is not running by
* default but can be progressed on the calling thread
*
* Use `run_for(duration)` etc. directly on `ctx`.
* This is meant to be used as a base for other fixtures.
*/
struct SyncAsioContextTest : public NoLoggerFixture
{
SyncAsioContextTest()
{
}
protected:
boost::asio::io_context ctx;
};