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
# format all relevant sources
find src unittests -type f \( -name '*.cpp' -o -name '*.h' -o -name '*.ipp' \) -print0 | xargs -0 clang-format -i
# paths to check and re-format
sources="src unittests"
formatter="clang-format -i"
# check how many lines differ
lines=$(git diff src unittests | wc -l)
first=$(git diff $sources)
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 [ "$lines" != "0" ]; then
if [ "$changes" != "0" ]; then
cat <<\EOF
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
on:
push:
branches: [master, release, develop, develop-next]
branches: [master, release/*, develop, develop-next]
pull_request:
branches: [master, release, develop, develop-next]
branches: [master, release/*, develop, develop-next]
workflow_dispatch:
jobs:
@@ -29,6 +29,7 @@ jobs:
- suffix: rpm
image: rippleci/clio-rpm-builder:2022-09-17
script: rpm
container:
image: ${{ matrix.type.image }}
@@ -67,59 +68,63 @@ jobs:
name: clio_tests-${{ matrix.type.suffix }}
path: ${{ github.workspace }}/clio_tests
sign:
name: Sign packages
needs: build_clio
runs-on: ubuntu-20.04
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 }}
build_dev:
name: ${{ matrix.os.name }} test
needs: lint
continue-on-error: ${{ matrix.os.experimental }}
strategy:
fail-fast: false
matrix:
type:
- suffix: deb
image: ubuntu:20.04
script: dpkg
# - suffix: rpm
# image: centos:7
# script: rpm
container:
image: ${{ matrix.type.image }}
os:
- name: ubuntu-22.04
experimental: true
- name: macos-11
experimental: true
- name: macos-12
experimental: false
runs-on: ${{ matrix.os.name }}
steps:
- uses: actions/checkout@v3
- name: Install dpkg-sig
run: |
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
path: clio
- name: find packages
run: find . -name "*.${{ matrix.type.suffix }}"
- name: Sign packages
uses: ./.github/actions/sign
- name: Verify the signature
run: |
set -e
for PKG in $(ls *.deb); do
gpg --verify "${PKG}"
done
- name: Get short SHA
id: shortsha
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- name: Artifact signed packages
uses: actions/upload-artifact@v2
- name: Check Boost cache
id: boost
uses: actions/cache@v3
with:
name: signed-clio-deb-packages-${{ steps.shortsha.outputs.sha8 }}
path: ${{ github.workspace }}/*.deb
path: boost
key: ${{ runner.os }}-boost
- name: Build boost
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: Build clio
run: |
export BOOST_ROOT=$(pwd)/boost
cd clio
cmake -B build
if ! cmake --build build -j$(nproc); then
echo '# 🔥${{ matrix.os.name }}🔥 failed!💥' >> $GITHUB_STEP_SUMMARY
fi
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)
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()

View File

@@ -11,6 +11,7 @@ ExecStart=@CLIO_INSTALL_DIR@/bin/clio_server @CLIO_INSTALL_DIR@/etc/config.json
Restart=on-failure
User=clio
Group=clio
LimitNOFILE=65536
[Install]
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)
if(Git_FOUND)
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE gch)
if(gch)
set(GIT_COMMIT_HASH "${gch}")
message(STATUS "Git commit: ${GIT_COMMIT_HASH}")
add_definitions(-DCLIO_GIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE git-ref)
if(git-ref)
set(BUILD "${git-ref}")
message(STATUS "Build version: ${BUILD}")
add_definitions(-DCLIO_BUILD="${BUILD}")
endif()
endif()
endif() #git
@@ -33,7 +33,6 @@ if(PACKAGING)
add_definitions(-DPKG=1)
endif()
add_library(clio)
target_compile_features(clio PUBLIC cxx_std_20)
target_include_directories(clio PUBLIC src)
@@ -45,6 +44,7 @@ include(CMake/ClioVersion.cmake)
include(CMake/deps/rippled.cmake)
include(CMake/deps/Boost.cmake)
include(CMake/deps/cassandra.cmake)
include(CMake/deps/SourceLocation.cmake)
target_sources(clio PRIVATE
## Main
@@ -99,10 +99,11 @@ target_sources(clio PRIVATE
src/rpc/handlers/Subscribe.cpp
# Server
src/rpc/handlers/ServerInfo.cpp
# Utility
# Utilities
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)
target_link_libraries(clio_server PUBLIC clio)
@@ -110,8 +111,11 @@ target_link_libraries(clio_server PUBLIC clio)
if(BUILD_TESTS)
add_executable(clio_tests
unittests/RPCErrors.cpp
unittests/config.cpp
unittests/main.cpp)
unittests/Backend.cpp
unittests/Logger.cpp
unittests/Config.cpp
unittests/ProfilerTest.cpp
unittests/DOSGuard.cpp)
include(CMake/deps/gtest.cmake)
endif()

View File

@@ -22,7 +22,7 @@ from which data can be extracted. The rippled node does not need to be running o
## 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.
@@ -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
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
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
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`.
`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_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
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
Since Clio relies on either Cassandra or Scylla for its database backend, here are some important considerations:

View File

@@ -1,46 +1,87 @@
{
"database":
{
"type":"cassandra",
"cassandra":
{
"contact_points":"127.0.0.1",
"port":9042,
"keyspace":"clio",
"replication_factor":1,
"table_prefix":"",
"max_write_requests_outstanding":25000,
"max_read_requests_outstanding":30000,
"threads":8
"database": {
"type": "cassandra",
"cassandra": {
"contact_points": "127.0.0.1",
"port": 9042,
"keyspace": "clio",
"replication_factor": 1,
"table_prefix": "",
"max_write_requests_outstanding": 25000,
"max_read_requests_outstanding": 30000,
"threads": 8
}
},
"etl_sources":
[
"etl_sources": [
{
"ip":"127.0.0.1",
"ws_port":"6006",
"grpc_port":"50051"
"ip": "127.0.0.1",
"ws_port": "6006",
"grpc_port": "50051"
}
],
"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":
{
"peers": [{"ip":"127.0.0.1","port":51234}]
},
"server":{
"ip":"0.0.0.0",
"port":51233
"ip": "0.0.0.0",
"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_directory":"./clio_log",
"log_directory": "./clio_log",
"log_rotation_size": 2048,
"log_directory_max_size": 51200,
"log_rotation_hour_interval": 12,
"log_tag_style": "uint",
"extractor_threads":8,
"read_only":false
"extractor_threads": 8,
"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/CassandraBackend.h>
#include <config/Config.h>
#include <log/Logger.h>
#include <boost/algorithm/string.hpp>
namespace Backend {
std::shared_ptr<BackendInterface>
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 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);
}
BOOST_LOG_TRIVIAL(info)
<< __func__ << ": Constructed BackendInterface Successfully";
log.info() << "Constructed BackendInterface Successfully";
return 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/STLedgerEntry.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 {
bool
BackendInterface::finishWrites(std::uint32_t const ledgerSequence)
@@ -26,7 +54,7 @@ std::optional<LedgerRange>
BackendInterface::hardFetchLedgerRangeNoThrow(
boost::asio::yield_context& yield) const
{
BOOST_LOG_TRIVIAL(trace) << __func__ << "(yield)";
gLog.trace() << "called";
while (true)
{
try
@@ -43,7 +71,7 @@ BackendInterface::hardFetchLedgerRangeNoThrow(
std::optional<LedgerRange>
BackendInterface::hardFetchLedgerRangeNoThrow() const
{
BOOST_LOG_TRIVIAL(trace) << __func__ << "()";
gLog.trace() << "called";
return retryOnTimeout([&]() { return hardFetchLedgerRange(); });
}
@@ -57,21 +85,17 @@ BackendInterface::fetchLedgerObject(
auto obj = cache_.get(key, sequence);
if (obj)
{
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " - cache hit - " << ripple::strHex(key);
gLog.trace() << "Cache hit - " << ripple::strHex(key);
return *obj;
}
else
{
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " - cache miss - " << ripple::strHex(key);
gLog.trace() << "Cache miss - " << ripple::strHex(key);
auto dbObj = doFetchLedgerObject(key, sequence, yield);
if (!dbObj)
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " - missed cache and missed in db";
gLog.trace() << "Missed cache and missed in db";
else
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " - missed cache but found in db";
gLog.trace() << "Missed cache but found in db";
return dbObj;
}
}
@@ -93,8 +117,7 @@ BackendInterface::fetchLedgerObjects(
else
misses.push_back(keys[i]);
}
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " - cache hits = " << keys.size() - misses.size()
gLog.trace() << "Cache hits = " << keys.size() - misses.size()
<< " - cache misses = " << misses.size();
if (misses.size())
@@ -121,11 +144,9 @@ BackendInterface::fetchSuccessorKey(
{
auto succ = cache_.getSuccessor(key, ledgerSequence);
if (succ)
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " - cache hit - " << ripple::strHex(key);
gLog.trace() << "Cache hit - " << ripple::strHex(key);
else
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " - cache miss - " << ripple::strHex(key);
gLog.trace() << "Cache miss - " << ripple::strHex(key);
return succ ? succ->key : doFetchSuccessorKey(key, ledgerSequence, yield);
}
@@ -179,8 +200,8 @@ BackendInterface::fetchBookOffers(
succMillis += getMillis(mid2 - mid1);
if (!offerDir || offerDir->key >= bookEnd)
{
BOOST_LOG_TRIVIAL(trace) << __func__ << " - offerDir.has_value() "
<< offerDir.has_value() << " breaking";
gLog.trace() << "offerDir.has_value() " << offerDir.has_value()
<< " breaking";
break;
}
uTipIndex = offerDir->key;
@@ -196,8 +217,7 @@ BackendInterface::fetchBookOffers(
auto next = sle.getFieldU64(ripple::sfIndexNext);
if (!next)
{
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " next is empty. breaking";
gLog.trace() << "Next is empty. breaking";
break;
}
auto nextKey = ripple::keylet::page(uTipIndex, next);
@@ -214,23 +234,21 @@ BackendInterface::fetchBookOffers(
auto objs = fetchLedgerObjects(keys, ledgerSequence, yield);
for (size_t i = 0; i < keys.size() && i < limit; ++i)
{
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " key = " << ripple::strHex(keys[i])
gLog.trace() << "Key = " << ripple::strHex(keys[i])
<< " blob = " << ripple::strHex(objs[i])
<< " ledgerSequence = " << ledgerSequence;
assert(objs[i].size());
page.offers.push_back({keys[i], objs[i]});
}
auto end = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " "
<< "Fetching " << std::to_string(keys.size()) << " offers took "
<< std::to_string(getMillis(mid - begin))
gLog.debug() << "Fetching " << std::to_string(keys.size())
<< " offers took " << std::to_string(getMillis(mid - begin))
<< " milliseconds. Fetching next dir took "
<< std::to_string(succMillis) << " milliseonds. Fetched next dir "
<< std::to_string(numSucc) << " times"
<< " Fetching next page of dir took " << std::to_string(pageMillis)
<< " milliseconds"
<< std::to_string(succMillis)
<< " milliseonds. Fetched next dir " << std::to_string(numSucc)
<< " times"
<< " Fetching next page of dir took "
<< std::to_string(pageMillis) << " milliseconds"
<< ". num pages = " << std::to_string(numPages)
<< ". Fetching all objects took "
<< std::to_string(getMillis(end - mid))
@@ -274,16 +292,15 @@ BackendInterface::fetchLedgerPage(
page.objects.push_back({std::move(keys[i]), std::move(objects[i])});
else if (!outOfOrder)
{
BOOST_LOG_TRIVIAL(error)
<< __func__
<< " deleted or non-existent object in successor table. key = "
gLog.error()
<< "Deleted or non-existent object in successor table. key = "
<< ripple::strHex(keys[i]) << " - seq = " << ledgerSequence;
std::stringstream msg;
for (size_t j = 0; j < objects.size(); ++j)
{
msg << " - " << ripple::strHex(keys[j]);
}
BOOST_LOG_TRIVIAL(error) << __func__ << msg.str();
gLog.error() << msg.str();
}
}
if (keys.size() && !reachedEnd)
@@ -304,7 +321,7 @@ BackendInterface::fetchFees(
if (!bytes)
{
BOOST_LOG_TRIVIAL(error) << __func__ << " - could not find fees";
gLog.error() << "Could not find fees";
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>
#include <boost/json.hpp>
#include <boost/log/trivial.hpp>
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 <backend/DBHelpers.h>
#include <backend/SimpleCache.h>
#include <backend/Types.h>
#include <config/Config.h>
#include <log/Logger.h>
#include <boost/asio/spawn.hpp>
#include <boost/json.hpp>
#include <thread>
#include <type_traits>
@@ -46,6 +62,8 @@ template <class F>
auto
retryOnTimeout(F func, size_t waitMs = 500)
{
static clio::Logger log{"Backend"};
while (true)
{
try
@@ -54,9 +72,8 @@ retryOnTimeout(F func, size_t waitMs = 500)
}
catch (DatabaseTimeout& t)
{
BOOST_LOG_TRIVIAL(error)
<< __func__
<< " Database request timed out. Sleeping and retrying ... ";
log.error()
<< "Database request timed out. Sleeping and retrying ... ";
std::this_thread::sleep_for(std::chrono::milliseconds(waitMs));
}
}
@@ -644,4 +661,3 @@ private:
} // namespace Backend
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 <backend/CassandraBackend.h>
#include <backend/DBHelpers.h>
#include <log/Logger.h>
#include <util/Profiler.h>
#include <functional>
#include <unordered_map>
using namespace clio;
namespace Backend {
// Type alias for async completion handlers
@@ -16,6 +40,8 @@ template <class T, class F>
void
processAsyncWriteResponse(T& requestParams, CassFuture* fut, F func)
{
static clio::Logger log{"Backend"};
CassandraBackend const& backend = *requestParams.backend;
auto rc = cass_future_error_code(fut);
if (rc != CASS_OK)
@@ -23,9 +49,9 @@ processAsyncWriteResponse(T& requestParams, CassFuture* fut, F func)
// exponential backoff with a max wait of 2^10 ms (about 1 second)
auto wait = std::chrono::milliseconds(
lround(std::pow(2, std::min(10u, requestParams.currentRetries))));
BOOST_LOG_TRIVIAL(error)
<< "ERROR!!! Cassandra write error: " << rc << ", "
<< cass_error_desc(rc) << " id= " << requestParams.toString()
log.error() << "ERROR!!! Cassandra write error: " << rc << ", "
<< cass_error_desc(rc)
<< " id= " << requestParams.toString()
<< ", current retries " << requestParams.currentRetries
<< ", retrying in " << wait.count() << " milliseconds";
++requestParams.currentRetries;
@@ -40,11 +66,11 @@ processAsyncWriteResponse(T& requestParams, CassFuture* fut, F func)
}
else
{
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " Succesfully inserted a record";
log.trace() << "Succesfully inserted a record";
requestParams.finish();
}
}
template <class T>
void
processAsyncWrite(CassFuture* fut, void* cbData)
@@ -155,6 +181,7 @@ makeAndExecuteAsyncWrite(
auto* cb = new WriteCallbackData<T, B>(b, std::move(d), bind, id);
cb->start();
}
template <class T, class B>
std::shared_ptr<BulkWriteCallbackData<T, B>>
makeAndExecuteBulkAsyncWrite(
@@ -170,13 +197,14 @@ makeAndExecuteBulkAsyncWrite(
cb->start();
return cb;
}
void
CassandraBackend::doWriteLedgerObject(
std::string&& key,
std::uint32_t const seq,
std::string&& blob)
{
BOOST_LOG_TRIVIAL(trace) << "Writing ledger object to cassandra";
log_.trace() << "Writing ledger object to cassandra";
if (range)
makeAndExecuteAsyncWrite(
this,
@@ -204,15 +232,16 @@ CassandraBackend::doWriteLedgerObject(
},
"ledger_object");
}
void
CassandraBackend::writeSuccessor(
std::string&& key,
std::uint32_t const seq,
std::string&& successor)
{
BOOST_LOG_TRIVIAL(trace)
<< "Writing successor. key = " << key
<< " seq = " << std::to_string(seq) << " successor = " << successor;
log_.trace() << "Writing successor. key = " << key.size() << " bytes. "
<< " seq = " << std::to_string(seq)
<< " successor = " << successor.size() << " bytes.";
assert(key.size() != 0);
assert(successor.size() != 0);
makeAndExecuteAsyncWrite(
@@ -319,7 +348,7 @@ CassandraBackend::writeTransaction(
std::string&& transaction,
std::string&& metadata)
{
BOOST_LOG_TRIVIAL(trace) << "Writing txn to cassandra";
log_.trace() << "Writing txn to cassandra";
std::string hashCpy = hash;
makeAndExecuteAsyncWrite(
@@ -393,13 +422,13 @@ CassandraBackend::writeNFTs(std::vector<NFTsData>&& data)
std::optional<LedgerRange>
CassandraBackend::hardFetchLedgerRange(boost::asio::yield_context& yield) const
{
BOOST_LOG_TRIVIAL(trace) << "Fetching from cassandra";
log_.trace() << "Fetching from cassandra";
CassandraStatement statement{selectLedgerRange_};
CassandraResult result = executeAsyncRead(statement, yield);
if (!result)
{
BOOST_LOG_TRIVIAL(error) << __func__ << " - no rows";
log_.error() << "No rows";
return {};
}
LedgerRange range;
@@ -496,8 +525,7 @@ CassandraBackend::fetchTransactions(
std::vector<TransactionAndMetadata> results{numHashes};
std::vector<std::shared_ptr<ReadCallbackData<result_type>>> cbs;
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_};
@@ -520,18 +548,15 @@ CassandraBackend::fetchTransactions(
// 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)
{
if (cb->errored)
throw DatabaseTimeout();
}
BOOST_LOG_TRIVIAL(debug)
<< "Fetched " << numHashes << " transactions from Cassandra in "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
.count()
log_.debug() << "Fetched " << numHashes
<< " transactions from Cassandra in " << timeDiff
<< " milliseconds";
return results;
}
@@ -550,9 +575,7 @@ CassandraBackend::fetchAllTransactionHashesInLedger(
auto end = std::chrono::system_clock::now();
if (!result)
{
BOOST_LOG_TRIVIAL(error)
<< __func__
<< " - no rows . ledger = " << std::to_string(ledgerSequence);
log_.error() << "No rows. Ledger = " << std::to_string(ledgerSequence);
return {};
}
std::vector<ripple::uint256> hashes;
@@ -560,10 +583,10 @@ CassandraBackend::fetchAllTransactionHashesInLedger(
{
hashes.push_back(result.getUInt256());
} while (result.nextRow());
BOOST_LOG_TRIVIAL(debug)
<< "Fetched " << hashes.size()
log_.debug() << "Fetched " << hashes.size()
<< " transaction hashes from Cassandra in "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
<< std::chrono::duration_cast<std::chrono::milliseconds>(
end - start)
.count()
<< " milliseconds";
return hashes;
@@ -613,9 +636,9 @@ CassandraBackend::fetchNFTTransactions(
{
statement.bindNextIntTuple(
cursor->ledgerSequence, cursor->transactionIndex);
BOOST_LOG_TRIVIAL(debug) << " token_id = " << ripple::strHex(tokenID)
log_.debug() << "token_id = " << ripple::strHex(tokenID)
<< " tuple = " << cursor->ledgerSequence
<< " : " << cursor->transactionIndex;
<< cursor->transactionIndex;
}
else
{
@@ -624,9 +647,8 @@ CassandraBackend::fetchNFTTransactions(
forward ? 0 : std::numeric_limits<std::uint32_t>::max();
statement.bindNextIntTuple(placeHolder, placeHolder);
BOOST_LOG_TRIVIAL(debug)
<< " token_id = " << ripple::strHex(tokenID) << " idx = " << seq
<< " tuple = " << placeHolder;
log_.debug() << "token_id = " << ripple::strHex(tokenID)
<< " idx = " << seq << " tuple = " << placeHolder;
}
statement.bindNextUInt(limit);
@@ -635,19 +657,19 @@ CassandraBackend::fetchNFTTransactions(
if (!result.hasResult())
{
BOOST_LOG_TRIVIAL(debug) << __func__ << " - no rows returned";
log_.debug() << "No rows returned";
return {};
}
std::vector<ripple::uint256> hashes = {};
auto numRows = result.numRows();
BOOST_LOG_TRIVIAL(info) << "num_rows = " << numRows;
log_.info() << "num_rows = " << numRows;
do
{
hashes.push_back(result.getUInt256());
if (--numRows == 0)
{
BOOST_LOG_TRIVIAL(debug) << __func__ << " setting cursor";
log_.debug() << "Setting cursor";
auto const [lgrSeq, txnIdx] = result.getInt64Tuple();
cursor = {
static_cast<std::uint32_t>(lgrSeq),
@@ -662,11 +684,11 @@ CassandraBackend::fetchNFTTransactions(
} while (result.nextRow());
auto txns = fetchTransactions(hashes, yield);
BOOST_LOG_TRIVIAL(debug) << __func__ << " txns = " << txns.size();
log_.debug() << "Txns = " << txns.size();
if (txns.size() == limit)
{
BOOST_LOG_TRIVIAL(debug) << __func__ << " returning cursor";
log_.debug() << "Returning cursor";
return {txns, cursor};
}
@@ -698,9 +720,9 @@ CassandraBackend::fetchAccountTransactions(
{
statement.bindNextIntTuple(
cursor->ledgerSequence, cursor->transactionIndex);
BOOST_LOG_TRIVIAL(debug) << " account = " << ripple::strHex(account)
log_.debug() << "account = " << ripple::strHex(account)
<< " tuple = " << cursor->ledgerSequence
<< " : " << cursor->transactionIndex;
<< cursor->transactionIndex;
}
else
{
@@ -709,9 +731,8 @@ CassandraBackend::fetchAccountTransactions(
forward ? 0 : std::numeric_limits<std::uint32_t>::max();
statement.bindNextIntTuple(placeHolder, placeHolder);
BOOST_LOG_TRIVIAL(debug)
<< " account = " << ripple::strHex(account) << " idx = " << seq
<< " tuple = " << placeHolder;
log_.debug() << "account = " << ripple::strHex(account)
<< " idx = " << seq << " tuple = " << placeHolder;
}
statement.bindNextUInt(limit);
@@ -719,19 +740,19 @@ CassandraBackend::fetchAccountTransactions(
if (!result.hasResult())
{
BOOST_LOG_TRIVIAL(debug) << __func__ << " - no rows returned";
log_.debug() << "No rows returned";
return {};
}
std::vector<ripple::uint256> hashes = {};
auto numRows = result.numRows();
BOOST_LOG_TRIVIAL(info) << "num_rows = " << std::to_string(numRows);
log_.info() << "num_rows = " << std::to_string(numRows);
do
{
hashes.push_back(result.getUInt256());
if (--numRows == 0)
{
BOOST_LOG_TRIVIAL(debug) << __func__ << " setting cursor";
log_.debug() << "Setting cursor";
auto [lgrSeq, txnIdx] = result.getInt64Tuple();
cursor = {
static_cast<std::uint32_t>(lgrSeq),
@@ -746,11 +767,11 @@ CassandraBackend::fetchAccountTransactions(
} while (result.nextRow());
auto txns = fetchTransactions(hashes, yield);
BOOST_LOG_TRIVIAL(debug) << __func__ << "txns = " << txns.size();
log_.debug() << "Txns = " << txns.size();
if (txns.size() == limit)
{
BOOST_LOG_TRIVIAL(debug) << __func__ << " returning cursor";
log_.debug() << "Returning cursor";
return {txns, cursor};
}
@@ -763,7 +784,7 @@ CassandraBackend::doFetchSuccessorKey(
std::uint32_t const ledgerSequence,
boost::asio::yield_context& yield) const
{
BOOST_LOG_TRIVIAL(trace) << "Fetching from cassandra";
log_.trace() << "Fetching from cassandra";
CassandraStatement statement{selectSuccessor_};
statement.bindNextBytes(key);
statement.bindNextInt(ledgerSequence);
@@ -772,7 +793,7 @@ CassandraBackend::doFetchSuccessorKey(
if (!result)
{
BOOST_LOG_TRIVIAL(debug) << __func__ << " - no rows";
log_.debug() << "No rows";
return {};
}
auto next = result.getUInt256();
@@ -787,7 +808,7 @@ CassandraBackend::doFetchLedgerObject(
std::uint32_t const sequence,
boost::asio::yield_context& yield) const
{
BOOST_LOG_TRIVIAL(trace) << "Fetching from cassandra";
log_.trace() << "Fetching from cassandra";
CassandraStatement statement{selectObject_};
statement.bindNextBytes(key);
statement.bindNextInt(sequence);
@@ -796,7 +817,7 @@ CassandraBackend::doFetchLedgerObject(
if (!result)
{
BOOST_LOG_TRIVIAL(debug) << __func__ << " - no rows";
log_.debug() << "No rows";
return {};
}
auto res = result.getBytes();
@@ -820,8 +841,7 @@ CassandraBackend::doFetchLedgerObjects(
result_type result(handler);
std::size_t const numKeys = keys.size();
BOOST_LOG_TRIVIAL(trace)
<< "Fetching " << numKeys << " records from Cassandra";
log_.trace() << "Fetching " << numKeys << " records from Cassandra";
std::atomic_int numOutstanding = numKeys;
std::vector<Blob> results{numKeys};
std::vector<std::shared_ptr<ReadCallbackData<result_type>>> cbs;
@@ -850,8 +870,7 @@ CassandraBackend::doFetchLedgerObjects(
throw DatabaseTimeout();
}
BOOST_LOG_TRIVIAL(trace)
<< "Fetched " << numKeys << " records from Cassandra";
log_.trace() << "Fetched " << numKeys << " records from Cassandra";
return results;
}
@@ -870,9 +889,7 @@ CassandraBackend::fetchLedgerDiff(
if (!result)
{
BOOST_LOG_TRIVIAL(error)
<< __func__
<< " - no rows . ledger = " << std::to_string(ledgerSequence);
log_.error() << "No rows. Ledger = " << std::to_string(ledgerSequence);
return {};
}
std::vector<ripple::uint256> keys;
@@ -880,9 +897,10 @@ CassandraBackend::fetchLedgerDiff(
{
keys.push_back(result.getUInt256());
} while (result.nextRow());
BOOST_LOG_TRIVIAL(debug)
<< "Fetched " << keys.size() << " diff hashes from Cassandra in "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
log_.debug() << "Fetched " << keys.size()
<< " diff hashes from Cassandra in "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
end - start)
.count()
<< " milliseconds";
auto objs = fetchLedgerObjects(keys, ledgerSequence, yield);
@@ -951,12 +969,12 @@ CassandraBackend::doOnlineDelete(
cv));
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]() {
return numOutstanding < concurrentLimit;
});
}
BOOST_LOG_TRIVIAL(debug) << __func__ << " fetched a page";
log_.debug() << "Fetched a page";
cursor = curCursor;
if (!cursor)
break;
@@ -982,11 +1000,11 @@ CassandraBackend::open(bool readOnly)
if (open_)
{
assert(false);
BOOST_LOG_TRIVIAL(error) << "database is already open";
log_.error() << "Database is already open";
return;
}
BOOST_LOG_TRIVIAL(info) << "Opening Cassandra Backend";
log_.info() << "Opening Cassandra Backend";
CassCluster* cluster = cass_cluster_new();
if (!cluster)
@@ -1002,7 +1020,7 @@ CassandraBackend::open(bool readOnly)
if (cass_cluster_set_cloud_secure_connection_bundle(
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: "
<< secureConnectBundle;
throw std::runtime_error(
@@ -1057,7 +1075,7 @@ CassandraBackend::open(bool readOnly)
auto username = config_.maybeValue<std::string>("username");
if (username)
{
BOOST_LOG_TRIVIAL(debug) << "user = " << *username;
log_.debug() << "user = " << *username;
auto password = config_.value<std::string>("password");
cass_cluster_set_credentials(
cluster, username->c_str(), password.c_str());
@@ -1080,10 +1098,11 @@ CassandraBackend::open(bool readOnly)
"max_read_requests_outstanding", maxReadRequestsOutstanding);
syncInterval_ = config_.valueOr<int>("sync_interval", syncInterval_);
BOOST_LOG_TRIVIAL(info)
<< __func__ << " sync interval is " << syncInterval_
<< ". max write requests outstanding is " << maxWriteRequestsOutstanding
<< ". max read requests outstanding is " << maxReadRequestsOutstanding;
log_.info() << "Sync interval is " << syncInterval_
<< ". max write requests outstanding is "
<< maxWriteRequestsOutstanding
<< ". max read requests outstanding is "
<< maxReadRequestsOutstanding;
cass_cluster_set_request_timeout(cluster, 10000);
@@ -1098,7 +1117,7 @@ CassandraBackend::open(bool readOnly)
ss << "nodestore: Error setting Cassandra max core connections per "
"host"
<< ", result: " << rc << ", " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << ss.str();
log_.error() << ss.str();
throw std::runtime_error(ss.str());
}
@@ -1140,8 +1159,7 @@ CassandraBackend::open(bool readOnly)
auto keyspace = config_.valueOr<std::string>("keyspace", "");
if (keyspace.empty())
{
BOOST_LOG_TRIVIAL(warning)
<< "No keyspace specified. Using keyspace clio";
log_.warn() << "No keyspace specified. Using keyspace clio";
keyspace = "clio";
}
@@ -1149,14 +1167,13 @@ CassandraBackend::open(bool readOnly)
auto tablePrefix = config_.valueOr<std::string>("table_prefix", "");
if (tablePrefix.empty())
{
BOOST_LOG_TRIVIAL(warning) << "Table prefix is empty";
log_.warn() << "Table prefix is empty";
}
cass_cluster_set_connect_timeout(cluster, 10000);
auto ttl = ttl_ * 2;
BOOST_LOG_TRIVIAL(info)
<< __func__ << " setting ttl to " << std::to_string(ttl);
log_.info() << "Setting ttl to " << std::to_string(ttl);
auto executeSimpleStatement = [this](std::string const& query) {
CassStatement* statement = makeStatement(query.c_str(), 0);
@@ -1169,7 +1186,7 @@ CassandraBackend::open(bool readOnly)
std::stringstream ss;
ss << "nodestore: Error executing simple statement: " << rc << ", "
<< cass_error_desc(rc) << " - " << query;
BOOST_LOG_TRIVIAL(error) << ss.str();
log_.error() << ss.str();
return false;
}
return true;
@@ -1192,7 +1209,7 @@ CassandraBackend::open(bool readOnly)
ss << "nodestore: Error connecting Cassandra session keyspace: "
<< rc << ", " << cass_error_desc(rc)
<< ", 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
session_.reset(cass_session_new());
fut = cass_session_connect(session_.get(), cluster);
@@ -1203,7 +1220,7 @@ CassandraBackend::open(bool readOnly)
std::stringstream ss;
ss << "nodestore: Error connecting Cassandra session at all: "
<< rc << ", " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << ss.str();
log_.error() << ss.str();
}
else
{
@@ -1626,6 +1643,6 @@ CassandraBackend::open(bool readOnly)
open_ = true;
BOOST_LOG_TRIVIAL(info) << "Opened CassandraBackend successfully";
log_.info() << "Opened CassandraBackend successfully";
}
} // 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 <backend/BackendInterface.h>
#include <backend/DBHelpers.h>
#include <log/Logger.h>
#include <cassandra.h>
#include <boost/asio.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/filesystem.hpp>
#include <boost/json.hpp>
#include <boost/log/trivial.hpp>
#include <atomic>
#include <backend/BackendInterface.h>
#include <backend/DBHelpers.h>
#include <cassandra.h>
#include <cstddef>
#include <iostream>
#include <memory>
@@ -25,6 +46,7 @@ namespace Backend {
class CassandraPreparedStatement
{
private:
clio::Logger log_{"Backend"};
CassPrepared const* prepared_ = nullptr;
public:
@@ -65,7 +87,7 @@ public:
std::stringstream ss;
ss << "nodestore: Error preparing statement : " << rc << ", "
<< cass_error_desc(rc) << ". query : " << query;
BOOST_LOG_TRIVIAL(error) << ss.str();
log_.error() << ss.str();
}
cass_future_free(prepareFuture);
return rc == CASS_OK;
@@ -73,7 +95,7 @@ public:
~CassandraPreparedStatement()
{
BOOST_LOG_TRIVIAL(trace) << __func__;
log_.trace() << "called";
if (prepared_)
{
cass_prepared_free(prepared_);
@@ -86,6 +108,7 @@ class CassandraStatement
{
CassStatement* statement_ = nullptr;
size_t curBindingIndex_ = 0;
clio::Logger log_{"Backend"};
public:
CassandraStatement(CassandraPreparedStatement const& prepared)
@@ -123,7 +146,7 @@ public:
std::stringstream ss;
ss << "Error binding boolean to statement: " << rc << ", "
<< cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << __func__ << " : " << ss.str();
log_.error() << ss.str();
throw std::runtime_error(ss.str());
}
curBindingIndex_++;
@@ -179,7 +202,7 @@ public:
std::stringstream ss;
ss << "Error binding bytes to statement: " << rc << ", "
<< cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << __func__ << " : " << ss.str();
log_.error() << ss.str();
throw std::runtime_error(ss.str());
}
curBindingIndex_++;
@@ -191,8 +214,8 @@ public:
if (!statement_)
throw std::runtime_error(
"CassandraStatement::bindNextUInt - statement_ is null");
BOOST_LOG_TRIVIAL(trace)
<< std::to_string(curBindingIndex_) << " " << std::to_string(value);
log_.trace() << std::to_string(curBindingIndex_) << " "
<< std::to_string(value);
CassError rc =
cass_statement_bind_int32(statement_, curBindingIndex_, value);
if (rc != CASS_OK)
@@ -200,7 +223,7 @@ public:
std::stringstream ss;
ss << "Error binding uint to statement: " << rc << ", "
<< cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << __func__ << " : " << ss.str();
log_.error() << ss.str();
throw std::runtime_error(ss.str());
}
curBindingIndex_++;
@@ -225,7 +248,7 @@ public:
std::stringstream ss;
ss << "Error binding int to statement: " << rc << ", "
<< cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << __func__ << " : " << ss.str();
log_.error() << ss.str();
throw std::runtime_error(ss.str());
}
curBindingIndex_++;
@@ -241,7 +264,7 @@ public:
std::stringstream ss;
ss << "Error binding int to tuple: " << rc << ", "
<< cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << __func__ << " : " << ss.str();
log_.error() << ss.str();
throw std::runtime_error(ss.str());
}
rc = cass_tuple_set_int64(tuple, 1, second);
@@ -250,7 +273,7 @@ public:
std::stringstream ss;
ss << "Error binding int to tuple: " << rc << ", "
<< cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << __func__ << " : " << ss.str();
log_.error() << ss.str();
throw std::runtime_error(ss.str());
}
rc = cass_statement_bind_tuple(statement_, curBindingIndex_, tuple);
@@ -259,7 +282,7 @@ public:
std::stringstream ss;
ss << "Error binding tuple to statement: " << rc << ", "
<< cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << __func__ << " : " << ss.str();
log_.error() << ss.str();
throw std::runtime_error(ss.str());
}
cass_tuple_free(tuple);
@@ -275,6 +298,7 @@ public:
class CassandraResult
{
clio::Logger log_{"Backend"};
CassResult const* result_ = nullptr;
CassRow const* row_ = nullptr;
CassIterator* iter_ = nullptr;
@@ -365,7 +389,7 @@ public:
std::stringstream msg;
msg << "CassandraResult::getBytes - error getting value: " << rc
<< ", " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << msg.str();
log_.error() << msg.str();
throw std::runtime_error(msg.str());
}
curGetIndex_++;
@@ -386,7 +410,7 @@ public:
std::stringstream msg;
msg << "CassandraResult::getuint256 - error getting value: " << rc
<< ", " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << msg.str();
log_.error() << msg.str();
throw std::runtime_error(msg.str());
}
curGetIndex_++;
@@ -406,7 +430,7 @@ public:
std::stringstream msg;
msg << "CassandraResult::getInt64 - error getting value: " << rc
<< ", " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << msg.str();
log_.error() << msg.str();
throw std::runtime_error(msg.str());
}
++curGetIndex_;
@@ -490,10 +514,9 @@ public:
{
if (!row_)
{
std::stringstream msg;
msg << __func__ << " - no result";
BOOST_LOG_TRIVIAL(error) << msg.str();
throw std::runtime_error(msg.str());
std::string msg{"No result"};
log_.error() << msg;
throw std::runtime_error(msg);
}
cass_bool_t val;
CassError rc =
@@ -501,9 +524,8 @@ public:
if (rc != CASS_OK)
{
std::stringstream msg;
msg << __func__ << " - error getting value: " << rc << ", "
<< cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << msg.str();
msg << "Error getting value: " << rc << ", " << cass_error_desc(rc);
log_.error() << msg.str();
throw std::runtime_error(msg.str());
}
++curGetIndex_;
@@ -518,6 +540,7 @@ public:
cass_iterator_free(iter_);
}
};
inline bool
isTimeout(CassError rc)
{
@@ -597,6 +620,7 @@ private:
return ret;
}
clio::Logger log_{"Backend"};
std::atomic<bool> open_{false};
std::unique_ptr<CassSession, void (*)(CassSession*)> session_{
@@ -751,13 +775,11 @@ public:
statement.bindNextInt(ledgerSequence_ - 1);
if (!executeSyncUpdate(statement))
{
BOOST_LOG_TRIVIAL(warning)
<< __func__ << " Update failed for ledger "
log_.warn() << "Update failed for ledger "
<< std::to_string(ledgerSequence_) << ". Returning";
return false;
}
BOOST_LOG_TRIVIAL(info) << __func__ << " Committed ledger "
<< std::to_string(ledgerSequence_);
log_.info() << "Committed ledger " << std::to_string(ledgerSequence_);
return true;
}
@@ -791,19 +813,17 @@ public:
statement.bindNextInt(lastSync_);
if (!executeSyncUpdate(statement))
{
BOOST_LOG_TRIVIAL(warning)
<< __func__ << " Update failed for ledger "
log_.warn() << "Update failed for ledger "
<< std::to_string(ledgerSequence_) << ". Returning";
return false;
}
BOOST_LOG_TRIVIAL(info) << __func__ << " Committed ledger "
log_.info() << "Committed ledger "
<< std::to_string(ledgerSequence_);
lastSync_ = ledgerSequence_;
}
else
{
BOOST_LOG_TRIVIAL(info)
<< __func__ << " Skipping commit. sync interval is "
log_.info() << "Skipping commit. sync interval is "
<< std::to_string(syncInterval_) << " - last sync is "
<< std::to_string(lastSync_) << " - ledger sequence is "
<< std::to_string(ledgerSequence_);
@@ -826,12 +846,12 @@ public:
std::optional<std::uint32_t>
fetchLatestLedgerSequence(boost::asio::yield_context& yield) const override
{
BOOST_LOG_TRIVIAL(trace) << __func__;
log_.trace() << "called";
CassandraStatement statement{selectLatestLedger_};
CassandraResult result = executeAsyncRead(statement, yield);
if (!result.hasResult())
{
BOOST_LOG_TRIVIAL(error)
log_.error()
<< "CassandraBackend::fetchLatestLedgerSequence - no rows";
return {};
}
@@ -843,13 +863,13 @@ public:
std::uint32_t const sequence,
boost::asio::yield_context& yield) const override
{
BOOST_LOG_TRIVIAL(trace) << __func__;
log_.trace() << "called";
CassandraStatement statement{selectLedgerBySeq_};
statement.bindNextInt(sequence);
CassandraResult result = executeAsyncRead(statement, yield);
if (!result)
{
BOOST_LOG_TRIVIAL(error) << __func__ << " - no rows";
log_.error() << "No rows";
return {};
}
std::vector<unsigned char> header = result.getBytes();
@@ -869,7 +889,7 @@ public:
if (!result.hasResult())
{
BOOST_LOG_TRIVIAL(debug) << __func__ << " - no rows returned";
log_.debug() << "No rows returned";
return {};
}
@@ -916,7 +936,7 @@ public:
std::optional<int64_t>
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_};
statement.bindNextBytes(key, 32);
@@ -924,7 +944,7 @@ public:
if (!result)
{
BOOST_LOG_TRIVIAL(error) << __func__ << " - no rows";
log_.error() << "No rows";
return {};
}
int64_t token = result.getInt64();
@@ -939,14 +959,14 @@ public:
ripple::uint256 const& hash,
boost::asio::yield_context& yield) const override
{
BOOST_LOG_TRIVIAL(trace) << __func__;
log_.trace() << "called";
CassandraStatement statement{selectTransaction_};
statement.bindNextBytes(hash);
CassandraResult result = executeAsyncRead(statement, yield);
if (!result)
{
BOOST_LOG_TRIVIAL(error) << __func__ << " - no rows";
log_.error() << "No rows";
return {};
}
return {
@@ -1036,9 +1056,7 @@ public:
std::unique_lock<std::mutex> lck(throttleMutex_);
if (!canAddRequest())
{
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " : "
<< "Max outstanding requests reached. "
log_.debug() << "Max outstanding requests reached. "
<< "Waiting for other requests to finish";
throttleCv_.wait(lck, [this]() { return canAddRequest(); });
}
@@ -1142,7 +1160,7 @@ public:
ss << "Cassandra sync write error";
ss << ", retrying";
ss << ": " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(warning) << ss.str();
log_.warn() << ss.str();
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
} while (rc != CASS_OK);
@@ -1166,7 +1184,7 @@ public:
ss << "Cassandra sync update error";
ss << ", retrying";
ss << ": " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(warning) << ss.str();
log_.warn() << ss.str();
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
} while (rc != CASS_OK);
@@ -1176,7 +1194,7 @@ public:
CassRow const* row = cass_result_first_row(res);
if (!row)
{
BOOST_LOG_TRIVIAL(error) << "executeSyncUpdate - no rows";
log_.error() << "executeSyncUpdate - no rows";
cass_result_free(res);
return false;
}
@@ -1185,16 +1203,14 @@ public:
if (rc != CASS_OK)
{
cass_result_free(res);
BOOST_LOG_TRIVIAL(error)
<< "executeSyncUpdate - error getting result " << rc << ", "
<< cass_error_desc(rc);
log_.error() << "executeSyncUpdate - error getting result " << rc
<< ", " << cass_error_desc(rc);
return false;
}
cass_result_free(res);
if (success != cass_true && timedOut)
{
BOOST_LOG_TRIVIAL(warning)
<< __func__ << " Update failed, but timedOut is true";
log_.warn() << "Update failed, but timedOut is true";
// if there was a timeout, the update may have succeeded in the
// background on the first attempt. To determine if this happened,
// we query the range from the db, making sure the range is what
@@ -1230,15 +1246,14 @@ public:
if (ec)
{
BOOST_LOG_TRIVIAL(error)
<< "Cannot read async cass_future_error_code";
log_.error() << "Cannot read async cass_future_error_code";
}
if (rc != CASS_OK)
{
std::stringstream ss;
ss << "Cassandra executeAsyncRead error";
ss << ": " << cass_error_desc(rc);
BOOST_LOG_TRIVIAL(error) << ss.str();
log_.error() << ss.str();
}
if (isTimeout(rc))
{
@@ -1261,4 +1276,3 @@ public:
};
} // 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/StringUtilities.h>
@@ -181,4 +199,3 @@ uint256ToString(ripple::uint256 const& uint)
}
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>
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/hardened_hash.h>
@@ -78,4 +96,3 @@ public:
};
} // 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/protocol/AccountID.h>
#include <optional>
@@ -89,4 +108,3 @@ constexpr ripple::uint256 lastKey{
constexpr ripple::uint256 hi192{
"0000000000000000000000000000000000000000000000001111111111111111"};
} // 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>
namespace clio {
@@ -161,11 +180,10 @@ ConfigReader::open(std::filesystem::path path)
}
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();
}
BOOST_LOG_TRIVIAL(warning) << "Using empty default configuration";
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>
@@ -344,7 +362,7 @@ private:
}
else if constexpr (std::is_same_v<Return, double>)
{
if (not value.is_double())
if (not value.is_number())
has_error = true;
}
else if constexpr (
@@ -385,5 +403,3 @@ public:
};
} // 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 <queue>
@@ -144,5 +162,3 @@ typeName<double>()
}
}; // 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 <condition_variable>
#include <mutex>
@@ -171,5 +190,3 @@ getMarkers(size_t numMarkers)
}
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/protocol/STLedgerEntry.h>
#include <boost/asio/strand.hpp>
@@ -5,18 +24,23 @@
#include <boost/beast/ssl.hpp>
#include <boost/json.hpp>
#include <boost/json/src.hpp>
#include <boost/log/trivial.hpp>
#include <backend/DBHelpers.h>
#include <etl/ETLSource.h>
#include <etl/ProbingETLSource.h>
#include <etl/ReportingETL.h>
#include <log/Logger.h>
#include <rpc/RPCHelpers.h>
#include <util/Profiler.h>
#include <thread>
using namespace clio;
void
ForwardCache::freshen()
{
BOOST_LOG_TRIVIAL(trace) << "Freshening ForwardCache";
log_.trace() << "Freshening ForwardCache";
auto numOutstanding =
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 &&
ec != boost::asio::error::connection_refused)
{
BOOST_LOG_TRIVIAL(error)
<< __func__ << " : "
<< "error code = " << ec << " - " << toString();
log_.error() << "error code = " << ec << " - " << toString();
}
else
{
BOOST_LOG_TRIVIAL(warning)
<< __func__ << " : "
<< "error code = " << ec << " - " << toString();
log_.warn() << "error code = " << ec << " - " << toString();
}
// 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_.async_wait([this](auto ec) {
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);
});
}
@@ -168,8 +188,8 @@ PlainETLSource::close(bool startAgain)
[this, startAgain](auto ec) {
if (ec)
{
BOOST_LOG_TRIVIAL(error)
<< __func__ << " async_close : "
log_.error()
<< " async_close : "
<< "error code = " << ec << " - " << toString();
}
closing_ = false;
@@ -213,8 +233,8 @@ SslETLSource::close(bool startAgain)
[this, startAgain](auto ec) {
if (ec)
{
BOOST_LOG_TRIVIAL(error)
<< __func__ << " async_close : "
log_.error()
<< " async_close : "
<< "error code = " << ec << " - " << toString();
}
closing_ = false;
@@ -246,8 +266,7 @@ ETLSourceImpl<Derived>::onResolve(
boost::beast::error_code ec,
boost::asio::ip::tcp::resolver::results_type results)
{
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " : ec = " << ec << " - " << toString();
log_.trace() << "ec = " << ec << " - " << toString();
if (ec)
{
// try again
@@ -269,8 +288,7 @@ PlainETLSource::onConnect(
boost::beast::error_code ec,
boost::asio::ip::tcp::resolver::results_type::endpoint_type endpoint)
{
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " : ec = " << ec << " - " << toString();
log_.trace() << "ec = " << ec << " - " << toString();
if (ec)
{
// start over
@@ -311,8 +329,7 @@ SslETLSource::onConnect(
boost::beast::error_code ec,
boost::asio::ip::tcp::resolver::results_type::endpoint_type endpoint)
{
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " : ec = " << ec << " - " << toString();
log_.trace() << "ec = " << ec << " - " << toString();
if (ec)
{
// start over
@@ -371,8 +388,7 @@ template <class Derived>
void
ETLSourceImpl<Derived>::onHandshake(boost::beast::error_code ec)
{
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " : ec = " << ec << " - " << toString();
log_.trace() << "ec = " << ec << " - " << toString();
if (auto action = hooks_.onConnected(ec);
action == ETLSourceHooks::Action::STOP)
return;
@@ -389,7 +405,7 @@ ETLSourceImpl<Derived>::onHandshake(boost::beast::error_code ec)
{"streams",
{"ledger", "manifests", "validations", "transactions_proposed"}}};
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(
boost::beast::websocket::stream_base::decorator(
@@ -415,8 +431,7 @@ ETLSourceImpl<Derived>::onWrite(
boost::beast::error_code ec,
size_t bytesWritten)
{
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " : ec = " << ec << " - " << toString();
log_.trace() << "ec = " << ec << " - " << toString();
if (ec)
{
// start over
@@ -433,8 +448,7 @@ template <class Derived>
void
ETLSourceImpl<Derived>::onRead(boost::beast::error_code ec, size_t size)
{
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " : ec = " << ec << " - " << toString();
log_.trace() << "ec = " << ec << " - " << toString();
// if error or error reading message, start over
if (ec)
{
@@ -446,8 +460,7 @@ ETLSourceImpl<Derived>::onRead(boost::beast::error_code ec, size_t size)
boost::beast::flat_buffer buffer;
swap(readBuffer_, buffer);
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " : calling async_read - " << toString();
log_.trace() << "calling async_read - " << toString();
derived().ws().async_read(
readBuffer_, [this](auto ec, size_t size) { onRead(ec, size); });
}
@@ -457,7 +470,7 @@ template <class Derived>
bool
ETLSourceImpl<Derived>::handleMessage()
{
BOOST_LOG_TRIVIAL(trace) << __func__ << " : " << toString();
log_.trace() << toString();
setLastMsgTime();
connected_ = true;
@@ -466,9 +479,9 @@ ETLSourceImpl<Derived>::handleMessage()
std::string msg{
static_cast<char const*>(readBuffer_.data().data()),
readBuffer_.size()};
BOOST_LOG_TRIVIAL(trace) << __func__ << msg;
log_.trace() << 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();
uint32_t ledgerIndex = 0;
@@ -487,20 +500,16 @@ ETLSourceImpl<Derived>::handleMessage()
setValidatedRange(
{validatedLedgers.c_str(), validatedLedgers.size()});
}
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " : "
<< "Received a message on ledger "
<< " subscription stream. Message : " << response << " - "
<< toString();
log_.info() << "Received a message on ledger "
<< " subscription stream. Message : " << response
<< " - " << toString();
}
else if (
response.contains("type") && response["type"] == "ledgerClosed")
{
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " : "
<< "Received a message on ledger "
<< " subscription stream. Message : " << response << " - "
<< toString();
log_.info() << "Received a message on ledger "
<< " subscription stream. Message : " << response
<< " - " << toString();
if (response.contains("ledger_index"))
{
ledgerIndex = response["ledger_index"].as_int64();
@@ -539,9 +548,7 @@ ETLSourceImpl<Derived>::handleMessage()
if (ledgerIndex != 0)
{
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " : "
<< "Pushing ledger sequence = " << ledgerIndex << " - "
log_.trace() << "Pushing ledger sequence = " << ledgerIndex << " - "
<< toString();
networkValidatedLedgers_->push(ledgerIndex);
}
@@ -549,13 +556,15 @@ ETLSourceImpl<Derived>::handleMessage()
}
catch (std::exception const& e)
{
BOOST_LOG_TRIVIAL(error) << "Exception in handleMessage : " << e.what();
log_.error() << "Exception in handleMessage : " << e.what();
return false;
}
}
class AsyncCallData
{
clio::Logger log_{"ETL"};
std::unique_ptr<org::xrpl::rpc::v1::GetLedgerDataResponse> cur_;
std::unique_ptr<org::xrpl::rpc::v1::GetLedgerDataResponse> next_;
@@ -585,8 +594,8 @@ public:
unsigned char prefix = marker.data()[0];
BOOST_LOG_TRIVIAL(debug)
<< "Setting up AsyncCallData. marker = " << ripple::strHex(marker)
log_.debug() << "Setting up AsyncCallData. marker = "
<< ripple::strHex(marker)
<< " . prefix = " << ripple::strHex(std::string(1, prefix))
<< " . nextPrefix_ = "
<< ripple::strHex(std::string(1, nextPrefix_));
@@ -609,25 +618,23 @@ public:
bool abort,
bool cacheOnly = false)
{
BOOST_LOG_TRIVIAL(trace) << "Processing response. "
log_.trace() << "Processing response. "
<< "Marker prefix = " << getMarkerPrefix();
if (abort)
{
BOOST_LOG_TRIVIAL(error) << "AsyncCallData aborted";
log_.error() << "AsyncCallData aborted";
return CallStatus::ERRORED;
}
if (!status_.ok())
{
BOOST_LOG_TRIVIAL(error)
<< "AsyncCallData status_ not ok: "
log_.error() << "AsyncCallData status_ not ok: "
<< " code = " << status_.error_code()
<< " message = " << status_.error_message();
return CallStatus::ERRORED;
}
if (!next_->is_unlimited())
{
BOOST_LOG_TRIVIAL(warning)
<< "AsyncCallData is_unlimited is false. Make sure "
log_.warn() << "AsyncCallData is_unlimited is false. Make sure "
"secure_gateway is set correctly at the ETL source";
}
@@ -651,7 +658,7 @@ public:
call(stub, cq);
}
BOOST_LOG_TRIVIAL(trace) << "Writing objects";
log_.trace() << "Writing objects";
std::vector<Backend::LedgerObject> cacheUpdates;
cacheUpdates.reserve(cur_->ledger_objects().objects_size());
for (int i = 0; i < cur_->ledger_objects().objects_size(); ++i)
@@ -681,7 +688,7 @@ public:
}
backend.cache().update(
cacheUpdates, request_.ledger().sequence(), cacheOnly);
BOOST_LOG_TRIVIAL(trace) << "Wrote objects";
log_.trace() << "Wrote objects";
return more ? CallStatus::MORE : CallStatus::DONE;
}
@@ -745,7 +752,7 @@ ETLSourceImpl<Derived>::loadInitialLedger(
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();
for (auto& c : calls)
@@ -764,20 +771,18 @@ ETLSourceImpl<Derived>::loadInitialLedger(
if (!ok)
{
BOOST_LOG_TRIVIAL(error) << "loadInitialLedger - ok is false";
log_.error() << "loadInitialLedger - ok is false";
return false;
// handle cancelled
}
else
{
BOOST_LOG_TRIVIAL(trace)
<< "Marker prefix = " << ptr->getMarkerPrefix();
log_.trace() << "Marker prefix = " << ptr->getMarkerPrefix();
auto result = ptr->process(stub_, cq, *backend_, abort, cacheOnly);
if (result != AsyncCallData::CallStatus::MORE)
{
numFinished++;
BOOST_LOG_TRIVIAL(debug)
<< "Finished a marker. "
log_.debug() << "Finished a marker. "
<< "Current number of finished = " << numFinished;
std::string lastKey = ptr->getLastKey();
if (lastKey.size())
@@ -789,15 +794,13 @@ ETLSourceImpl<Derived>::loadInitialLedger(
}
if (backend_->cache().size() > progress)
{
BOOST_LOG_TRIVIAL(info)
<< "Downloaded " << backend_->cache().size()
log_.info() << "Downloaded " << backend_->cache().size()
<< " records from rippled";
progress += incr;
}
}
}
BOOST_LOG_TRIVIAL(info)
<< __func__ << " - finished loadInitialLedger. cache size = "
log_.info() << "Finished loadInitialLedger. cache size = "
<< backend_->cache().size();
size_t numWrites = 0;
if (!abort)
@@ -805,20 +808,22 @@ ETLSourceImpl<Derived>::loadInitialLedger(
backend_->cache().setFull();
if (!cacheOnly)
{
auto start = std::chrono::system_clock::now();
auto seconds = util::timed<std::chrono::seconds>([&]() {
for (auto& key : edgeKeys)
{
BOOST_LOG_TRIVIAL(debug)
<< __func__
<< " writing edge key = " << ripple::strHex(key);
log_.debug()
<< "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));
std::move(key),
sequence,
uint256ToString(succ->key));
}
ripple::uint256 prev = Backend::firstKey;
while (auto cur = backend_->cache().getSuccessor(prev, sequence))
while (auto cur =
backend_->cache().getSuccessor(prev, sequence))
{
assert(cur);
if (prev == Backend::firstKey)
@@ -840,8 +845,7 @@ ETLSourceImpl<Derived>::loadInitialLedger(
assert(succ);
if (succ->key == cur->key)
{
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " Writing book successor = "
log_.debug() << "Writing book successor = "
<< ripple::strHex(base) << " - "
<< ripple::strHex(cur->key);
@@ -855,8 +859,8 @@ ETLSourceImpl<Derived>::loadInitialLedger(
}
prev = std::move(cur->key);
if (numWrites % 100000 == 0 && numWrites != 0)
BOOST_LOG_TRIVIAL(info) << __func__ << " Wrote "
<< numWrites << " book successors";
log_.info()
<< "Wrote " << numWrites << " book successors";
}
backend_->writeSuccessor(
@@ -865,13 +869,9 @@ ETLSourceImpl<Derived>::loadInitialLedger(
uint256ToString(Backend::lastKey));
++numWrites;
auto end = std::chrono::system_clock::now();
auto seconds =
std::chrono::duration_cast<std::chrono::seconds>(end - start)
.count();
BOOST_LOG_TRIVIAL(info)
<< __func__
<< " - Looping through cache and submitting all writes took "
});
log_.info()
<< "Looping through cache and submitting all writes took "
<< seconds
<< " seconds. numWrites = " << std::to_string(numWrites);
}
@@ -902,8 +902,7 @@ ETLSourceImpl<Derived>::fetchLedger(
grpc::Status status = stub_->GetLedger(&context, request, &response);
if (status.ok() && !response.is_unlimited())
{
BOOST_LOG_TRIVIAL(warning)
<< "ETLSourceImpl::fetchLedger - is_unlimited is "
log_.warn() << "ETLSourceImpl::fetchLedger - is_unlimited is "
"false. Make sure secure_gateway is set "
"correctly on the ETL source. source = "
<< toString() << " status = " << status.error_message();
@@ -951,8 +950,7 @@ ETLLoadBalancer::ETLLoadBalancer(
entry, ioContext, backend, subscriptions, nwvl, *this);
sources_.push_back(std::move(source));
BOOST_LOG_TRIVIAL(info) << __func__ << " : added etl source - "
<< sources_.back()->toString();
log_.info() << "Added etl source - " << sources_.back()->toString();
}
}
@@ -965,7 +963,7 @@ ETLLoadBalancer::loadInitialLedger(uint32_t sequence, bool cacheOnly)
source->loadInitialLedger(sequence, downloadRanges_, cacheOnly);
if (!res)
{
BOOST_LOG_TRIVIAL(error) << "Failed to download initial ledger."
log_.error() << "Failed to download initial ledger."
<< " Sequence = " << sequence
<< " source = " << source->toString();
}
@@ -982,26 +980,24 @@ ETLLoadBalancer::fetchLedger(
{
org::xrpl::rpc::v1::GetLedgerResponse response;
bool success = execute(
[&response, ledgerSequence, getObjects, getObjectNeighbors](
[&response, ledgerSequence, getObjects, getObjectNeighbors, log = log_](
auto& source) {
auto [status, data] = source->fetchLedger(
ledgerSequence, getObjects, getObjectNeighbors);
response = std::move(data);
if (status.ok() && response.validated())
{
BOOST_LOG_TRIVIAL(info)
<< "Successfully fetched ledger = " << ledgerSequence
log.info() << "Successfully fetched ledger = " << ledgerSequence
<< " from source = " << source->toString();
return true;
}
else
{
BOOST_LOG_TRIVIAL(warning)
<< "Error getting ledger = " << ledgerSequence
<< " Reply : " << response.DebugString()
<< " error_code : " << status.error_code()
<< " error_msg : " << status.error_message()
<< " source = " << source->toString();
log.warn() << "Error getting ledger = " << ledgerSequence
<< ", Reply: " << response.DebugString()
<< ", error_code: " << status.error_code()
<< ", error_msg: " << status.error_message()
<< ", source = " << source->toString();
return false;
}
},
@@ -1042,7 +1038,7 @@ ETLSourceImpl<Derived>::forwardToRippled(
{
if (auto resp = forwardCache_.get(request); resp)
{
BOOST_LOG_TRIVIAL(debug) << "request hit forwardCache";
log_.debug() << "request hit forwardCache";
return resp;
}
@@ -1056,14 +1052,13 @@ ETLSourceImpl<Derived>::requestFromRippled(
std::string const& clientIp,
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);
boost::json::object response;
if (!connected_)
{
BOOST_LOG_TRIVIAL(error)
<< "Attempted to proxy but failed to connect to tx";
log_.error() << "Attempted to proxy but failed to connect to tx";
return {};
}
namespace beast = boost::beast; // from <boost/beast.hpp>
@@ -1077,7 +1072,7 @@ ETLSourceImpl<Derived>::requestFromRippled(
// These objects perform our I/O
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_);
// Look up the domain name
@@ -1087,7 +1082,7 @@ ETLSourceImpl<Derived>::requestFromRippled(
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
ws->next_layer().async_connect(results, yield[ec]);
if (ec)
@@ -1106,15 +1101,15 @@ ETLSourceImpl<Derived>::requestFromRippled(
" websocket-client-coro");
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
ws->async_handshake(ip_, "/", yield[ec]);
if (ec)
return {};
BOOST_LOG_TRIVIAL(trace) << "Sending request";
log_.trace() << "Sending request";
// Send the message
ws->async_write(
net::buffer(boost::json::serialize(request)), yield[ec]);
@@ -1132,11 +1127,11 @@ ETLSourceImpl<Derived>::requestFromRippled(
if (!parsed.is_object())
{
BOOST_LOG_TRIVIAL(error)
<< "Error parsing response: " << std::string{begin, end};
log_.error() << "Error parsing response: "
<< std::string{begin, end};
return {};
}
BOOST_LOG_TRIVIAL(trace) << "Successfully forward request";
log_.trace() << "Successfully forward request";
response = parsed.as_object();
@@ -1145,7 +1140,7 @@ ETLSourceImpl<Derived>::requestFromRippled(
}
catch (std::exception const& e)
{
BOOST_LOG_TRIVIAL(error) << "Encountered exception : " << e.what();
log_.error() << "Encountered exception : " << e.what();
return {};
}
}
@@ -1162,45 +1157,36 @@ ETLLoadBalancer::execute(Func f, uint32_t ledgerSequence)
{
auto& source = sources_[sourceIdx];
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " : "
<< "Attempting to execute func. ledger sequence = "
log_.debug() << "Attempting to execute func. ledger sequence = "
<< ledgerSequence << " - source = " << source->toString();
if (source->hasLedger(ledgerSequence) || true)
{
bool res = f(source);
if (res)
{
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " : "
<< "Successfully executed func at source = "
log_.debug() << "Successfully executed func at source = "
<< source->toString()
<< " - ledger sequence = " << ledgerSequence;
break;
}
else
{
BOOST_LOG_TRIVIAL(warning)
<< __func__ << " : "
<< "Failed to execute func at source = "
log_.warn() << "Failed to execute func at source = "
<< source->toString()
<< " - ledger sequence = " << ledgerSequence;
}
}
else
{
BOOST_LOG_TRIVIAL(warning)
<< __func__ << " : "
<< "Ledger not present at source = " << source->toString()
log_.warn() << "Ledger not present at source = "
<< source->toString()
<< " - ledger sequence = " << ledgerSequence;
}
sourceIdx = (sourceIdx + 1) % sources_.size();
numAttempts++;
if (numAttempts % sources_.size() == 0)
{
BOOST_LOG_TRIVIAL(error)
<< __func__ << " : "
<< "Error executing function "
log_.error() << "Error executing function "
<< " - ledger sequence = " << ledgerSequence
<< " - Tried all sources. Sleeping and trying again";
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/asio.hpp>
@@ -8,14 +35,6 @@
#include <boost/beast/ssl.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 ETLSource;
class ProbingETLSource;
@@ -32,6 +51,7 @@ class ForwardCache
{
using response_type = std::optional<boost::json::object>;
clio::Logger log_{"ETL"};
mutable std::atomic_bool stopping_ = false;
mutable std::shared_mutex mtx_;
std::unordered_map<std::string, response_type> latestForwarded_;
@@ -127,6 +147,9 @@ public:
{
}
protected:
clio::Logger log_{"ETL"};
private:
friend ForwardCache;
friend ProbingETLSource;
@@ -161,7 +184,7 @@ class ETLSourceImpl : public ETLSource
std::vector<std::pair<uint32_t, uint32_t>> validatedLedgers_;
std::string validatedLedgersRaw_;
std::string validatedLedgersRaw_{"N/A"};
std::shared_ptr<NetworkValidatedLedgers> networkValidatedLedgers_;
@@ -218,7 +241,7 @@ protected:
void
run() override
{
BOOST_LOG_TRIVIAL(trace) << __func__ << " : " << toString();
log_.trace() << toString();
auto const host = ip_;
auto const port = wsPort_;
@@ -292,13 +315,11 @@ public:
stub_ = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
grpc::CreateCustomChannel(
ss.str(), grpc::InsecureChannelCredentials(), chArgs));
BOOST_LOG_TRIVIAL(debug)
<< "Made stub for remote = " << toString();
log_.debug() << "Made stub for remote = " << toString();
}
catch (std::exception const& e)
{
BOOST_LOG_TRIVIAL(debug)
<< "Exception while creating stub = " << e.what()
log_.debug() << "Exception while creating stub = " << e.what()
<< " . Remote = " << toString();
}
}
@@ -371,7 +392,6 @@ public:
getValidatedRange() const
{
std::lock_guard lck(mtx_);
return validatedLedgersRaw_;
}
@@ -389,9 +409,8 @@ public:
std::string
toString() const override
{
return "{ validated_ledger : " + getValidatedRange() +
" , ip : " + ip_ + " , web socket port : " + wsPort_ +
", grpc port : " + grpcPort_ + " }";
return "{validated_ledger: " + getValidatedRange() + ", ip: " + ip_ +
", web socket port: " + wsPort_ + ", grpc port: " + grpcPort_ + "}";
}
boost::json::object
@@ -592,8 +611,8 @@ public:
class ETLLoadBalancer
{
private:
clio::Logger log_{"ETL"};
std::vector<std::unique_ptr<ETLSource>> sources_;
std::uint32_t downloadRanges_ = 16;
public:
@@ -703,5 +722,3 @@ private:
bool
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/protocol/STBase.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)};
std::stringstream msg;
msg << __func__ << " - unexpected NFTokenMint data in tx "
<< sttx.getTransactionID();
msg << " - unexpected NFTokenMint data in tx " << sttx.getTransactionID();
throw std::runtime_error(msg.str());
}
@@ -173,7 +191,7 @@ getNFTokenBurnData(ripple::TxMeta const& txMeta, ripple::STTx const& sttx)
}
std::stringstream msg;
msg << __func__ << " - could not determine owner at burntime for tx "
msg << " - could not determine owner at burntime for tx "
<< sttx.getTransactionID();
throw std::runtime_error(msg.str());
}
@@ -198,7 +216,7 @@ getNFTokenAcceptOfferData(
if (affectedBuyOffer == txMeta.getNodes().end())
{
std::stringstream msg;
msg << __func__ << " - unexpected NFTokenAcceptOffer data in tx "
msg << " - unexpected NFTokenAcceptOffer data in tx "
<< sttx.getTransactionID();
throw std::runtime_error(msg.str());
}
@@ -228,7 +246,7 @@ getNFTokenAcceptOfferData(
if (affectedSellOffer == txMeta.getNodes().end())
{
std::stringstream msg;
msg << __func__ << " - unexpected NFTokenAcceptOffer data in tx "
msg << " - unexpected NFTokenAcceptOffer data in tx "
<< sttx.getTransactionID();
throw std::runtime_error(msg.str());
}
@@ -278,7 +296,7 @@ getNFTokenAcceptOfferData(
}
std::stringstream msg;
msg << __func__ << " - unexpected NFTokenAcceptOffer data in tx "
msg << " - unexpected NFTokenAcceptOffer data in tx "
<< sttx.getTransactionID();
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 <log/Logger.h>
using namespace clio;
ProbingETLSource::ProbingETLSource(
clio::Config const& config,
@@ -85,7 +107,8 @@ std::string
ProbingETLSource::toString() const
{
if (!currentSrc_)
return "{ probing }";
return "{probing... ws: " + plainSrc_->toString() +
", wss: " + sslSrc_->toString() + "}";
return currentSrc_->toString();
}
@@ -148,8 +171,7 @@ ProbingETLSource::make_SSLHooks() noexcept
{
plainSrc_->pause();
currentSrc_ = sslSrc_;
BOOST_LOG_TRIVIAL(info)
<< "Selected WSS as the main source: "
log_.info() << "Selected WSS as the main source: "
<< currentSrc_->toString();
}
return ETLSourceHooks::Action::PROCEED;
@@ -179,8 +201,7 @@ ProbingETLSource::make_PlainHooks() noexcept
{
sslSrc_->pause();
currentSrc_ = plainSrc_;
BOOST_LOG_TRIVIAL(info)
<< "Selected Plain WS as the main source: "
log_.info() << "Selected Plain WS as the main source: "
<< currentSrc_->toString();
}
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/beast/core.hpp>
@@ -11,6 +29,7 @@
#include <config/Config.h>
#include <etl/ETLSource.h>
#include <log/Logger.h>
/// This ETLSource implementation attempts to connect over both secure websocket
/// and plain websocket. First to connect pauses the other and the probing is
@@ -18,6 +37,8 @@
/// connection the probing is kickstarted again.
class ProbingETLSource : public ETLSource
{
clio::Logger log_{"ETL"};
std::mutex mtx_;
boost::asio::ssl::context sslCtx_;
std::shared_ptr<ETLSource> sslSrc_;
@@ -89,5 +110,3 @@ private:
ETLSourceHooks
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/beast/core/CurrentThreadName.h>
#include <backend/DBHelpers.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/ip/tcp.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <cstdlib>
#include <iostream>
#include <string>
#include <subscriptions/SubscriptionManager.h>
#include <thread>
#include <variant>
namespace detail {
using namespace clio;
namespace clio::detail {
/// Convenience function for printing out basic ledger info
std::string
toString(ripple::LedgerInfo const& info)
@@ -26,7 +51,7 @@ toString(ripple::LedgerInfo const& info)
<< " ParentHash : " << strHex(info.parentHash) << " }";
return ss.str();
}
} // namespace detail
} // namespace clio::detail
FormattedTransactionsData
ReportingETL::insertTransactions(
@@ -43,9 +68,7 @@ ReportingETL::insertTransactions(
ripple::SerialIter it{raw->data(), raw->size()};
ripple::STTx sttx{it};
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " : "
<< "Inserting transaction = " << sttx.getTransactionID();
log_.trace() << "Inserting transaction = " << sttx.getTransactionID();
ripple::TxMeta txMeta{
sttx.getTransactionID(), ledger.seq, txn.metadata_blob()};
@@ -97,8 +120,7 @@ ReportingETL::loadInitialLedger(uint32_t startingSequence)
auto rng = backend_->hardFetchLedgerRangeNoThrow();
if (rng)
{
BOOST_LOG_TRIVIAL(fatal) << __func__ << " : "
<< "Database is not empty";
log_.fatal() << "Database is not empty";
assert(false);
return {};
}
@@ -114,56 +136,52 @@ ReportingETL::loadInitialLedger(uint32_t startingSequence)
ripple::LedgerInfo lgrInfo =
deserializeHeader(ripple::makeSlice(ledgerData->ledger_header()));
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " : "
<< "Deserialized ledger header. " << detail::toString(lgrInfo);
auto start = std::chrono::system_clock::now();
log_.debug() << "Deserialized ledger header. " << detail::toString(lgrInfo);
auto timeDiff = util::timed<std::chrono::duration<double>>([&]() {
backend_->startWrites();
BOOST_LOG_TRIVIAL(debug) << __func__ << " started writes";
log_.debug() << "Started writes";
backend_->writeLedger(
lgrInfo, std::move(*ledgerData->mutable_ledger_header()));
BOOST_LOG_TRIVIAL(debug) << __func__ << " wrote ledger";
log_.debug() << "Wrote ledger";
FormattedTransactionsData insertTxResult =
insertTransactions(lgrInfo, *ledgerData);
BOOST_LOG_TRIVIAL(debug) << __func__ << " inserted txns";
log_.debug() << "Inserted txns";
// download the full account state map. This function downloads full 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
// download the full account state map. This function downloads full
// 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";
log_.debug() << "Loaded initial ledger";
if (!stopping_)
{
backend_->writeAccountTransactions(
std::move(insertTxResult.accountTxData));
backend_->writeNFTs(std::move(insertTxResult.nfTokensData));
backend_->writeNFTTransactions(std::move(insertTxResult.nfTokenTxData));
backend_->writeNFTTransactions(
std::move(insertTxResult.nfTokenTxData));
}
backend_->finishWrites(startingSequence);
auto end = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(debug) << "Time to download and store ledger = "
<< ((end - start).count()) / 1000000000.0;
});
log_.debug() << "Time to download and store ledger = " << timeDiff;
return lgrInfo;
}
void
ReportingETL::publishLedger(ripple::LedgerInfo const& lgrInfo)
{
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " - Publishing ledger " << std::to_string(lgrInfo.seq);
log_.info() << "Publishing ledger " << std::to_string(lgrInfo.seq);
if (!writing_)
{
BOOST_LOG_TRIVIAL(debug) << __func__ << " - Updating cache";
log_.info() << "Updating cache";
std::vector<Backend::LedgerObject> diff =
Backend::synchronousAndRetryOnTimeout([&](auto yield) {
@@ -205,11 +223,10 @@ ReportingETL::publishLedger(ripple::LedgerInfo const& lgrInfo)
subscriptions_->pubBookChanges(lgrInfo, transactions);
BOOST_LOG_TRIVIAL(info) << __func__ << " - Published ledger "
<< std::to_string(lgrInfo.seq);
log_.info() << "Published ledger " << std::to_string(lgrInfo.seq);
}
else
BOOST_LOG_TRIVIAL(info) << __func__ << " - Skipping publishing ledger "
log_.info() << "Skipping publishing ledger "
<< std::to_string(lgrInfo.seq);
setLastPublish();
}
@@ -219,9 +236,7 @@ ReportingETL::publishLedger(
uint32_t ledgerSequence,
std::optional<uint32_t> maxAttempts)
{
BOOST_LOG_TRIVIAL(info)
<< __func__ << " : "
<< "Attempting to publish ledger = " << ledgerSequence;
log_.info() << "Attempting to publish ledger = " << ledgerSequence;
size_t numAttempts = 0;
while (!stopping_)
{
@@ -229,17 +244,15 @@ ReportingETL::publishLedger(
if (!range || range->maxSequence < ledgerSequence)
{
BOOST_LOG_TRIVIAL(debug) << __func__ << " : "
<< "Trying to publish. Could not find "
log_.debug() << "Trying to publish. Could not find "
"ledger with sequence = "
<< ledgerSequence;
// We try maxAttempts times to publish the ledger, waiting one
// second in between each attempt.
if (maxAttempts && numAttempts >= maxAttempts)
{
BOOST_LOG_TRIVIAL(debug) << __func__ << " : "
<< "Failed to publish ledger after "
<< numAttempts << " attempts.";
log_.debug() << "Failed to publish ledger after " << numAttempts
<< " attempts.";
return false;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
@@ -264,25 +277,19 @@ ReportingETL::publishLedger(
std::optional<org::xrpl::rpc::v1::GetLedgerResponse>
ReportingETL::fetchLedgerData(uint32_t seq)
{
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " : "
<< "Attempting to fetch ledger with sequence = " << seq;
log_.debug() << "Attempting to fetch ledger with sequence = " << seq;
std::optional<org::xrpl::rpc::v1::GetLedgerResponse> response =
loadBalancer_->fetchLedger(seq, false, false);
if (response)
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " : "
<< "GetLedger reply = " << response->DebugString();
log_.trace() << "GetLedger reply = " << response->DebugString();
return response;
}
std::optional<org::xrpl::rpc::v1::GetLedgerResponse>
ReportingETL::fetchLedgerDataAndDiff(uint32_t seq)
{
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " : "
<< "Attempting to fetch ledger with sequence = " << seq;
log_.debug() << "Attempting to fetch ledger with sequence = " << seq;
std::optional<org::xrpl::rpc::v1::GetLedgerResponse> response =
loadBalancer_->fetchLedger(
@@ -291,45 +298,34 @@ ReportingETL::fetchLedgerDataAndDiff(uint32_t seq)
!backend_->cache().isFull() ||
backend_->cache().latestLedgerSequence() >= seq);
if (response)
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " : "
<< "GetLedger reply = " << response->DebugString();
log_.trace() << "GetLedger reply = " << response->DebugString();
return response;
}
std::pair<ripple::LedgerInfo, bool>
ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
{
BOOST_LOG_TRIVIAL(debug) << __func__ << " : "
<< "Beginning ledger update";
log_.debug() << "Beginning ledger update";
ripple::LedgerInfo lgrInfo =
deserializeHeader(ripple::makeSlice(rawData.ledger_header()));
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " : "
<< "Deserialized ledger header. " << detail::toString(lgrInfo);
log_.debug() << "Deserialized ledger header. " << detail::toString(lgrInfo);
backend_->startWrites();
BOOST_LOG_TRIVIAL(debug) << __func__ << " : "
<< "started writes";
log_.debug() << "started writes";
backend_->writeLedger(lgrInfo, std::move(*rawData.mutable_ledger_header()));
BOOST_LOG_TRIVIAL(debug) << __func__ << " : "
<< "wrote ledger header";
log_.debug() << "wrote ledger header";
// Write successor info, if included from rippled
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()))
{
auto firstBook = std::move(*obj.mutable_first_book());
if (!firstBook.size())
firstBook = uint256ToString(Backend::lastKey);
BOOST_LOG_TRIVIAL(debug) << __func__ << " writing book successor "
log_.debug() << "writing book successor "
<< ripple::strHex(obj.book_base()) << " - "
<< ripple::strHex(firstBook);
@@ -352,9 +348,7 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
if (obj.mod_type() ==
org::xrpl::rpc::v1::RawLedgerObject::DELETED)
{
BOOST_LOG_TRIVIAL(debug)
<< __func__
<< " modifying successors for deleted object "
log_.debug() << "Modifying successors for deleted object "
<< ripple::strHex(obj.key()) << " - "
<< ripple::strHex(*predPtr) << " - "
<< ripple::strHex(*succPtr);
@@ -364,8 +358,7 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
}
else
{
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " adding successor for new object "
log_.debug() << "adding successor for new object "
<< ripple::strHex(obj.key()) << " - "
<< ripple::strHex(*predPtr) << " - "
<< ripple::strHex(*succPtr);
@@ -381,8 +374,7 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
}
}
else
BOOST_LOG_TRIVIAL(debug) << __func__ << " object modified "
<< ripple::strHex(obj.key());
log_.debug() << "object modified " << ripple::strHex(obj.key());
}
}
std::vector<Backend::LedgerObject> cacheUpdates;
@@ -396,15 +388,13 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
assert(key);
cacheUpdates.push_back(
{*key, {obj.mutable_data()->begin(), obj.mutable_data()->end()}});
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " key = " << ripple::strHex(*key)
log_.debug() << "key = " << ripple::strHex(*key)
<< " - mod type = " << obj.mod_type();
if (obj.mod_type() != org::xrpl::rpc::v1::RawLedgerObject::MODIFIED &&
!rawData.object_neighbors_included())
{
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " object neighbors not included. using cache";
log_.debug() << "object neighbors not included. using cache";
if (!backend_->cache().isFull() ||
backend_->cache().latestLedgerSequence() != lgrInfo.seq - 1)
throw std::runtime_error(
@@ -423,9 +413,7 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
checkBookBase = isBookDir(*key, *blob);
if (checkBookBase)
{
BOOST_LOG_TRIVIAL(debug)
<< __func__
<< " is book dir. key = " << ripple::strHex(*key);
log_.debug() << "Is book dir. key = " << ripple::strHex(*key);
auto bookBase = getBookBase(*key);
auto oldFirstDir =
backend_->cache().getSuccessor(bookBase, lgrInfo.seq - 1);
@@ -435,9 +423,8 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
if ((isDeleted && key == oldFirstDir->key) ||
(!isDeleted && key < oldFirstDir->key))
{
BOOST_LOG_TRIVIAL(debug)
<< __func__
<< " Need to recalculate book base successor. base = "
log_.debug()
<< "Need to recalculate book base successor. base = "
<< ripple::strHex(bookBase)
<< " - key = " << ripple::strHex(*key)
<< " - isDeleted = " << isDeleted
@@ -458,8 +445,7 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
// rippled didn't send successor information, so use our cache
if (!rawData.object_neighbors_included())
{
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " object neighbors not included. using cache";
log_.debug() << "object neighbors not included. using cache";
if (!backend_->cache().isFull() ||
backend_->cache().latestLedgerSequence() != lgrInfo.seq)
throw std::runtime_error(
@@ -477,8 +463,7 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
ub = {Backend::lastKey, {}};
if (obj.blob.size() == 0)
{
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " writing successor for deleted object "
log_.debug() << "writing successor for deleted object "
<< ripple::strHex(obj.key) << " - "
<< ripple::strHex(lb->key) << " - "
<< ripple::strHex(ub->key);
@@ -499,8 +484,7 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
lgrInfo.seq,
uint256ToString(ub->key));
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " writing successor for new object "
log_.debug() << "writing successor for new object "
<< ripple::strHex(lb->key) << " - "
<< ripple::strHex(obj.key) << " - "
<< ripple::strHex(ub->key);
@@ -516,10 +500,9 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
lgrInfo.seq,
uint256ToString(succ->key));
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " Updating book successor "
<< ripple::strHex(base) << " - "
<< ripple::strHex(succ->key);
log_.debug()
<< "Updating book successor " << ripple::strHex(base)
<< " - " << ripple::strHex(succ->key);
}
else
{
@@ -528,42 +511,31 @@ ReportingETL::buildNextLedger(org::xrpl::rpc::v1::GetLedgerResponse& rawData)
lgrInfo.seq,
uint256ToString(Backend::lastKey));
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " Updating book successor "
<< ripple::strHex(base) << " - "
<< ripple::strHex(Backend::lastKey);
log_.debug()
<< "Updating book successor " << ripple::strHex(base)
<< " - " << ripple::strHex(Backend::lastKey);
}
}
}
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " : "
log_.debug()
<< "Inserted/modified/deleted all objects. Number of objects = "
<< rawData.ledger_objects().objects_size();
FormattedTransactionsData insertTxResult =
insertTransactions(lgrInfo, rawData);
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " : "
<< "Inserted all transactions. Number of transactions = "
log_.debug() << "Inserted all transactions. Number of transactions = "
<< rawData.transactions_list().transactions_size();
backend_->writeAccountTransactions(std::move(insertTxResult.accountTxData));
backend_->writeNFTs(std::move(insertTxResult.nfTokensData));
backend_->writeNFTTransactions(std::move(insertTxResult.nfTokenTxData));
BOOST_LOG_TRIVIAL(debug) << __func__ << " : "
<< "wrote account_tx";
auto start = std::chrono::system_clock::now();
log_.debug() << "wrote account_tx";
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};
}
@@ -596,8 +568,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
* optional, returns.
*/
BOOST_LOG_TRIVIAL(debug) << __func__ << " : "
<< "Starting etl pipeline";
log_.debug() << "Starting etl pipeline";
writing_ = true;
auto rng = backend_->hardFetchLedgerRangeNoThrow();
@@ -647,12 +618,10 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
currentSequence) &&
!writeConflict && !isStopping())
{
auto start = std::chrono::system_clock::now();
std::optional<org::xrpl::rpc::v1::GetLedgerResponse>
fetchResponse{fetchLedgerDataAndDiff(currentSequence)};
auto end = std::chrono::system_clock::now();
auto time = ((end - start).count()) / 1000000000.0;
auto [fetchResponse, time] =
util::timed<std::chrono::duration<double>>([&]() {
return fetchLedgerDataAndDiff(currentSequence);
});
totalTime += time;
// if the fetch is unsuccessful, stop. fetchLedger only
@@ -670,8 +639,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
fetchResponse->transactions_list().transactions_size() /
time;
BOOST_LOG_TRIVIAL(info)
<< "Extract phase time = " << time
log_.info() << "Extract phase time = " << time
<< " . Extract phase tps = " << tps
<< " . Avg extract time = "
<< totalTime / (currentSequence - startSequence + 1)
@@ -720,7 +688,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
auto duration = ((end - start).count()) / 1000000000.0;
if (success)
BOOST_LOG_TRIVIAL(info)
log_.info()
<< "Load phase of etl : "
<< "Successfully wrote ledger! Ledger info: "
<< detail::toString(lgrInfo) << ". txn count = " << numTxns
@@ -729,7 +697,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
<< ". load txns per second = " << numTxns / duration
<< ". load objs per second = " << numObjects / duration;
else
BOOST_LOG_TRIVIAL(error)
log_.error()
<< "Error writing ledger. " << detail::toString(lgrInfo);
// success is false if the ledger was already written
if (success)
@@ -747,7 +715,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
{
deleting_ = true;
ioContext_.post([this, &minSequence]() {
BOOST_LOG_TRIVIAL(info) << "Running online delete";
log_.info() << "Running online delete";
Backend::synchronous(
[&](boost::asio::yield_context& yield) {
@@ -755,7 +723,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
*onlineDeleteInterval_, yield);
});
BOOST_LOG_TRIVIAL(info) << "Finished online delete";
log_.info() << "Finished online delete";
auto rng = backend_->fetchLedgerRange();
minSequence = rng->minSequence;
deleting_ = false;
@@ -774,13 +742,12 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
for (auto& t : extractors)
t.join();
auto end = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(debug)
<< "Extracted and wrote " << *lastPublishedSequence - startSequence
<< " in " << ((end - begin).count()) / 1000000000.0;
log_.debug() << "Extracted and wrote "
<< *lastPublishedSequence - startSequence << " in "
<< ((end - begin).count()) / 1000000000.0;
writing_ = false;
BOOST_LOG_TRIVIAL(debug) << __func__ << " : "
<< "Stopping etl pipeline";
log_.debug() << "Stopping etl pipeline";
return lastPublishedSequence;
}
@@ -801,38 +768,32 @@ ReportingETL::monitor()
auto rng = backend_->hardFetchLedgerRangeNoThrow();
if (!rng)
{
BOOST_LOG_TRIVIAL(info) << __func__ << " : "
<< "Database is empty. Will download a ledger "
log_.info() << "Database is empty. Will download a ledger "
"from the network.";
std::optional<ripple::LedgerInfo> ledger;
if (startSequence_)
{
BOOST_LOG_TRIVIAL(info)
<< __func__ << " : "
<< "ledger sequence specified in config. "
log_.info() << "ledger sequence specified in config. "
<< "Will begin ETL process starting with ledger "
<< *startSequence_;
ledger = loadInitialLedger(*startSequence_);
}
else
{
BOOST_LOG_TRIVIAL(info)
<< __func__ << " : "
log_.info()
<< "Waiting for next ledger to be validated by network...";
std::optional<uint32_t> mostRecentValidated =
networkValidatedLedgers_->getMostRecent();
if (mostRecentValidated)
{
BOOST_LOG_TRIVIAL(info) << __func__ << " : "
<< "Ledger " << *mostRecentValidated
log_.info() << "Ledger " << *mostRecentValidated
<< " has been validated. "
<< "Downloading...";
ledger = loadInitialLedger(*mostRecentValidated);
}
else
{
BOOST_LOG_TRIVIAL(info) << __func__ << " : "
<< "The wait for the next validated "
log_.info() << "The wait for the next validated "
<< "ledger has been aborted. "
<< "Exiting monitor loop";
return;
@@ -842,8 +803,7 @@ ReportingETL::monitor()
rng = backend_->hardFetchLedgerRangeNoThrow();
else
{
BOOST_LOG_TRIVIAL(error)
<< __func__ << " : "
log_.error()
<< "Failed to load initial ledger. Exiting monitor loop";
return;
}
@@ -852,20 +812,17 @@ ReportingETL::monitor()
{
if (startSequence_)
{
BOOST_LOG_TRIVIAL(warning)
log_.warn()
<< "start sequence specified but db is already populated";
}
BOOST_LOG_TRIVIAL(info)
<< __func__ << " : "
log_.info()
<< "Database already populated. Picking up from the tip of history";
loadCache(rng->maxSequence);
}
assert(rng);
uint32_t nextSequence = rng->maxSequence + 1;
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " : "
<< "Database is populated. "
log_.debug() << "Database is populated. "
<< "Starting monitor loop. sequence = " << nextSequence;
while (true)
{
@@ -878,9 +835,7 @@ ReportingETL::monitor()
else if (networkValidatedLedgers_->waitUntilValidatedByNetwork(
nextSequence, 1000))
{
BOOST_LOG_TRIVIAL(info)
<< __func__ << " : "
<< "Ledger with sequence = " << nextSequence
log_.info() << "Ledger with sequence = " << nextSequence
<< " has been validated by the network. "
<< "Attempting to find in database and publish";
// Attempt to take over responsibility of ETL writer after 10 failed
@@ -893,17 +848,13 @@ ReportingETL::monitor()
bool success = publishLedger(nextSequence, timeoutSeconds);
if (!success)
{
BOOST_LOG_TRIVIAL(warning)
<< __func__ << " : "
<< "Failed to publish ledger with sequence = "
log_.warn() << "Failed to publish ledger with sequence = "
<< nextSequence << " . Beginning ETL";
// doContinousETLPipelined returns the most recent sequence
// published empty optional if no sequence was published
std::optional<uint32_t> lastPublished =
runETLPipeline(nextSequence, extractorThreads_);
BOOST_LOG_TRIVIAL(info)
<< __func__ << " : "
<< "Aborting ETL. Falling back to publishing";
log_.info() << "Aborting ETL. Falling back to publishing";
// if no ledger was published, don't increment nextSequence
if (lastPublished)
nextSequence = *lastPublished + 1;
@@ -920,8 +871,8 @@ ReportingETL::loadCacheFromClioPeer(
std::string const& port,
boost::asio::yield_context& yield)
{
BOOST_LOG_TRIVIAL(info)
<< "Loading cache from peer. ip = " << ip << " . port = " << port;
log_.info() << "Loading cache from peer. ip = " << ip
<< " . port = " << port;
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace websocket = beast::websocket; // from
@@ -933,7 +884,7 @@ ReportingETL::loadCacheFromClioPeer(
// These objects perform our I/O
tcp::resolver resolver{ioContext_};
BOOST_LOG_TRIVIAL(trace) << __func__ << " Creating websocket";
log_.trace() << "Creating websocket";
auto ws =
std::make_unique<websocket::stream<beast::tcp_stream>>(ioContext_);
@@ -942,14 +893,13 @@ ReportingETL::loadCacheFromClioPeer(
if (ec)
return {};
BOOST_LOG_TRIVIAL(trace) << __func__ << " Connecting websocket";
log_.trace() << "Connecting websocket";
// Make the connection on the IP address we get from a lookup
ws->next_layer().async_connect(results, yield[ec]);
if (ec)
return false;
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " Performing websocket handshake";
log_.trace() << "Performing websocket handshake";
// Perform the websocket handshake
ws->async_handshake(ip, "/", yield[ec]);
if (ec)
@@ -957,7 +907,7 @@ ReportingETL::loadCacheFromClioPeer(
std::optional<boost::json::value> marker;
BOOST_LOG_TRIVIAL(trace) << __func__ << " Sending request";
log_.trace() << "Sending request";
auto getRequest = [&](auto marker) {
boost::json::object request = {
{"command", "ledger_data"},
@@ -981,8 +931,7 @@ ReportingETL::loadCacheFromClioPeer(
yield[ec]);
if (ec)
{
BOOST_LOG_TRIVIAL(error)
<< __func__ << " error writing = " << ec.message();
log_.error() << "error writing = " << ec.message();
return false;
}
@@ -990,8 +939,7 @@ ReportingETL::loadCacheFromClioPeer(
ws->async_read(buffer, yield[ec]);
if (ec)
{
BOOST_LOG_TRIVIAL(error)
<< __func__ << " error reading = " << ec.message();
log_.error() << "error reading = " << ec.message();
return false;
}
@@ -1000,35 +948,29 @@ ReportingETL::loadCacheFromClioPeer(
if (!parsed.is_object())
{
BOOST_LOG_TRIVIAL(error)
<< __func__ << " Error parsing response: " << raw;
log_.error() << "Error parsing response: " << raw;
return false;
}
BOOST_LOG_TRIVIAL(trace)
<< __func__ << " Successfully parsed response " << parsed;
log_.trace() << "Successfully parsed response " << parsed;
if (auto const& response = parsed.as_object();
response.contains("error"))
{
BOOST_LOG_TRIVIAL(error)
<< __func__ << " Response contains error: " << response;
log_.error() << "Response contains error: " << response;
auto const& err = response.at("error");
if (err.is_string() && err.as_string() == "lgrNotFound")
{
++numAttempts;
if (numAttempts >= 5)
{
BOOST_LOG_TRIVIAL(error)
<< __func__
log_.error()
<< " ledger not found at peer after 5 attempts. "
"peer = "
<< ip << " ledger = " << ledgerIndex
<< ". Check your config and the health of the peer";
return false;
}
BOOST_LOG_TRIVIAL(warning)
<< __func__
<< " ledger not found. ledger = " << ledgerIndex
log_.warn() << "Ledger not found. ledger = " << ledgerIndex
<< ". Sleeping and trying again";
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
@@ -1041,8 +983,7 @@ ReportingETL::loadCacheFromClioPeer(
if (!response.contains("cache_full") ||
!response.at("cache_full").as_bool())
{
BOOST_LOG_TRIVIAL(error)
<< __func__ << " cache not full for clio node. ip = " << ip;
log_.error() << "cache not full for clio node. ip = " << ip;
return false;
}
if (response.contains("marker"))
@@ -1063,8 +1004,7 @@ ReportingETL::loadCacheFromClioPeer(
if (!stateObject.key.parseHex(
obj.at("index").as_string().c_str()))
{
BOOST_LOG_TRIVIAL(error)
<< __func__ << " failed to parse object id";
log_.error() << "failed to parse object id";
return false;
}
boost::algorithm::unhex(
@@ -1075,21 +1015,18 @@ ReportingETL::loadCacheFromClioPeer(
backend_->cache().update(objects, ledgerIndex, true);
if (marker)
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " - At marker " << *marker;
log_.debug() << "At marker " << *marker;
} 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;
}
catch (std::exception const& e)
{
BOOST_LOG_TRIVIAL(error)
<< __func__ << " Encountered exception : " << e.what()
log_.error() << "Encountered exception : " << e.what()
<< " - ip = " << ip;
return false;
}
@@ -1101,7 +1038,7 @@ ReportingETL::loadCache(uint32_t seq)
if (cacheLoadStyle_ == CacheLoadStyle::NOT_AT_ALL)
{
backend_->cache().setDisabled();
BOOST_LOG_TRIVIAL(warning) << "Cache is disabled. Not loading";
log_.warn() << "Cache is disabled. Not loading";
return;
}
// 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 &&
!backend_->cache().isFull())
{
BOOST_LOG_TRIVIAL(debug)
<< "Cache not full. Cache size = " << backend_->cache().size()
<< ". Sleeping ...";
log_.debug() << "Cache not full. Cache size = "
<< backend_->cache().size() << ". Sleeping ...";
std::this_thread::sleep_for(std::chrono::seconds(10));
BOOST_LOG_TRIVIAL(info)
<< "Cache is full. Cache size = " << backend_->cache().size();
log_.info() << "Cache is full. Cache size = "
<< backend_->cache().size();
}
}
@@ -1198,9 +1134,8 @@ ReportingETL::loadCacheFromDb(uint32_t seq)
if (c)
cursorStr << ripple::strHex(*c) << ", ";
}
BOOST_LOG_TRIVIAL(info)
<< "Loading cache. num cursors = " << cursors.size() - 1;
BOOST_LOG_TRIVIAL(trace) << __func__ << " cursors = " << cursorStr.str();
log_.info() << "Loading cache. num cursors = " << cursors.size() - 1;
log_.trace() << "cursors = " << cursorStr.str();
cacheDownloader_ = std::thread{[this, seq, cursors]() {
auto startTime = std::chrono::system_clock::now();
@@ -1221,8 +1156,7 @@ ReportingETL::loadCacheFromDb(uint32_t seq)
std::string cursorStr = cursor.has_value()
? ripple::strHex(cursor.value())
: ripple::strHex(Backend::firstKey);
BOOST_LOG_TRIVIAL(debug)
<< "Starting a cursor: " << cursorStr
log_.debug() << "Starting a cursor: " << cursorStr
<< " markers = " << *markers;
while (!stopping_)
@@ -1237,7 +1171,7 @@ ReportingETL::loadCacheFromDb(uint32_t seq)
backend_->cache().update(res.objects, seq, true);
if (!res.cursor || (end && *(res.cursor) > *end))
break;
BOOST_LOG_TRIVIAL(trace)
log_.trace()
<< "Loading cache. cache size = "
<< backend_->cache().size() << " - cursor = "
<< ripple::strHex(res.cursor.value())
@@ -1254,16 +1188,14 @@ ReportingETL::loadCacheFromDb(uint32_t seq)
auto duration =
std::chrono::duration_cast<std::chrono::seconds>(
endTime - startTime);
BOOST_LOG_TRIVIAL(info)
<< "Finished loading cache. cache size = "
log_.info() << "Finished loading cache. cache size = "
<< backend_->cache().size() << ". Took "
<< duration.count() << " seconds";
backend_->cache().setFull();
}
else
{
BOOST_LOG_TRIVIAL(info)
<< "Finished a cursor. num remaining = "
log_.info() << "Finished a cursor. num remaining = "
<< *numRemaining << " start = " << cursorStr
<< " markers = " << *markers;
}
@@ -1275,7 +1207,7 @@ ReportingETL::loadCacheFromDb(uint32_t seq)
void
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();
uint32_t latestSequence;
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 <boost/algorithm/string.hpp>
@@ -8,6 +26,7 @@
#include <boost/beast/websocket.hpp>
#include <backend/BackendInterface.h>
#include <etl/ETLSource.h>
#include <log/Logger.h>
#include <subscriptions/SubscriptionManager.h>
#include "org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h"
@@ -55,6 +74,8 @@ class SubscriptionManager;
class ReportingETL
{
private:
clio::Logger log_{"ETL"};
std::shared_ptr<BackendInterface> backend_;
std::shared_ptr<SubscriptionManager> subscriptions_;
std::shared_ptr<ETLLoadBalancer> loadBalancer_;
@@ -300,7 +321,7 @@ private:
void
run()
{
BOOST_LOG_TRIVIAL(info) << "Starting reporting etl";
log_.info() << "Starting reporting etl";
stopping_ = false;
doWork();
@@ -337,8 +358,8 @@ public:
~ReportingETL()
{
BOOST_LOG_TRIVIAL(info) << "onStop called";
BOOST_LOG_TRIVIAL(debug) << "Stopping Reporting ETL";
log_.info() << "onStop called";
log_.debug() << "Stopping Reporting ETL";
stopping_ = true;
if (worker_.joinable())
@@ -346,7 +367,7 @@ public:
if (cacheDownloader_.joinable())
cacheDownloader_.join();
BOOST_LOG_TRIVIAL(debug) << "Joined ReportingETL worker thread";
log_.debug() << "Joined ReportingETL worker thread";
}
boost::json::object
@@ -392,5 +413,3 @@ public:
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>
@@ -12,5 +30,3 @@ std::string const&
getClioFullVersionString();
} // 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 <boost/preprocessor/stringize.hpp>
#include <algorithm>
@@ -12,17 +31,15 @@ namespace Build {
// and follow the format described at http://semver.org/
//------------------------------------------------------------------------------
// clang-format off
char const* const versionString = "1.0.2"
// clang-format on
char const* const versionString = "1.0.4"
// clang-format on
#if defined(DEBUG) || defined(SANITIZER)
"+"
#ifdef CLIO_GIT_COMMIT_HASH
CLIO_GIT_COMMIT_HASH
"."
#ifdef CLIO_BUILD
CLIO_BUILD
#endif
#ifdef DEBUG
"DEBUG"
".DEBUG"
#ifdef SANITIZER
"."
#endif
@@ -31,12 +48,10 @@ char const* const versionString = "1.0.2"
#ifdef SANITIZER
BOOST_PP_STRINGIZE(SANITIZER)
#endif
#endif
#ifdef PKG
"-release"
#endif
//--------------------------------------------------------------------------
;
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>
#ifdef GRPC_TSAN_ENABLED
#undef GRPC_TSAN_ENABLED
@@ -6,26 +25,22 @@
#undef GRPC_ASAN_ENABLED
#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/strand.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/json.hpp>
#include <boost/log/core.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 <boost/program_options.hpp>
#include <algorithm>
#include <backend/BackendFactory.h>
#include <config/Config.h>
#include <cstdlib>
#include <etl/ReportingETL.h>
#include <fstream>
#include <functional>
#include <iostream>
@@ -35,12 +50,67 @@
#include <string>
#include <thread>
#include <vector>
#include <webserver/Listener.h>
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>
parse_certs(Config const& config)
parseCerts(Config const& config)
{
if (!config.contains("ssl_cert_file") || !config.contains("ssl_key_file"))
return {};
@@ -80,85 +150,12 @@ parse_certs(Config const& config)
return ctx;
}
void
initLogging(Config const& config)
{
namespace src = boost::log::sources;
namespace keywords = boost::log::keywords;
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;
}
/**
* @brief Start context threads
*
* @param ioc Context
* @param numThreads Number of worker threads to start
*/
void
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
main(int argc, char* argv[])
try
{
// Check command line arguments.
if (argc != 2)
{
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]);
auto const configPath = parseCli(argc, argv);
auto const config = ConfigReader::open(configPath);
if (!config)
{
std::cerr << "Couldnt parse config. Exiting..." << std::endl;
std::cerr << "Couldnt parse config '" << configPath << "'."
<< std::endl;
return EXIT_FAILURE;
}
initLogging(config);
LogService::init(config);
LogService::info() << "Clio version: " << Build::getClioFullVersionString();
BOOST_LOG_TRIVIAL(info)
<< "Clio version: " << Build::getClioFullVersionString();
auto ctx = parse_certs(config);
auto ctx = parseCerts(config);
auto ctxRef = ctx
? std::optional<std::reference_wrapper<ssl::context>>{ctx.value()}
: std::nullopt;
@@ -209,30 +191,29 @@ main(int argc, char* argv[])
auto const threads = config.valueOr("io_threads", 2);
if (threads <= 0)
{
BOOST_LOG_TRIVIAL(fatal) << "io_threads is less than 0";
LogService::fatal() << "io_threads is less than 0";
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
boost::asio::io_context ioc{threads};
// Rate limiter, to prevent abuse
DOSGuard dosGuard{config, ioc};
auto sweepHandler = IntervalSweepHandler{config, ioc};
auto dosGuard = DOSGuard{config, sweepHandler};
// Interface to the database
std::shared_ptr<BackendInterface> backend{
Backend::make_Backend(ioc, config)};
auto backend = Backend::make_Backend(ioc, config);
// Manages clients subscribed to streams
std::shared_ptr<SubscriptionManager> subscriptions{
SubscriptionManager::make_SubscriptionManager(config, backend)};
auto subscriptions =
SubscriptionManager::make_SubscriptionManager(config, backend);
// Tracks which ledgers have been validated by the
// network
std::shared_ptr<NetworkValidatedLedgers> ledgers{
NetworkValidatedLedgers::make_ValidatedLedgers()};
auto ledgers = NetworkValidatedLedgers::make_ValidatedLedgers();
// Handles the connection to one or more rippled nodes.
// ETL uses the balancer to extract data.
@@ -258,3 +239,7 @@ main(int argc, char* argv[])
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/RPC.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 <chrono>
@@ -53,5 +71,3 @@ public:
};
} // 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 <algorithm>
@@ -55,6 +74,13 @@ getErrorInfo(ClioError code)
{ClioError::rpcMALFORMED_CURRENCY,
"malformedCurrency",
"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; };
@@ -106,7 +132,7 @@ makeError(Status const& status)
return str.empty() ? nullopt : make_optional(str);
};
return visit(
auto res = visit(
overloadSet{
[&status, &wrapOptional](RippledError err) {
if (err == ripple::rpcUNKNOWN)
@@ -130,6 +156,14 @@ makeError(Status const& status)
},
},
status.code);
if (status.extraInfo)
{
for (auto& [key, value] : status.extraInfo.value())
{
res[key] = value;
}
}
return res;
}
} // 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>
@@ -17,6 +35,9 @@ namespace RPC {
*/
enum class ClioError {
rpcMALFORMED_CURRENCY = 5000,
rpcMALFORMED_REQUEST = 5001,
rpcMALFORMED_OWNER = 5002,
rpcMALFORMED_ADDRESS = 5003,
};
/**
@@ -50,9 +71,12 @@ struct Status
CombinedError code = RippledError::rpcSUCCESS;
std::string error = "";
std::string message = "";
std::optional<boost::json::object> extraInfo;
Status() = default;
/* 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.
// This means that we have to be able to duplicate this
@@ -81,6 +105,34 @@ struct Status
return *err != RippledError::rpcSUCCESS;
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);
} // 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>
@@ -102,4 +120,3 @@ doServerInfo(Context const& context);
Result
doRandom(Context const& context);
} // 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 <log/Logger.h>
#include <rpc/Handlers.h>
#include <rpc/RPCHelpers.h>
#include <webserver/HttpBase.h>
@@ -9,9 +29,15 @@
#include <unordered_map>
using namespace std;
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gPerfLog{"Performance"};
clio::Logger gLog{"RPC"};
} // namespace
namespace RPC {
Context::Context(
boost::asio::yield_context& yield_,
string const& command_,
@@ -40,7 +66,7 @@ Context::Context(
, counters(counters_)
, clientIp(clientIp_)
{
BOOST_LOG_TRIVIAL(debug) << tag() << "new Context created";
gPerfLog.debug() << tag() << "new Context created";
}
optional<Context>
@@ -314,9 +340,6 @@ buildResponse(Context const& ctx)
if (!res)
return Status{RippledError::rpcFAILED_TO_FORWARD};
if (res->contains("result") && res->at("result").is_object())
return res->at("result").as_object();
return *res;
}
@@ -325,8 +348,7 @@ buildResponse(Context const& ctx)
if (ctx.backend->isTooBusy())
{
BOOST_LOG_TRIVIAL(error)
<< __func__ << " Database is too busy. Rejecting request";
gLog.error() << "Database is too busy. Rejecting request";
return Status{RippledError::rpcTOO_BUSY};
}
@@ -337,18 +359,16 @@ buildResponse(Context const& ctx)
try
{
BOOST_LOG_TRIVIAL(debug)
<< ctx.tag() << __func__ << " start executing rpc `" << ctx.method
gPerfLog.debug() << ctx.tag() << " start executing rpc `" << ctx.method
<< '`';
auto v = (*method)(ctx);
BOOST_LOG_TRIVIAL(debug)
<< ctx.tag() << __func__ << " finish executing rpc `" << ctx.method
gPerfLog.debug() << ctx.tag() << " finish executing rpc `" << ctx.method
<< '`';
if (auto object = get_if<boost::json::object>(&v);
object && not shouldSuppressValidatedFlag(ctx))
{
(*object)["validated"] = true;
(*object)[JS(validated)] = true;
}
return v;
@@ -363,13 +383,12 @@ buildResponse(Context const& ctx)
}
catch (Backend::DatabaseTimeout const& t)
{
BOOST_LOG_TRIVIAL(error) << __func__ << " Database timeout";
gLog.error() << "Database timeout";
return Status{RippledError::rpcTOO_BUSY};
}
catch (exception const& err)
{
BOOST_LOG_TRIVIAL(error)
<< ctx.tag() << __func__ << " caught exception : " << err.what();
gLog.error() << ctx.tag() << " caught exception: " << err.what();
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 <util/Taggable.h>
#include <boost/asio/spawn.hpp>
#include <boost/json.hpp>
#include <backend/BackendInterface.h>
#include <rpc/Counters.h>
#include <util/Taggable.h>
#include <optional>
#include <string>
#include <variant>
@@ -34,6 +52,7 @@ namespace RPC {
struct Context : public util::Taggable
{
clio::Logger perfLog_{"Performance"};
boost::asio::yield_context& yield;
std::string method;
std::uint32_t version;
@@ -72,13 +91,13 @@ struct AccountCursor
std::uint32_t hint;
std::string
toString()
toString() const
{
return ripple::strHex(index) + "," + std::to_string(hint);
}
bool
isNonZero()
isNonZero() const
{
return index.isNonZero() || hint != 0;
}
@@ -129,6 +148,7 @@ template <class T>
void
logDuration(Context const& ctx, T const& dur)
{
static clio::Logger log{"RPC"};
std::stringstream ss;
ss << ctx.tag() << "Request processing duration = "
<< std::chrono::duration_cast<std::chrono::milliseconds>(dur).count()
@@ -136,13 +156,11 @@ logDuration(Context const& ctx, T const& dur)
auto seconds =
std::chrono::duration_cast<std::chrono::seconds>(dur).count();
if (seconds > 10)
BOOST_LOG_TRIVIAL(error) << ss.str();
log.error() << ss.str();
else if (seconds > 1)
BOOST_LOG_TRIVIAL(warning) << ss.str();
log.warn() << ss.str();
else
BOOST_LOG_TRIVIAL(info) << ss.str();
log.info() << ss.str();
}
} // 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/format.hpp>
#include <ripple/basics/StringUtilities.h>
using namespace clio;
#include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h>
// local to compilation unit loggers
namespace {
clio::Logger gLog{"RPC"};
} // namespace
namespace RPC {
@@ -74,35 +101,11 @@ getRequiredUInt(boost::json::object const& request, std::string const& 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>
parseAccountCursor(
BackendInterface const& backend,
std::uint32_t seq,
std::optional<std::string> jsonCursor,
ripple::AccountID const& accountID,
boost::asio::yield_context& yield)
{
ripple::uint256 cursorIndex = beast::zero;
@@ -133,19 +136,6 @@ parseAccountCursor(
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});
}
@@ -412,12 +402,11 @@ deserializeTxPlusMeta(Backend::TransactionAndMetadata const& blobs)
blobs.metadata.begin(),
blobs.metadata.end(),
std::ostream_iterator<unsigned char>(meta));
BOOST_LOG_TRIVIAL(error)
<< __func__
<< " Failed to deserialize transaction. txn = " << txn.str()
<< " - meta = " << meta.str()
<< " txn length = " << std::to_string(blobs.transaction.size())
<< " meta length = " << std::to_string(blobs.metadata.size());
gLog.error() << "Failed to deserialize transaction. txn = " << txn.str()
<< " - meta = " << meta.str() << " txn length = "
<< std::to_string(blobs.transaction.size())
<< " meta length = "
<< std::to_string(blobs.metadata.size());
throw e;
}
}
@@ -657,13 +646,11 @@ traverseOwnedNodes(
ripple::keylet::account(accountID).key, sequence, yield))
return Status{RippledError::rpcACT_NOT_FOUND};
auto parsedCursor =
parseAccountCursor(backend, sequence, jsonCursor, accountID, yield);
if (!parsedCursor)
auto maybeCursor = parseAccountCursor(backend, sequence, jsonCursor, yield);
if (!maybeCursor)
return Status(ripple::rpcINVALID_PARAMS, "Malformed cursor");
auto [hexCursor, startHint] = *parsedCursor;
auto [hexCursor, startHint] = *maybeCursor;
return traverseOwnedNodes(
backend,
@@ -709,22 +696,21 @@ traverseOwnedNodes(
auto hintDir =
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))
if (auto const& indexes = sle.getFieldV256(ripple::sfIndexes);
std::find(std::begin(indexes), std::end(indexes), hexMarker) ==
std::end(indexes))
{
if (key == hexMarker)
{
// We found the hint, we can start here
currentIndex = hintIndex;
break;
}
}
// result in empty dataset
return AccountCursor({beast::zero, 0});
}
currentIndex = hintIndex;
bool found = false;
for (;;)
{
@@ -807,20 +793,16 @@ traverseOwnedNodes(
}
auto end = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(debug)
<< "Time loading owned directories: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
gLog.debug() << "Time loading owned directories: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
end - start)
.count()
<< " milliseconds";
start = std::chrono::system_clock::now();
auto objects = backend.fetchLedgerObjects(keys, sequence, yield);
end = std::chrono::system_clock::now();
auto [objects, timeDiff] = util::timed(
[&]() { return backend.fetchLedgerObjects(keys, sequence, yield); });
BOOST_LOG_TRIVIAL(debug)
<< "Time loading owned entries: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
.count()
gLog.debug() << "Time loading owned entries: " << timeDiff
<< " milliseconds";
for (auto i = 0; i < objects.size(); ++i)
@@ -1341,7 +1323,7 @@ postProcessOrderBook(
}
catch (std::exception const& e)
{
BOOST_LOG_TRIVIAL(error) << "caught exception: " << e.what();
gLog.error() << "caught exception: " << e.what();
}
}
return jsonOffers;
@@ -1351,46 +1333,48 @@ std::variant<Status, ripple::Book>
parseBook(boost::json::object const& request)
{
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"))
return Status{RippledError::rpcINVALID_PARAMS, "missingTakerGets"};
return Status{
RippledError::rpcINVALID_PARAMS, "Missing field 'taker_gets'"};
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())
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();
if (!taker_pays.contains("currency"))
return Status{
RippledError::rpcINVALID_PARAMS, "missingTakerPaysCurrency"};
return Status{RippledError::rpcSRC_CUR_MALFORMED};
if (!taker_pays.at("currency").is_string())
return Status{
RippledError::rpcINVALID_PARAMS, "takerPaysCurrencyNotString"};
return Status{RippledError::rpcSRC_CUR_MALFORMED};
auto taker_gets = request.at("taker_gets").as_object();
if (!taker_gets.contains("currency"))
return Status{
RippledError::rpcINVALID_PARAMS, "missingTakerGetsCurrency"};
return Status{RippledError::rpcDST_AMT_MALFORMED};
if (!taker_gets.at("currency").is_string())
return Status{
RippledError::rpcINVALID_PARAMS, "takerGetsCurrencyNotString"};
RippledError::rpcDST_AMT_MALFORMED,
};
ripple::Currency pay_currency;
if (!ripple::to_currency(
pay_currency, taker_pays.at("currency").as_string().c_str()))
return Status{
RippledError::rpcSRC_CUR_MALFORMED, "badTakerPaysCurrency"};
return Status{RippledError::rpcSRC_CUR_MALFORMED};
ripple::Currency get_currency;
if (!ripple::to_currency(
get_currency, taker_gets["currency"].as_string().c_str()))
return Status{
RippledError::rpcDST_AMT_MALFORMED, "badTakerGetsCurrency"};
return Status{RippledError::rpcDST_AMT_MALFORMED};
ripple::AccountID pay_issuer;
if (taker_pays.contains("issuer"))
@@ -1401,13 +1385,10 @@ parseBook(boost::json::object const& request)
if (!ripple::to_issuer(
pay_issuer, taker_pays.at("issuer").as_string().c_str()))
return Status{
RippledError::rpcSRC_ISR_MALFORMED, "badTakerPaysIssuer"};
return Status{RippledError::rpcSRC_ISR_MALFORMED};
if (pay_issuer == ripple::noAccount())
return Status{
RippledError::rpcSRC_ISR_MALFORMED,
"badTakerPaysIssuerAccountOne"};
return Status{RippledError::rpcSRC_ISR_MALFORMED};
}
else
{
@@ -1573,47 +1554,52 @@ traverseTransactions(
}
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)))
{
auto& min = request.at(JS(ledger_index_min));
if (!min.is_int64())
if (!request.at(JS(ledger_index_min)).is_int64())
{
return Status{
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() ||
context.range.minSequence > min.as_int64())
if (context.range.maxSequence < *min ||
context.range.minSequence > *min)
return Status{
RippledError::rpcLGR_IDX_MALFORMED,
"ledgerSeqMinOutOfRange"};
else
minIndex = boost::json::value_to<std::uint32_t>(min);
minIndex = static_cast<uint32_t>(*min);
}
if (forward && !cursor)
cursor = {minIndex, 0};
}
auto maxIndex = context.range.maxSequence;
if (request.contains(JS(ledger_index_max)))
{
auto& max = request.at(JS(ledger_index_max));
if (!max.is_int64())
if (!request.at(JS(ledger_index_max)).is_int64())
{
return Status{
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() ||
context.range.minSequence > max.as_int64())
return Status{
RippledError::rpcLGR_IDX_MALFORMED,
"ledgerSeqMaxOutOfRange"};
if (context.range.maxSequence < *max ||
context.range.minSequence > *max)
return Status{RippledError::rpcLGR_IDXS_INVALID};
else
maxIndex = boost::json::value_to<std::uint32_t>(max);
maxIndex = static_cast<uint32_t>(*max);
}
if (minIndex > maxIndex)
@@ -1623,6 +1609,11 @@ traverseTransactions(
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_max)) ||
@@ -1656,8 +1647,7 @@ traverseTransactions(
boost::json::array txns;
auto [blobs, retCursor] = transactionFetcher(
context.backend, limit, forward, cursor, context.yield);
auto serializationStart = std::chrono::system_clock::now();
auto timeDiff = util::timed([&, &retCursor = retCursor, &blobs = blobs]() {
if (retCursor)
{
boost::json::object cursorJson;
@@ -1676,9 +1666,8 @@ traverseTransactions(
}
else if (txnPlusMeta.ledgerSequence > maxIndex && !forward)
{
BOOST_LOG_TRIVIAL(debug)
<< __func__
<< " skipping over transactions from incomplete ledger";
gLog.debug()
<< "Skipping over transactions from incomplete ledger";
continue;
}
@@ -1707,12 +1696,9 @@ traverseTransactions(
response[JS(ledger_index_min)] = minIndex;
response[JS(ledger_index_max)] = maxIndex;
response[JS(transactions)] = txns;
});
gLog.info() << "serialization took " << timeDiff
BOOST_LOG_TRIVIAL(info)
<< __func__ << " serialization took "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now() - serializationStart)
.count()
<< " milliseconds";
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
* the handlers
@@ -36,7 +54,6 @@ parseAccountCursor(
BackendInterface const& backend,
std::uint32_t seq,
std::optional<std::string> jsonCursor,
ripple::AccountID const& accountID,
boost::asio::yield_context& yield);
// TODO this function should probably be in a different file and namespace
@@ -273,4 +290,3 @@ computeBookChanges(
std::vector<Backend::TransactionAndMetadata> const& transactions);
} // 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>
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/spawn.hpp>
#include <boost/json.hpp>
#include <boost/log/trivial.hpp>
#include <memory>
#include <optional>
@@ -20,6 +39,7 @@ class WorkQueue
std::atomic_uint64_t curSize_ = 0;
uint32_t maxSize_ = std::numeric_limits<uint32_t>::max();
clio::Logger log_{"RPC"};
public:
WorkQueue(std::uint32_t numWorkers, uint32_t maxSize = 0);
@@ -30,10 +50,8 @@ public:
{
if (curSize_ >= maxSize_ && !isWhiteListed)
{
BOOST_LOG_TRIVIAL(warning)
<< __func__
<< " queue is full. rejecting job. current size = " << curSize_
<< " max size = " << maxSize_;
log_.warn() << "Queue is full. rejecting job. current size = "
<< curSize_ << " max size = " << maxSize_;
return false;
}
++curSize_;
@@ -52,7 +70,7 @@ public:
// durationUs_
++queued_;
durationUs_ += wait;
BOOST_LOG_TRIVIAL(debug) << "WorkQueue wait time = " << wait
log_.info() << "WorkQueue wait time = " << wait
<< " queue size = " << curSize_;
f(yield);
--curSize_;
@@ -77,5 +95,3 @@ private:
boost::asio::io_context 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/basics/StringUtilities.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/basics/StringUtilities.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/STLedgerEntry.h>
#include <boost/json.hpp>
@@ -50,17 +69,12 @@ doAccountInfo(Context const& context)
if (!accountID)
return Status{RippledError::rpcACT_MALFORMED};
assert(accountID.has_value());
auto key = ripple::keylet::account(accountID.value());
std::optional<std::vector<unsigned char>> dbResponse =
context.backend->fetchLedgerObject(key.key, lgrInfo.seq, context.yield);
if (!dbResponse)
{
return Status{RippledError::rpcACT_NOT_FOUND};
}
ripple::STLedgerEntry sle{
ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key.key};
@@ -68,12 +82,6 @@ doAccountInfo(Context const& context)
if (!key.check(sle))
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(ledger_hash)] = ripple::strHex(lgrInfo.hash);
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/paths/TrustLine.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/paths/TrustLine.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))
return *status;
auto nextMarker = std::get<RPC::AccountCursor>(next);
auto const& nextMarker = std::get<RPC::AccountCursor>(next);
if (nextMarker.isNonZero())
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/paths/TrustLine.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 <util/Profiler.h>
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gLog{"RPC"};
} // namespace
namespace RPC {
@@ -18,15 +46,13 @@ doAccountTx(Context const& context)
bool const forward,
std::optional<Backend::TransactionsCursor> const& cursorIn,
boost::asio::yield_context& yield) {
auto const start = std::chrono::system_clock::now();
auto const txnsAndCursor = backend->fetchAccountTransactions(
auto [txnsAndCursor, timeDiff] = util::timed([&]() {
return backend->fetchAccountTransactions(
accountID, limit, forward, cursorIn, yield);
BOOST_LOG_TRIVIAL(info)
<< outerFuncName << " db fetch took "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now() - start)
.count()
<< " milliseconds - num blobs = " << txnsAndCursor.txns.size();
});
gLog.info() << outerFuncName << " db fetch took " << timeDiff
<< " milliseconds - num blobs = "
<< txnsAndCursor.txns.size();
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/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/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/jss.h>
#include <boost/json.hpp>
#include <algorithm>
#include <rpc/RPCHelpers.h>
#include <backend/BackendInterface.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 {
@@ -64,9 +92,9 @@ doBookOffers(Context const& context)
bookBase, lgrInfo.seq, limit, marker, context.yield);
auto end = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(warning)
<< "Time loading books: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
gLog.warn() << "Time loading books: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
end - start)
.count()
<< " milliseconds - request = " << request;
@@ -78,9 +106,9 @@ doBookOffers(Context const& context)
auto end2 = std::chrono::system_clock::now();
BOOST_LOG_TRIVIAL(warning)
<< "Time transforming to json: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end2 - end)
gLog.warn() << "Time transforming to json: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
end2 - end)
.count()
<< " milliseconds - request = " << request;

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/protocol/ErrorCodes.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/protocol/ErrorCodes.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 <rpc/RPCHelpers.h>
@@ -25,7 +44,7 @@ doGatewayBalances(Context const& context)
std::map<ripple::AccountID, std::vector<ripple::STAmount>> frozenBalances;
std::set<ripple::AccountID> hotWallets;
if (request.contains("hot_wallet"))
if (request.contains(JS(hotwallet)))
{
auto getAccountID =
[](auto const& j) -> std::optional<ripple::AccountID> {
@@ -44,7 +63,7 @@ doGatewayBalances(Context const& context)
return {};
};
auto const& hw = request.at("hot_wallet");
auto const& hw = request.at(JS(hotwallet));
bool valid = true;
// null is treated as a valid 0-sized array of hotwallet
@@ -110,7 +129,7 @@ doGatewayBalances(Context const& context)
if (hotWallets.count(peer) > 0)
{
// This is a specified hot wallet
hotBalances[peer].push_back(balance);
hotBalances[peer].push_back(-balance);
}
else if (balSign > 0)
{
@@ -120,7 +139,7 @@ doGatewayBalances(Context const& context)
else if (freeze)
{
// An obligation the gateway has frozen
frozenBalances[peer].push_back(balance);
frozenBalances[peer].push_back(-balance);
}
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 <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/protocol/STLedgerEntry.h>
#include <backend/BackendInterface.h>
#include <log/Logger.h>
#include <rpc/RPCHelpers.h>
#include <boost/json.hpp>
#include <backend/BackendInterface.h>
#include <rpc/RPCHelpers.h>
// Get state nodes from a ledger
// Inputs:
// 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 {
using boost::json::value_to;
@@ -65,7 +93,7 @@ doLedgerData(Context const& context)
}
else
{
BOOST_LOG_TRIVIAL(debug) << __func__ << " : parsing marker";
gLog.debug() << "Parsing marker";
marker = ripple::uint256{};
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_resolution)] =
lgrInfo.closeTimeResolution.count();
header[JS(closed)] = true;
header[JS(hash)] = ripple::strHex(lgrInfo.hash);
header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash);
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(closed)] = true;
response[JS(ledger)] = header;
}
else
@@ -168,9 +196,8 @@ doLedgerData(Context const& context)
std::chrono::duration_cast<std::chrono::microseconds>(end - start)
.count();
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " number of results = " << results.size()
<< " fetched in " << time << " microseconds";
gLog.debug() << "Number of results = " << results.size() << " fetched in "
<< time << " microseconds";
boost::json::array objects;
objects.reserve(results.size());
for (auto const& [key, object] : results)
@@ -194,8 +221,7 @@ doLedgerData(Context const& context)
time = std::chrono::duration_cast<std::chrono::microseconds>(end2 - end)
.count();
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " number of results = " << results.size()
gLog.debug() << "Number of results = " << results.size()
<< " serialized in " << time << " microseconds";
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/STLedgerEntry.h>
#include <boost/json.hpp>
@@ -29,13 +48,16 @@ doLedgerEntry(Context const& context)
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
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.at(JS(index)).is_string())
return Status{RippledError::rpcINVALID_PARAMS, "indexNotString"};
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)))
{
@@ -46,7 +68,7 @@ doLedgerEntry(Context const& context)
auto const account = ripple::parseBase58<ripple::AccountID>(
request.at(JS(account_root)).as_string().c_str());
if (!account || account->isZero())
return Status{RippledError::rpcINVALID_PARAMS, "malformedAddress"};
return Status{ClioError::rpcMALFORMED_ADDRESS};
else
key = ripple::keylet::account(*account).key;
}
@@ -172,8 +194,7 @@ doLedgerEntry(Context const& context)
if (!ownerID)
{
return Status{
RippledError::rpcINVALID_PARAMS, "malformedAddress"};
return Status{ClioError::rpcMALFORMED_ADDRESS};
}
else
{
@@ -219,8 +240,7 @@ doLedgerEntry(Context const& context)
.c_str());
if (!id)
return Status{
RippledError::rpcINVALID_PARAMS, "malformedAddress"};
return Status{ClioError::rpcMALFORMED_ADDRESS};
else
{
std::uint32_t seq =
@@ -256,8 +276,7 @@ doLedgerEntry(Context const& context)
offer.at(JS(account)).as_string().c_str());
if (!id)
return Status{
RippledError::rpcINVALID_PARAMS, "malformedAddress"};
return Status{ClioError::rpcMALFORMED_ADDRESS};
else
{
std::uint32_t seq =
@@ -310,7 +329,7 @@ doLedgerEntry(Context const& context)
if (!id1 || !id2)
return Status{
RippledError::rpcINVALID_PARAMS, "malformedAddresses"};
ClioError::rpcMALFORMED_ADDRESS, "malformedAddresses"};
else if (!ripple::to_currency(
currency, state.at(JS(currency)).as_string().c_str()))
@@ -325,24 +344,24 @@ doLedgerEntry(Context const& context)
{
if (!request.at(JS(ticket)).is_string())
return Status{
RippledError::rpcINVALID_PARAMS, "ticketNotString"};
ClioError::rpcMALFORMED_REQUEST, "ticketNotString"};
if (!key.parseHex(request.at(JS(ticket)).as_string().c_str()))
return Status{
RippledError::rpcINVALID_PARAMS, "malformedTicket"};
ClioError::rpcMALFORMED_REQUEST, "malformedTicket"};
}
else if (
!request.at(JS(ticket)).as_object().contains(JS(owner)) ||
!request.at(JS(ticket)).as_object().at(JS(owner)).is_string())
{
return Status{RippledError::rpcINVALID_PARAMS, "malformedOwner"};
return Status{ClioError::rpcMALFORMED_REQUEST};
}
else if (
!request.at(JS(ticket)).as_object().contains(JS(ticket_seq)) ||
!request.at(JS(ticket)).as_object().at(JS(ticket_seq)).is_int64())
{
return Status{
RippledError::rpcINVALID_PARAMS, "malformedTicketSeq"};
ClioError::rpcMALFORMED_REQUEST, "malformedTicketSeq"};
}
else
{
@@ -354,8 +373,7 @@ doLedgerEntry(Context const& context)
.c_str());
if (!id)
return Status{
RippledError::rpcINVALID_PARAMS, "malformedOwner"};
return Status{ClioError::rpcMALFORMED_OWNER};
else
{
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 <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 <util/Profiler.h>
using namespace clio;
// local to compilation unit loggers
namespace {
clio::Logger gLog{"RPC"};
} // namespace
namespace RPC {
@@ -20,15 +48,14 @@ doNFTHistory(Context const& context)
std::optional<Backend::TransactionsCursor> const& cursorIn,
boost::asio::yield_context& yield)
-> Backend::TransactionsAndCursor {
auto const start = std::chrono::system_clock::now();
auto const txnsAndCursor = backend->fetchNFTTransactions(
auto const [txnsAndCursor, timeDiff] =
util::timed([&, &tokenID = tokenID]() {
return backend->fetchNFTTransactions(
tokenID, limit, forward, cursorIn, yield);
BOOST_LOG_TRIVIAL(info)
<< outerFuncName << " db fetch took "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now() - start)
.count()
<< " milliseconds - num blobs = " << txnsAndCursor.txns.size();
});
gLog.info() << outerFuncName << " db fetch took " << timeDiff
<< " milliseconds - num blobs = "
<< txnsAndCursor.txns.size();
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/protocol/Indexes.h>
#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/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/jss.h>
#include <boost/json.hpp>
#include <algorithm>
#include <rpc/RPCHelpers.h>
#include <boost/json.hpp>
#include <algorithm>
namespace json = boost::json;
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 <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 <ripple/beast/utility/rngfill.h>
#include <ripple/crypto/csprng.h>
#include <rpc/RPCHelpers.h>
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 <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 <subscriptions/SubscriptionManager.h>
#include <webserver/WsBase.h>
@@ -442,8 +461,7 @@ doUnsubscribe(Context const& context)
if (request.contains("books"))
unsubscribeToBooks(books, context.session, *context.subscriptions);
boost::json::object response = {{"status", "success"}};
return response;
return boost::json::object{};
}
} // 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>
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 <rpc/RPCHelpers.h>
@@ -31,6 +50,17 @@ doTx(Context const& context)
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();
if (!range)
@@ -38,7 +68,17 @@ doTx(Context const& context)
auto dbResponse = context.backend->fetchTransaction(hash, context.yield);
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};
}
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>
@@ -36,5 +54,3 @@ public:
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 <subscriptions/SubscriptionManager.h>
#include <webserver/WsBase.h>
@@ -155,7 +174,9 @@ SubscriptionManager::subLedger(
boost::asio::yield_context& yield,
std::shared_ptr<WsBase> session)
{
ledgerSubscribers_.subscribe(session);
subscribeHelper(session, ledgerSubscribers_, [this](session_ptr session) {
unsubLedger(session);
});
auto ledgerRange = backend_->fetchLedgerRange();
assert(ledgerRange);
@@ -185,7 +206,9 @@ SubscriptionManager::unsubLedger(std::shared_ptr<WsBase> session)
void
SubscriptionManager::subTransactions(std::shared_ptr<WsBase> session)
{
txSubscribers_.subscribe(session);
subscribeHelper(session, txSubscribers_, [this](session_ptr session) {
unsubTransactions(session);
});
}
void
@@ -199,10 +222,11 @@ SubscriptionManager::subAccount(
ripple::AccountID const& account,
std::shared_ptr<WsBase>& session)
{
accountSubscribers_.subscribe(session, account);
std::unique_lock lk(cleanupMtx_);
cleanupFuncs_[session].emplace_back([this, account](session_ptr session) {
subscribeHelper(
session,
account,
accountSubscribers_,
[this, account](session_ptr session) {
unsubAccount(account, session);
});
}
@@ -220,11 +244,10 @@ SubscriptionManager::subBook(
ripple::Book const& book,
std::shared_ptr<WsBase> session)
{
bookSubscribers_.subscribe(session, book);
std::unique_lock lk(cleanupMtx_);
cleanupFuncs_[session].emplace_back(
[this, book](session_ptr session) { unsubBook(book, session); });
subscribeHelper(
session, book, bookSubscribers_, [this, book](session_ptr session) {
unsubBook(book, session);
});
}
void
@@ -238,11 +261,10 @@ SubscriptionManager::unsubBook(
void
SubscriptionManager::subBookChanges(std::shared_ptr<WsBase> session)
{
bookChangesSubscribers_.subscribe(session);
std::unique_lock lk(cleanupMtx_);
cleanupFuncs_[session].emplace_back(
[this](session_ptr session) { unsubBookChanges(session); });
subscribeHelper(
session, bookChangesSubscribers_, [this](session_ptr session) {
unsubBookChanges(session);
});
}
void
@@ -408,13 +430,21 @@ SubscriptionManager::subProposedAccount(
ripple::AccountID const& account,
std::shared_ptr<WsBase> session)
{
accountProposedSubscribers_.subscribe(session, account);
subscribeHelper(
session,
account,
accountProposedSubscribers_,
[this, account](session_ptr session) {
unsubProposedAccount(account, session);
});
}
void
SubscriptionManager::subManifest(std::shared_ptr<WsBase> session)
{
manifestSubscribers_.subscribe(session);
subscribeHelper(session, manifestSubscribers_, [this](session_ptr session) {
unsubManifest(session);
});
}
void
@@ -426,7 +456,10 @@ SubscriptionManager::unsubManifest(std::shared_ptr<WsBase> session)
void
SubscriptionManager::subValidation(std::shared_ptr<WsBase> session)
{
validationsSubscribers_.subscribe(session);
subscribeHelper(
session, validationsSubscribers_, [this](session_ptr session) {
unsubValidation(session);
});
}
void
@@ -446,7 +479,10 @@ SubscriptionManager::unsubProposedAccount(
void
SubscriptionManager::subProposedTransactions(std::shared_ptr<WsBase> session)
{
txProposedSubscribers_.subscribe(session);
subscribeHelper(
session, txProposedSubscribers_, [this](session_ptr session) {
unsubProposedTransactions(session);
});
}
void
@@ -454,6 +490,28 @@ SubscriptionManager::unsubProposedTransactions(std::shared_ptr<WsBase> 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
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 <config/Config.h>
#include <memory>
#include <log/Logger.h>
#include <subscriptions/Message.h>
#include <memory>
class WsBase;
class Subscription
@@ -87,6 +107,7 @@ public:
class SubscriptionManager
{
using session_ptr = std::shared_ptr<WsBase>;
clio::Logger log_{"Subscriptions"};
std::vector<std::thread> workers_;
boost::asio::io_context ioc_;
@@ -134,8 +155,8 @@ public:
// 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
// performance benefits
BOOST_LOG_TRIVIAL(info) << "Starting subscription manager with "
<< numThreads << " workers";
log_.info() << "Starting subscription manager with " << numThreads
<< " workers";
workers_.reserve(numThreads);
for (auto i = numThreads; i > 0; --i)
@@ -256,16 +277,29 @@ private:
void
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.
* Each time we add a subscriber, we add the opposite lambda that
* unsubscribes that subscriber when cleanup is called with the session that
* closed.
*/
using CleanupFunction = std::function<void(session_ptr)>;
std::mutex cleanupMtx_;
std::unordered_map<session_ptr, std::vector<CleanupFunction>>
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 <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/json.hpp>
@@ -209,7 +227,8 @@ private:
friend Type
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();
if (boost::iequals(style, "int") || boost::iequals(style, "uint"))
@@ -258,5 +277,3 @@ public:
};
} // 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/iterator/transform_iterator.hpp>
#include <chrono>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <config/Config.h>
class DOSGuard
namespace clio {
class BaseDOSGuard
{
boost::asio::io_context& ctx_;
std::mutex mtx_; // protects ipFetchCount_
std::unordered_map<std::string, std::uint32_t> ipFetchCount_;
public:
virtual ~BaseDOSGuard() = default;
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::uint32_t const maxFetches_;
std::uint32_t const sweepInterval_;
std::uint32_t const maxConnCount_;
std::uint32_t const maxRequestCount_;
clio::Logger log_{"RPC"};
public:
DOSGuard(clio::Config const& config, boost::asio::io_context& ctx)
: ctx_{ctx}
, whitelist_{getWhitelist(config)}
, maxFetches_{config.valueOr("dos_guard.max_fetches", 100u)}
, sweepInterval_{config.valueOr("dos_guard.sweep_interval", 1u)}
/**
* @brief Constructs a new DOS guard.
*
* @param config Clio config
* @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()
{
auto wait = std::chrono::seconds(sweepInterval_);
std::shared_ptr<boost::asio::steady_timer> timer =
std::make_shared<boost::asio::steady_timer>(
ctx_, std::chrono::steady_clock::now() + wait);
timer->async_wait(
[timer, this](const boost::system::error_code& error) {
clear();
createTimer();
});
}
bool
isWhiteListed(std::string const& ip)
/**
* @brief Check whether an ip address is in the whitelist or not
*
* @param ip The ip address to check
* @return true
* @return false
*/
[[nodiscard]] bool
isWhiteListed(std::string const& ip) const noexcept
{
return whitelist_.contains(ip);
}
bool
isOk(std::string const& ip)
/**
* @brief Check whether an ip address is currently rate limited or not
*
* @param ip The ip address to check
* @return true If not rate limited
* @return false If rate limited and the request should not be processed
*/
[[nodiscard]] bool
isOk(std::string const& ip) const noexcept
{
if (whitelist_.contains(ip))
return true;
{
std::unique_lock lck(mtx_);
auto it = ipFetchCount_.find(ip);
if (it == ipFetchCount_.end())
if (ipState_.find(ip) != ipState_.end())
{
auto [transferedByte, requests] = ipState_.at(ip);
if (transferedByte > maxFetches_ || requests > maxRequestCount_)
{
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;
return it->second < maxFetches_;
}
bool
add(std::string const& ip, uint32_t numObjects)
/**
* @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_);
auto it = ipFetchCount_.find(ip);
if (it == ipFetchCount_.end())
ipFetchCount_[ip] = numObjects;
else
it->second += numObjects;
ipState_[ip].transferedByte += numObjects;
}
return isOk(ip);
}
void
clear()
/**
* @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_);
ipFetchCount_.clear();
ipState_[ip].requestsCount++;
}
return isOk(ip);
}
/**
* @brief Instantly clears all fetch counters added by @see add(std::string
* const&, uint32_t)
*/
void
clear() noexcept override
{
std::unique_lock lck(mtx_);
ipState_.clear();
}
private:
std::unordered_set<std::string> const
[[nodiscard]] std::unordered_set<std::string> const
getWhitelist(clio::Config const& config) const
{
using T = std::unordered_set<std::string> const;
@@ -100,4 +246,72 @@ private:
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/spawn.hpp>
@@ -19,14 +37,17 @@
#include <thread>
#include <etl/ReportingETL.h>
#include <log/Logger.h>
#include <main/Build.h>
#include <rpc/Counters.h>
#include <rpc/RPC.h>
#include <rpc/WorkQueue.h>
#include <util/Profiler.h>
#include <util/Taggable.h>
#include <vector>
#include <webserver/DOSGuard.h>
// TODO: consider removing those - visible to anyone including this header
namespace http = boost::beast::http;
namespace net = boost::asio;
namespace ssl = boost::asio::ssl;
@@ -95,13 +116,15 @@ class HttpBase : public util::Taggable
std::shared_ptr<ETLLoadBalancer> balancer_;
std::shared_ptr<ReportingETL const> etl_;
util::TagDecoratorFactory const& tagFactory_;
DOSGuard& dosGuard_;
clio::DOSGuard& dosGuard_;
RPC::Counters& counters_;
WorkQueue& workQueue_;
send_lambda lambda_;
protected:
clio::Logger perfLog_{"Performance"};
boost::beast::flat_buffer buffer_;
bool upgraded_ = false;
bool
dead()
@@ -135,8 +158,7 @@ protected:
if (!ec_ && ec != boost::asio::error::operation_aborted)
{
ec_ = ec;
BOOST_LOG_TRIVIAL(info)
<< tag() << __func__ << ": " << what << ": " << ec.message();
perfLog_.info() << tag() << ": " << what << ": " << ec.message();
boost::beast::get_lowest_layer(derived().stream())
.socket()
.close(ec);
@@ -151,7 +173,7 @@ public:
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard,
clio::DOSGuard& dosGuard,
RPC::Counters& counters,
WorkQueue& queue,
boost::beast::flat_buffer buffer)
@@ -168,11 +190,18 @@ public:
, lambda_(*this)
, buffer_(std::move(buffer))
{
BOOST_LOG_TRIVIAL(debug) << tag() << "http session created";
perfLog_.debug() << tag() << "http session created";
}
virtual ~HttpBase()
{
BOOST_LOG_TRIVIAL(debug) << tag() << "http session closed";
perfLog_.debug() << tag() << "http session closed";
}
clio::DOSGuard&
dosGuard()
{
return dosGuard_;
}
void
@@ -209,14 +238,37 @@ public:
if (ec)
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_))
{
upgraded_ = true;
// Disable the timeout.
// The websocket::stream uses its own timeout settings.
boost::beast::get_lowest_layer(derived().stream()).expires_never();
return make_websocket_session(
ioc_,
derived().release_stream(),
derived().ip(),
std::move(req_),
std::move(buffer_),
backend_,
@@ -229,13 +281,18 @@ public:
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)
return;
BOOST_LOG_TRIVIAL(debug) << tag() << "http::" << __func__
<< " received request from ip = " << *ip
perfLog_.debug() << tag() << "Received request from ip = " << *ip
<< " - posting to WorkQueue";
auto session = derived().shared_from_this();
@@ -256,23 +313,18 @@ public:
dosGuard_,
counters_,
*ip,
session);
session,
perfLog_);
},
dosGuard_.isWhiteListed(*ip)))
{
// Non-whitelist connection rejected due to full connection
// queue
http::response<http::string_body> res{
http::status::ok, req_.version()};
res.set(
http::field::server,
"clio-server-" + Build::getClioVersionString());
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));
lambda_(httpResponse(
http::status::ok,
"application/json",
boost::json::serialize(
RPC::makeError(RPC::RippledError::rpcTOO_BUSY))));
}
}
@@ -318,10 +370,11 @@ handle_request(
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard,
clio::DOSGuard& dosGuard,
RPC::Counters& counters,
std::string const& ip,
std::shared_ptr<Session> http)
std::shared_ptr<Session> http,
clio::Logger& perfLog)
{
auto const httpResponse = [&req](
http::status status,
@@ -348,17 +401,11 @@ handle_request(
return send(httpResponse(
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
{
BOOST_LOG_TRIVIAL(debug)
<< http->tag()
<< "http received request from work queue: " << req.body();
perfLog.debug() << http->tag()
<< "http received request from work queue: "
<< req.body();
boost::json::object request;
std::string responseStr = "";
@@ -405,14 +452,11 @@ handle_request(
boost::json::serialize(
RPC::makeError(RPC::RippledError::rpcBAD_SYNTAX))));
boost::json::object response{{"result", boost::json::object{}}};
boost::json::object& result = response["result"].as_object();
boost::json::object response;
auto [v, timeDiff] =
util::timed([&]() { return RPC::buildResponse(*context); });
auto start = std::chrono::system_clock::now();
auto v = RPC::buildResponse(*context);
auto end = std::chrono::system_clock::now();
auto us =
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
auto us = std::chrono::duration<int, std::milli>(timeDiff);
RPC::logDuration(*context, us);
if (auto status = std::get_if<RPC::Status>(&v))
@@ -420,10 +464,10 @@ handle_request(
counters.rpcErrored(context->method);
auto error = RPC::makeError(*status);
error["request"] = request;
result = error;
response["result"] = error;
BOOST_LOG_TRIVIAL(debug) << http->tag() << __func__
<< " Encountered error: " << responseStr;
perfLog.debug()
<< http->tag() << "Encountered error: " << responseStr;
}
else
{
@@ -431,10 +475,15 @@ handle_request(
// requests as successful.
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"))
result["status"] = "success";
response["result"] = result;
}
boost::json::array warnings;
@@ -457,8 +506,7 @@ handle_request(
}
catch (std::exception const& e)
{
BOOST_LOG_TRIVIAL(error)
<< http->tag() << __func__ << " Caught exception : " << e.what();
perfLog.error() << http->tag() << "Caught exception : " << e.what();
return send(httpResponse(
http::status::internal_server_error,
"application/json",
@@ -466,5 +514,3 @@ handle_request(
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>
@@ -13,6 +31,7 @@ class HttpSession : public HttpBase<HttpSession>,
public std::enable_shared_from_this<HttpSession>
{
boost::beast::tcp_stream stream_;
std::optional<std::string> ip_;
public:
// Take ownership of the socket
@@ -24,7 +43,7 @@ public:
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard,
clio::DOSGuard& dosGuard,
RPC::Counters& counters,
WorkQueue& queue,
boost::beast::flat_buffer buffer)
@@ -41,6 +60,21 @@ public:
std::move(buffer))
, 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&
@@ -57,14 +91,7 @@ public:
std::optional<std::string>
ip()
{
try
{
return stream_.socket().remote_endpoint().address().to_string();
}
catch (std::exception const&)
{
return {};
}
return ip_;
}
// Start the asynchronous operation
@@ -91,5 +118,3 @@ public:
// 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>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
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 <subscriptions/SubscriptionManager.h>
#include <util/Taggable.h>
#include <webserver/HttpSession.h>
@@ -12,6 +27,10 @@
#include <webserver/SslHttpSession.h>
#include <webserver/SslWsSession.h>
#include <boost/asio/dispatch.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <iostream>
class SubscriptionManager;
@@ -23,6 +42,7 @@ class Detector
using std::enable_shared_from_this<
Detector<PlainSession, SslSession>>::shared_from_this;
clio::Logger log_{"WebServer"};
boost::asio::io_context& ioc_;
boost::beast::tcp_stream stream_;
std::optional<std::reference_wrapper<ssl::context>> ctx_;
@@ -31,7 +51,7 @@ class Detector
std::shared_ptr<ETLLoadBalancer> balancer_;
std::shared_ptr<ReportingETL const> etl_;
util::TagDecoratorFactory const& tagFactory_;
DOSGuard& dosGuard_;
clio::DOSGuard& dosGuard_;
RPC::Counters& counters_;
WorkQueue& queue_;
boost::beast::flat_buffer buffer_;
@@ -46,7 +66,7 @@ public:
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard,
clio::DOSGuard& dosGuard,
RPC::Counters& counters,
WorkQueue& queue)
: ioc_(ioc)
@@ -69,8 +89,7 @@ public:
if (ec == net::ssl::error::stream_truncated)
return;
BOOST_LOG_TRIVIAL(info)
<< "Detector failed: " << message << ec.message() << std::endl;
log_.info() << "Detector failed (" << message << "): " << ec.message();
}
// Launch the detector
@@ -137,6 +156,7 @@ void
make_websocket_session(
boost::asio::io_context& ioc,
boost::beast::tcp_stream stream,
std::optional<std::string> const& ip,
http::request<http::string_body> req,
boost::beast::flat_buffer buffer,
std::shared_ptr<BackendInterface const> backend,
@@ -144,13 +164,14 @@ make_websocket_session(
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard,
clio::DOSGuard& dosGuard,
RPC::Counters& counters,
WorkQueue& queue)
{
std::make_shared<WsUpgrader>(
ioc,
std::move(stream),
ip,
backend,
subscriptions,
balancer,
@@ -168,6 +189,7 @@ void
make_websocket_session(
boost::asio::io_context& ioc,
boost::beast::ssl_stream<boost::beast::tcp_stream> stream,
std::optional<std::string> const& ip,
http::request<http::string_body> req,
boost::beast::flat_buffer buffer,
std::shared_ptr<BackendInterface const> backend,
@@ -175,13 +197,14 @@ make_websocket_session(
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard,
clio::DOSGuard& dosGuard,
RPC::Counters& counters,
WorkQueue& queue)
{
std::make_shared<SslWsUpgrader>(
ioc,
std::move(stream),
ip,
backend,
subscriptions,
balancer,
@@ -202,6 +225,7 @@ class Listener
using std::enable_shared_from_this<
Listener<PlainSession, SslSession>>::shared_from_this;
clio::Logger log_{"WebServer"};
boost::asio::io_context& ioc_;
std::optional<std::reference_wrapper<ssl::context>> ctx_;
tcp::acceptor acceptor_;
@@ -210,7 +234,7 @@ class Listener
std::shared_ptr<ETLLoadBalancer> balancer_;
std::shared_ptr<ReportingETL const> etl_;
util::TagDecoratorFactory tagFactory_;
DOSGuard& dosGuard_;
clio::DOSGuard& dosGuard_;
WorkQueue queue_;
RPC::Counters counters_;
@@ -226,7 +250,7 @@ public:
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory tagFactory,
DOSGuard& dosGuard)
clio::DOSGuard& dosGuard)
: ioc_(ioc)
, ctx_(ctx)
, acceptor_(net::make_strand(ioc))
@@ -255,8 +279,7 @@ public:
acceptor_.bind(endpoint, ec);
if (ec)
{
BOOST_LOG_TRIVIAL(error)
<< "Failed to bind to endpoint: " << endpoint
log_.error() << "Failed to bind to endpoint: " << endpoint
<< ". message: " << ec.message();
throw std::runtime_error("Failed to bind to specified endpoint");
}
@@ -265,8 +288,7 @@ public:
acceptor_.listen(net::socket_base::max_listen_connections, ec);
if (ec)
{
BOOST_LOG_TRIVIAL(error)
<< "Failed to listen at endpoint: " << endpoint
log_.error() << "Failed to listen at endpoint: " << endpoint
<< ". message: " << ec.message();
throw std::runtime_error("Failed to listen at specified endpoint");
}
@@ -334,8 +356,9 @@ make_HttpServer(
std::shared_ptr<SubscriptionManager> subscriptions,
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
DOSGuard& dosGuard)
clio::DOSGuard& dosGuard)
{
static clio::Logger log{"WebServer"};
if (!config.contains("server"))
return nullptr;
@@ -348,7 +371,7 @@ make_HttpServer(
auto const maxQueueSize =
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;
auto server = std::make_shared<HttpServer>(
@@ -368,5 +391,3 @@ make_HttpServer(
return 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/beast/core.hpp>
@@ -32,17 +50,19 @@ public:
explicit PlainWsSession(
boost::asio::io_context& ioc,
boost::asio::ip::tcp::socket&& socket,
std::optional<std::string> ip,
std::shared_ptr<BackendInterface const> backend,
std::shared_ptr<SubscriptionManager> subscriptions,
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard,
clio::DOSGuard& dosGuard,
RPC::Counters& counters,
WorkQueue& queue,
boost::beast::flat_buffer&& buffer)
: WsSession(
ioc,
ip,
backend,
subscriptions,
balancer,
@@ -65,19 +85,7 @@ public:
std::optional<std::string>
ip()
{
try
{
return ws()
.next_layer()
.socket()
.remote_endpoint()
.address()
.to_string();
}
catch (std::exception const&)
{
return {};
}
return ip_;
}
~PlainWsSession() = default;
@@ -94,21 +102,23 @@ class WsUpgrader : public std::enable_shared_from_this<WsUpgrader>
std::shared_ptr<ETLLoadBalancer> balancer_;
std::shared_ptr<ReportingETL const> etl_;
util::TagDecoratorFactory const& tagFactory_;
DOSGuard& dosGuard_;
clio::DOSGuard& dosGuard_;
RPC::Counters& counters_;
WorkQueue& queue_;
http::request<http::string_body> req_;
std::optional<std::string> ip_;
public:
WsUpgrader(
boost::asio::io_context& ioc,
boost::asio::ip::tcp::socket&& socket,
std::optional<std::string> ip,
std::shared_ptr<BackendInterface const> backend,
std::shared_ptr<SubscriptionManager> subscriptions,
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard,
clio::DOSGuard& dosGuard,
RPC::Counters& counters,
WorkQueue& queue,
boost::beast::flat_buffer&& b)
@@ -123,17 +133,19 @@ public:
, dosGuard_(dosGuard)
, counters_(counters)
, queue_(queue)
, ip_(ip)
{
}
WsUpgrader(
boost::asio::io_context& ioc,
boost::beast::tcp_stream&& stream,
std::optional<std::string> ip,
std::shared_ptr<BackendInterface const> backend,
std::shared_ptr<SubscriptionManager> subscriptions,
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard,
clio::DOSGuard& dosGuard,
RPC::Counters& counters,
WorkQueue& queue,
boost::beast::flat_buffer&& b,
@@ -150,6 +162,7 @@ public:
, counters_(counters)
, queue_(queue)
, req_(std::move(req))
, ip_(ip)
{
}
@@ -198,6 +211,7 @@ private:
std::make_shared<PlainWsSession>(
ioc_,
http_.release_socket(),
ip_,
backend_,
subscriptions_,
balancer_,
@@ -210,5 +224,3 @@ private:
->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
Copyright (c) 2021 Ripple Labs Inc.
This file is part of clio: https://github.com/XRPLF/clio
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
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
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.
*/
//==============================================================================
#ifndef REPORTING_SSL_H
#define REPORTING_SSL_H
#pragma once
#include <boost/asio/ssl.hpp>
@@ -62,5 +61,3 @@ parse_certs(const char* certFilename, const char* keyFilename)
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>
@@ -13,6 +31,7 @@ class SslHttpSession : public HttpBase<SslHttpSession>,
public std::enable_shared_from_this<SslHttpSession>
{
boost::beast::ssl_stream<boost::beast::tcp_stream> stream_;
std::optional<std::string> ip_;
public:
// Take ownership of the socket
@@ -25,7 +44,7 @@ public:
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard,
clio::DOSGuard& dosGuard,
RPC::Counters& counters,
WorkQueue& queue,
boost::beast::flat_buffer buffer)
@@ -42,6 +61,25 @@ public:
std::move(buffer))
, 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>&
@@ -58,18 +96,7 @@ public:
std::optional<std::string>
ip()
{
try
{
return stream_.next_layer()
.socket()
.remote_endpoint()
.address()
.to_string();
}
catch (std::exception const&)
{
return {};
}
return ip_;
}
// Start the asynchronous operation
@@ -126,5 +153,3 @@ public:
// 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/beast/core.hpp>
@@ -30,17 +48,19 @@ public:
explicit SslWsSession(
boost::asio::io_context& ioc,
boost::beast::ssl_stream<boost::beast::tcp_stream>&& stream,
std::optional<std::string> ip,
std::shared_ptr<BackendInterface const> backend,
std::shared_ptr<SubscriptionManager> subscriptions,
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard,
clio::DOSGuard& dosGuard,
RPC::Counters& counters,
WorkQueue& queue,
boost::beast::flat_buffer&& b)
: WsSession(
ioc,
ip,
backend,
subscriptions,
balancer,
@@ -63,20 +83,7 @@ public:
std::optional<std::string>
ip()
{
try
{
return ws()
.next_layer()
.next_layer()
.socket()
.remote_endpoint()
.address()
.to_string();
}
catch (std::exception const&)
{
return {};
}
return ip_;
}
};
@@ -86,12 +93,13 @@ class SslWsUpgrader : public std::enable_shared_from_this<SslWsUpgrader>
boost::beast::ssl_stream<boost::beast::tcp_stream> https_;
boost::optional<http::request_parser<http::string_body>> parser_;
boost::beast::flat_buffer buffer_;
std::optional<std::string> ip_;
std::shared_ptr<BackendInterface const> backend_;
std::shared_ptr<SubscriptionManager> subscriptions_;
std::shared_ptr<ETLLoadBalancer> balancer_;
std::shared_ptr<ReportingETL const> etl_;
util::TagDecoratorFactory const& tagFactory_;
DOSGuard& dosGuard_;
clio::DOSGuard& dosGuard_;
RPC::Counters& counters_;
WorkQueue& queue_;
http::request<http::string_body> req_;
@@ -99,6 +107,7 @@ class SslWsUpgrader : public std::enable_shared_from_this<SslWsUpgrader>
public:
SslWsUpgrader(
boost::asio::io_context& ioc,
std::optional<std::string> ip,
boost::asio::ip::tcp::socket&& socket,
ssl::context& ctx,
std::shared_ptr<BackendInterface const> backend,
@@ -106,13 +115,14 @@ public:
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard,
clio::DOSGuard& dosGuard,
RPC::Counters& counters,
WorkQueue& queue,
boost::beast::flat_buffer&& b)
: ioc_(ioc)
, https_(std::move(socket), ctx)
, buffer_(std::move(b))
, ip_(ip)
, backend_(backend)
, subscriptions_(subscriptions)
, balancer_(balancer)
@@ -126,12 +136,13 @@ public:
SslWsUpgrader(
boost::asio::io_context& ioc,
boost::beast::ssl_stream<boost::beast::tcp_stream> stream,
std::optional<std::string> ip,
std::shared_ptr<BackendInterface const> backend,
std::shared_ptr<SubscriptionManager> subscriptions,
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard,
clio::DOSGuard& dosGuard,
RPC::Counters& counters,
WorkQueue& queue,
boost::beast::flat_buffer&& b,
@@ -139,6 +150,7 @@ public:
: ioc_(ioc)
, https_(std::move(stream))
, buffer_(std::move(b))
, ip_(ip)
, backend_(backend)
, subscriptions_(subscriptions)
, balancer_(balancer)
@@ -211,6 +223,7 @@ private:
std::make_shared<SslWsSession>(
ioc_,
std::move(https_),
ip_,
backend_,
subscriptions_,
balancer_,
@@ -223,5 +236,3 @@ private:
->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/websocket.hpp>
@@ -7,17 +38,7 @@
#include <iostream>
#include <memory>
#include <backend/BackendInterface.h>
#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>
// TODO: Consider removing these. Visible to anyone including this header.
namespace http = boost::beast::http;
namespace net = boost::asio;
namespace ssl = boost::asio::ssl;
@@ -27,8 +48,8 @@ using tcp = boost::asio::ip::tcp;
inline void
logError(boost::beast::error_code ec, char const* what)
{
BOOST_LOG_TRIVIAL(debug)
<< __func__ << " : " << what << ": " << ec.message() << "\n";
static clio::Logger log{"WebServer"};
log.debug() << what << ": " << ec.message() << "\n";
}
inline boost::json::object
@@ -38,7 +59,6 @@ getDefaultWsResponse(boost::json::value const& id)
if (!id.is_null())
defaultResp["id"] = id;
defaultResp["result"] = boost::json::object_kind;
defaultResp["status"] = "success";
defaultResp["type"] = "response";
@@ -48,6 +68,8 @@ getDefaultWsResponse(boost::json::value const& id)
class WsBase : public util::Taggable
{
protected:
clio::Logger log_{"WebServer"};
clio::Logger perfLog_{"Performance"};
boost::system::error_code ec_;
public:
@@ -100,7 +122,7 @@ class WsSession : public WsBase,
std::shared_ptr<ETLLoadBalancer> balancer_;
std::shared_ptr<ReportingETL const> etl_;
util::TagDecoratorFactory const& tagFactory_;
DOSGuard& dosGuard_;
clio::DOSGuard& dosGuard_;
RPC::Counters& counters_;
WorkQueue& queue_;
std::mutex mtx_;
@@ -108,14 +130,16 @@ class WsSession : public WsBase,
bool sending_ = false;
std::queue<std::shared_ptr<Message>> messages_;
protected:
std::optional<std::string> ip_;
void
wsFail(boost::beast::error_code ec, char const* what)
{
if (!ec_ && ec != boost::asio::error::operation_aborted)
{
ec_ = ec;
BOOST_LOG_TRIVIAL(info)
<< tag() << __func__ << ": " << what << ": " << ec.message();
perfLog_.info() << tag() << ": " << what << ": " << ec.message();
boost::beast::get_lowest_layer(derived().ws()).socket().close(ec);
if (auto manager = subscriptions_.lock(); manager)
@@ -126,12 +150,13 @@ class WsSession : public WsBase,
public:
explicit WsSession(
boost::asio::io_context& ioc,
std::optional<std::string> ip,
std::shared_ptr<BackendInterface const> backend,
std::shared_ptr<SubscriptionManager> subscriptions,
std::shared_ptr<ETLLoadBalancer> balancer,
std::shared_ptr<ReportingETL const> etl,
util::TagDecoratorFactory const& tagFactory,
DOSGuard& dosGuard,
clio::DOSGuard& dosGuard,
RPC::Counters& counters,
WorkQueue& queue,
boost::beast::flat_buffer&& buffer)
@@ -146,12 +171,16 @@ public:
, dosGuard_(dosGuard)
, counters_(counters)
, queue_(queue)
, ip_(ip)
{
BOOST_LOG_TRIVIAL(info) << tag() << "session created";
perfLog_.info() << tag() << "session created";
}
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
@@ -244,7 +273,7 @@ public:
if (ec)
return wsFail(ec, "accept");
BOOST_LOG_TRIVIAL(info) << tag() << "accepting new connection";
perfLog_.info() << tag() << "accepting new connection";
// Read a message
do_read();
@@ -287,7 +316,7 @@ public:
try
{
BOOST_LOG_TRIVIAL(debug)
perfLog_.debug()
<< tag() << "ws received request from work queue : " << request;
auto range = backend_->fetchLedgerRange();
@@ -309,18 +338,16 @@ public:
if (!context)
{
BOOST_LOG_TRIVIAL(warning)
<< tag() << " could not create RPC context";
perfLog_.warn() << tag() << "Could not create RPC context";
return sendError(RPC::RippledError::rpcBAD_SYNTAX);
}
response = getDefaultWsResponse(id);
auto start = std::chrono::system_clock::now();
auto v = RPC::buildResponse(*context);
auto end = std::chrono::system_clock::now();
auto us = std::chrono::duration_cast<std::chrono::microseconds>(
end - start);
auto [v, timeDiff] =
util::timed([&]() { return RPC::buildResponse(*context); });
auto us = std::chrono::duration<int, std::milli>(timeDiff);
logDuration(*context, us);
if (auto status = std::get_if<RPC::Status>(&v))
@@ -339,13 +366,23 @@ public:
{
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)
{
BOOST_LOG_TRIVIAL(error)
<< tag() << __func__ << " caught exception : " << e.what();
perfLog_.error() << tag() << "Caught exception : " << e.what();
return sendError(RPC::RippledError::rpcINTERNAL);
}
@@ -385,8 +422,7 @@ public:
if (!ip)
return;
BOOST_LOG_TRIVIAL(info) << tag() << "ws::" << __func__
<< " received request from ip = " << *ip;
perfLog_.info() << tag() << "Received request from ip = " << *ip;
auto sendError = [this, ip](
auto error,
@@ -399,7 +435,7 @@ public:
e["request"] = request;
auto responseStr = boost::json::serialize(e);
BOOST_LOG_TRIVIAL(trace) << __func__ << " : " << responseStr;
log_.trace() << responseStr;
dosGuard_.add(*ip, responseStr.size());
send(std::move(responseStr));
};
@@ -416,21 +452,23 @@ public:
}(std::move(msg));
boost::json::object request;
if (!raw.is_object())
return sendError(
RPC::RippledError::rpcINVALID_PARAMS, nullptr, request);
request = raw.as_object();
auto id = request.contains("id") ? request.at("id") : nullptr;
if (!dosGuard_.isOk(*ip))
// dosGuard served request++ and check ip address
// dosGuard should check before any request, even invalid request
if (!dosGuard_.request(*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
{
BOOST_LOG_TRIVIAL(debug)
<< tag() << __func__ << " adding to work queue";
request = raw.as_object();
auto id = request.contains("id") ? request.at("id") : nullptr;
perfLog_.debug() << tag() << "Adding to work queue";
if (!queue_.postCoro(
[shared_this = shared_from_this(),
@@ -445,5 +483,3 @@ public:
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 <config/Config.h>
#include <etl/ReportingETL.h>
#include <gtest/gtest.h>
#include <log/Logger.h>
#include <rpc/RPCHelpers.h>
#include <util/Fixtures.h>
#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/trivial.hpp>
#include <backend/BackendFactory.h>
#include <backend/BackendInterface.h>
#include <config/Config.h>
#include <gtest/gtest.h>
TEST(BackendTest, Basic)
#include <algorithm>
class BackendTest : public NoLoggerFixture
{
};
TEST_F(BackendTest, Basic)
{
boost::asio::io_context ioc;
std::optional<boost::asio::io_context::work> work;
@@ -21,7 +46,7 @@ TEST(BackendTest, Basic)
boost::asio::spawn(
ioc, [&done, &work, &ioc](boost::asio::yield_context yield) {
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::to_string(std::chrono::system_clock::now()
.time_since_epoch()
@@ -41,7 +66,6 @@ TEST(BackendTest, Basic)
std::vector<boost::json::object> configs = {cassandraConfig};
for (auto& config : configs)
{
std::cout << keyspace << std::endl;
auto backend = Backend::make_Backend(ioc, clio::Config{config});
std::string rawHeader =
@@ -107,10 +131,8 @@ TEST(BackendTest, Basic)
}
{
std::cout << "fetching ledger by sequence" << std::endl;
auto retLgr =
backend->fetchLedgerBySequence(lgrInfo.seq, yield);
std::cout << "fetched ledger by sequence" << std::endl;
ASSERT_TRUE(retLgr.has_value());
EXPECT_EQ(retLgr->seq, lgrInfo.seq);
EXPECT_EQ(
@@ -147,10 +169,8 @@ TEST(BackendTest, Basic)
EXPECT_EQ(seq, lgrInfoNext.seq);
}
{
std::cout << "fetching ledger by sequence" << std::endl;
auto retLgr =
backend->fetchLedgerBySequence(lgrInfoNext.seq, yield);
std::cout << "fetched ledger by sequence" << std::endl;
EXPECT_TRUE(retLgr.has_value());
EXPECT_EQ(retLgr->seq, lgrInfoNext.seq);
EXPECT_EQ(
@@ -802,9 +822,6 @@ TEST(BackendTest, Basic)
auto objs,
auto accountTx,
auto state) {
std::cout
<< "writing ledger = " << std::to_string(lgrInfo.seq)
<< std::endl;
backend->startWrites();
backend->writeLedger(
@@ -978,11 +995,6 @@ TEST(BackendTest, Basic)
uint32_t limit = 10;
page = backend->fetchLedgerPage(
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)
// EXPECT_EQ(page.objects.size(), limit);
retObjs.insert(
@@ -1007,7 +1019,6 @@ TEST(BackendTest, Basic)
}
}
if (found != (obj.second.size() != 0))
std::cout << ripple::strHex(obj.first) << std::endl;
ASSERT_EQ(found, obj.second.size() != 0);
}
};
@@ -1148,15 +1159,12 @@ TEST(BackendTest, Basic)
for (auto [seq, diff] : state)
{
std::cout << "flatteneing" << std::endl;
auto flat = flatten(seq);
std::cout << "flattened" << std::endl;
checkLedger(
lgrInfos[seq],
allTxns[seq],
flat,
flattenAccountTx(seq));
std::cout << "checked" << std::endl;
}
}
@@ -1168,11 +1176,11 @@ TEST(BackendTest, Basic)
EXPECT_EQ(done, true);
}
TEST(Backend, cache)
TEST_F(BackendTest, cache)
{
using namespace Backend;
boost::log::core::get()->set_filter(
boost::log::trivial::severity >= boost::log::trivial::warning);
clio::log_severity >= clio::Severity::WRN);
SimpleCache cache;
ASSERT_FALSE(cache.isFull());
cache.setFull();
@@ -1410,11 +1418,11 @@ TEST(Backend, cache)
}
}
TEST(Backend, cacheBackground)
TEST_F(BackendTest, cacheBackground)
{
using namespace Backend;
boost::log::core::get()->set_filter(
boost::log::trivial::severity >= boost::log::trivial::warning);
clio::log_severity >= clio::Severity::WRN);
SimpleCache cache;
ASSERT_FALSE(cache.isFull());
ASSERT_EQ(cache.size(), 0);
@@ -1814,7 +1822,7 @@ TEST(Backend, cacheBackground)
ASSERT_EQ(idx, allObjs.size());
}
TEST(Backend, cacheIntegration)
TEST_F(BackendTest, cacheIntegration)
{
boost::asio::io_context ioc;
std::optional<boost::asio::io_context::work> work;
@@ -1824,7 +1832,7 @@ TEST(Backend, cacheIntegration)
boost::asio::spawn(
ioc, [&ioc, &done, &work](boost::asio::yield_context yield) {
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::to_string(std::chrono::system_clock::now()
.time_since_epoch()
@@ -1844,7 +1852,6 @@ TEST(Backend, cacheIntegration)
std::vector<boost::json::object> configs = {cassandraConfig};
for (auto& config : configs)
{
std::cout << keyspace << std::endl;
auto backend = Backend::make_Backend(ioc, clio::Config{config});
backend->cache().setFull();
@@ -1927,10 +1934,8 @@ TEST(Backend, cacheIntegration)
}
{
std::cout << "fetching ledger by sequence" << std::endl;
auto retLgr =
backend->fetchLedgerBySequence(lgrInfo.seq, yield);
std::cout << "fetched ledger by sequence" << std::endl;
ASSERT_TRUE(retLgr.has_value());
EXPECT_EQ(retLgr->seq, lgrInfo.seq);
EXPECT_EQ(
@@ -1967,10 +1972,8 @@ TEST(Backend, cacheIntegration)
EXPECT_EQ(seq, lgrInfoNext.seq);
}
{
std::cout << "fetching ledger by sequence" << std::endl;
auto retLgr =
backend->fetchLedgerBySequence(lgrInfoNext.seq, yield);
std::cout << "fetched ledger by sequence" << std::endl;
EXPECT_TRUE(retLgr.has_value());
EXPECT_EQ(retLgr->seq, lgrInfoNext.seq);
EXPECT_EQ(
@@ -2226,8 +2229,6 @@ TEST(Backend, cacheIntegration)
return lgrInfo;
};
auto writeLedger = [&](auto lgrInfo, auto objs, auto state) {
std::cout << "writing ledger = "
<< std::to_string(lgrInfo.seq);
backend->startWrites();
backend->writeLedger(
@@ -2344,11 +2345,6 @@ TEST(Backend, cacheIntegration)
uint32_t limit = 10;
page = backend->fetchLedgerPage(
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)
// EXPECT_EQ(page.objects.size(), limit);
retObjs.insert(
@@ -2372,7 +2368,6 @@ TEST(Backend, cacheIntegration)
}
}
if (found != (obj.second.size() != 0))
std::cout << ripple::strHex(obj.first) << std::endl;
ASSERT_EQ(found, obj.second.size() != 0);
}
};
@@ -2437,11 +2432,8 @@ TEST(Backend, cacheIntegration)
for (auto [seq, diff] : state)
{
std::cout << "flatteneing" << std::endl;
auto flat = flatten(seq);
std::cout << "flattened" << std::endl;
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 <util/Fixtures.h>
#include <boost/filesystem.hpp>
#include <boost/json/parse.hpp>
#include <boost/log/core.hpp>
#include <gtest/gtest.h>
#include <cstdio>
#include <exception>
@@ -34,17 +52,10 @@ constexpr static auto JSONData = R"JSON(
}
)JSON";
class ConfigTest : public ::testing::Test
class ConfigTest : public NoLoggerFixture
{
protected:
Config cfg{json::parse(JSONData)};
void
SetUp() override
{
// disable logging in test
core::get()->set_logging_enabled(false);
}
};
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 <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;
};