Compare commits

..

2 Commits

Author SHA1 Message Date
Sergey Kuznetsov
7b18e28c47 fix: Fix extra brackets in warnings (#1519)
Fixes #1518
2024-07-05 12:05:14 +01:00
cyan317
4940d463dc Fix empty currency (#1481) 2024-06-21 13:01:14 +01:00
468 changed files with 5332 additions and 25075 deletions

View File

@@ -142,7 +142,7 @@ CheckOptions:
readability-braces-around-statements.ShortStatementLines: 2
bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true
bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc
misc-include-cleaner.IgnoreHeaders: '.*/(detail|impl)/.*;.*(expected|unexpected).*;.*ranges_lower_bound\.h;time.h;stdlib.h'
misc-include-cleaner.IgnoreHeaders: '.*/(detail|impl)/.*;.*(expected|unexpected).*'
HeaderFilterRegex: '^.*/(src|tests)/.*\.(h|hpp)$'
WarningsAsErrors: '*'

View File

@@ -8,6 +8,3 @@ Diagnostics:
IgnoreHeader:
- ".*/(detail|impl)/.*"
- ".*expected.*"
- ".*ranges_lower_bound.h"
- "time.h"
- "stdlib.h"

View File

@@ -13,36 +13,14 @@ TMPDIR=${ROOT}/.cache/doxygen
TMPFILE=${TMPDIR}/docs.log
DOCDIR=${TMPDIR}/out
# Check doxygen is at all installed
if [ -z "$DOXYGEN" ]; then
# No hard error if doxygen is not installed yet
cat <<EOF
WARNING
-----------------------------------------------------------------------------
'doxygen' is required to check documentation.
Please install it for next time.
Your changes may fail to pass CI once pushed.
-----------------------------------------------------------------------------
EOF
exit 0
fi
# Check version of doxygen is at least 1.12
version=$($DOXYGEN --version | grep -o '[0-9\.]*')
if [[ "1.12.0" > "$version" ]]; then
# No hard error if doxygen version is not the one we want - let CI deal with it
cat <<EOF
ERROR
-----------------------------------------------------------------------------
A minimum of version 1.12 of `which doxygen` is required.
Your version is $version. Please upgrade it for next time.
Your changes may fail to pass CI once pushed.
'doxygen' is required to check documentation.
Please install it for next time. For the time being it's on CI.
-----------------------------------------------------------------------------
EOF

View File

@@ -5,20 +5,6 @@
# This script checks the format of the code and cmake files.
# In many cases it will automatically fix the issues and abort the commit.
no_formatted_directories_staged() {
staged_directories=$(git diff-index --cached --name-only HEAD | awk -F/ '{print $1}')
for sd in $staged_directories; do
if [[ "$sd" =~ ^(benchmark|cmake|src|tests)$ ]]; then
return 1
fi
done
return 0
}
if no_formatted_directories_staged ; then
exit 0
fi
echo "+ Checking code format..."
# paths to check and re-format
@@ -26,12 +12,12 @@ sources="src tests"
formatter="clang-format -i"
version=$($formatter --version | grep -o '[0-9\.]*')
if [[ "19.0.0" > "$version" ]]; then
if [[ "18.0.0" > "$version" ]]; then
cat <<EOF
ERROR
-----------------------------------------------------------------------------
A minimum of version 19 of `which clang-format` is required.
A minimum of version 18 of `which clang-format` is required.
Your version is $version.
Please fix paths and run again.
-----------------------------------------------------------------------------

View File

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

View File

@@ -1,58 +1,3 @@
#!/bin/sh
# git for-each-ref refs/tags # see which tags are annotated and which are lightweight. Annotated tags are "tag" objects.
# # Set these so your commits and tags are always signed
# git config commit.gpgsign true
# git config tag.gpgsign true
verify_commit_signed() {
if git verify-commit HEAD &> /dev/null; then
:
# echo "HEAD commit seems signed..."
else
echo "HEAD commit isn't signed!"
exit 1
fi
}
verify_tag() {
if git describe --exact-match --tags HEAD &> /dev/null; then
: # You might be ok to push
# echo "Tag is annotated."
return 0
else
echo "Tag for [$version] not an annotated tag."
exit 1
fi
}
verify_tag_signed() {
if git verify-tag "$version" &> /dev/null ; then
: # ok, I guess we'll let you push
# echo "Tag appears signed"
return 0
else
echo "$version tag isn't signed"
echo "Sign it with [git tag -ams\"$version\" $version]"
exit 1
fi
}
while read local_ref local_oid remote_ref remote_oid; do
# Check some things if we're pushing a branch called "release/"
if echo "$remote_ref" | grep ^refs\/heads\/release\/ &> /dev/null ; then
version=$(git tag --points-at HEAD)
echo "Looks like you're trying to push a $version release..."
echo "Making sure you've signed and tagged it."
if verify_commit_signed && verify_tag && verify_tag_signed ; then
: # Ok, I guess you can push
else
exit 1
fi
fi
done
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'pre-push' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; }
git lfs pre-push "$@"

View File

@@ -1,66 +0,0 @@
name: Build and push Docker image
description: Build and push Docker image to DockerHub and GitHub Container Registry
inputs:
image_name:
description: Name of the image to build
required: true
push_image:
description: Whether to push the image to the registry (true/false)
required: true
directory:
description: The directory containing the Dockerfile
required: true
tags:
description: Comma separated tags to apply to the image
required: true
platforms:
description: Platforms to build the image for (e.g. linux/amd64,linux/arm64)
required: true
description:
description: Short description of the image
required: true
runs:
using: composite
steps:
- name: Login to DockerHub
if: ${{ inputs.push_image == 'true' }}
uses: docker/login-action@v3
with:
username: ${{ env.DOCKERHUB_USER }}
password: ${{ env.DOCKERHUB_PW }}
- name: Login to GitHub Container Registry
if: ${{ inputs.push_image == 'true' }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ env.GITHUB_TOKEN }}
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/metadata-action@v5
id: meta
with:
images: ${{ inputs.image_name }}
tags: ${{ inputs.tags }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: ${{ inputs.directory }}
platforms: ${{ inputs.platforms }}
push: ${{ inputs.push_image == 'true' }}
tags: ${{ steps.meta.outputs.tags }}
- name: Update DockerHub description
if: ${{ inputs.push_image == 'true' }}
uses: peter-evans/dockerhub-description@v4
with:
username: ${{ env.DOCKERHUB_USER }}
password: ${{ env.DOCKERHUB_PW }}
repository: ${{ inputs.image_name }}
short-description: ${{ inputs.description }}
readme-filepath: ${{ inputs.directory }}/README.md

View File

@@ -1,16 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "04:00"
timezone: "Etc/GMT"
reviewers:
- "cindyyan317"
- "godexsoft"
- "kuznetsss"
commit-message:
prefix: "[CI] "
target-branch: "develop"

View File

@@ -23,7 +23,7 @@ jobs:
run: |
./.githooks/check-format --diff
shell: bash
check_docs:
name: Check documentation
runs-on: ubuntu-20.04
@@ -211,7 +211,6 @@ jobs:
- uses: actions/download-artifact@v4
with:
name: clio_tests_${{ runner.os }}_${{ matrix.build_type }}_${{ matrix.conan_profile }}
- name: Run clio_tests
run: |
chmod +x ./clio_tests

View File

@@ -1,95 +0,0 @@
name: Build and publish Clio docker image
on:
workflow_call:
inputs:
tags:
required: true
type: string
description: Comma separated tags for docker image
artifact_name:
type: string
description: Name of Github artifact to put into docker image
strip_binary:
type: boolean
description: Whether to strip clio binary
default: true
publish_image:
type: boolean
description: Whether to publish docker image
required: true
workflow_dispatch:
inputs:
tags:
required: true
type: string
description: Comma separated tags for docker image
clio_server_binary_url:
required: true
type: string
description: Url to download clio_server binary from
binary_sha256:
required: true
type: string
description: sha256 hash of the binary
strip_binary:
type: boolean
description: Whether to strip clio binary
default: true
jobs:
build_and_publish_image:
name: Build and publish image
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
- name: Download Clio binary from artifact
if: ${{ inputs.artifact_name != null }}
uses: actions/download-artifact@v4
with:
name: ${{ inputs.artifact_name }}
path: ./docker/clio/artifact/
- name: Download Clio binary from url
if: ${{ inputs.clio_server_binary_url != null }}
shell: bash
run: |
wget ${{inputs.clio_server_binary_url}} -P ./docker/clio/artifact/
if [ "$(sha256sum ./docker/clio/clio_server | awk '{print $1}')" != "${{inputs.binary_sha256}}" ]; then
echo "Binary sha256 sum doesn't match"
exit 1
fi
- name: Unpack binary
shell: bash
run: |
sudo apt update && sudo apt install -y tar unzip
cd docker/clio/artifact
artifact=$(find . -type f)
if [[ $artifact == *.zip ]]; then
unzip $artifact
elif [[ $artifact == *.tar.gz ]]; then
tar -xvf $artifact
fi
mv clio_server ../
cd ../
rm -rf ./artifact
- name: Strip binary
if: ${{ inputs.strip_binary }}
shell: bash
run: strip ./docker/clio/clio_server
- name: Build Docker image
uses: ./.github/actions/build_docker_image
env:
DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }}
DOCKERHUB_PW: ${{ secrets.DOCKERHUB_PW }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
image_name: rippleci/clio
push_image: ${{ inputs.publish_image }}
directory: docker/clio
tags: ${{ inputs.tags }}
platforms: linux/amd64
description: Clio is an XRP Ledger API server.

View File

@@ -47,7 +47,7 @@ jobs:
- name: Upload clio_tests
uses: actions/upload-artifact@v4
with:
name: clio_tests_check_libxrpl
name: clio_tests_libxrpl-${{ github.event.client_payload.version }}
path: build/clio_tests
run_tests:
@@ -60,7 +60,7 @@ jobs:
steps:
- uses: actions/download-artifact@v4
with:
name: clio_tests_check_libxrpl
name: clio_tests_libxrpl-${{ github.event.client_payload.version }}
- name: Run clio_tests
run: |

View File

@@ -1,18 +0,0 @@
name: Check PR title
on:
pull_request:
types: [opened, edited, reopened, synchronize]
branches: [develop]
jobs:
check_title:
runs-on: ubuntu-20.04
# permissions:
# pull-requests: write
steps:
- uses: ytanikin/PRConventionalCommits@1.3.0
with:
task_types: '["build","feat","fix","docs","test","ci","style","refactor","perf","chore"]'
add_label: false
# Turned off labelling because it leads to an error, see https://github.com/ytanikin/PRConventionalCommits/issues/19
# custom_labels: '{"build":"build", "feat":"enhancement", "fix":"bug", "docs":"documentation", "test":"testability", "ci":"ci", "style":"refactoring", "refactor":"refactoring", "perf":"performance", "chore":"tooling"}'

View File

@@ -60,7 +60,7 @@ jobs:
shell: bash
id: run_clang_tidy
run: |
run-clang-tidy-19 -p build -j ${{ steps.number_of_threads.outputs.threads_number }} -fix -quiet 1>output.txt
run-clang-tidy-18 -p build -j ${{ steps.number_of_threads.outputs.threads_number }} -fix -quiet 1>output.txt
- name: Check format
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
@@ -89,7 +89,7 @@ jobs:
List of the issues found: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/
- uses: crazy-max/ghaction-import-gpg@v6
- uses: crazy-max/ghaction-import-gpg@v5
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
with:
gpg_private_key: ${{ secrets.ACTIONS_GPG_PRIVATE_KEY }}
@@ -99,7 +99,7 @@ jobs:
- name: Create PR with fixes
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v5
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
@@ -109,7 +109,7 @@ jobs:
branch: "clang_tidy/autofix"
branch-suffix: timestamp
delete-branch: true
title: "style: clang-tidy auto fixes"
title: "[CI] clang-tidy auto fixes"
body: "Fixes #${{ steps.create_issue.outputs.created_issue_id }}. Please review and commit clang-tidy fixes."
reviewers: "cindyyan317,godexsoft,kuznetsss"

View File

@@ -17,7 +17,7 @@ jobs:
id: check
shell: bash
run: |
passed=$(if [[ $(git log -1 --pretty=format:%s | grep 'style: clang-tidy auto fixes') ]]; then echo 'true' ; else echo 'false' ; fi)
passed=$(if [[ $(git log -1 --pretty=format:%s | grep '\[CI\] clang-tidy auto fixes') ]]; then echo 'true' ; else echo 'false' ; fi)
echo "passed=$passed" >> $GITHUB_OUTPUT
- name: Run clang-tidy workflow

View File

@@ -34,7 +34,7 @@ jobs:
cmake ../docs && cmake --build . --target docs
- name: Setup Pages
uses: actions/configure-pages@v5
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3

View File

@@ -3,10 +3,6 @@ on:
schedule:
- cron: '0 5 * * 1-5'
workflow_dispatch:
pull_request:
paths:
- '.github/workflows/nightly.yml'
- '.github/workflows/build_clio_docker_image.yml'
jobs:
build:
@@ -17,15 +13,12 @@ jobs:
include:
- os: macos14
build_type: Release
static: false
- os: heavy
build_type: Release
static: true
container:
image: rippleci/clio_ci:latest
- os: heavy
build_type: Debug
static: true
container:
image: rippleci/clio_ci:latest
runs-on: [self-hosted, "${{ matrix.os }}"]
@@ -57,7 +50,6 @@ jobs:
conan_profile: ${{ steps.conan.outputs.conan_profile }}
conan_cache_hit: ${{ steps.restore_cache.outputs.conan_cache_hit }}
build_type: ${{ matrix.build_type }}
static: ${{ matrix.static }}
- name: Build Clio
uses: ./.github/actions/build_clio
@@ -138,7 +130,6 @@ jobs:
./clio_integration_tests --backend_host=scylladb
nightly_release:
if: ${{ github.event_name != 'pull_request' }}
needs: run_tests
runs-on: ubuntu-20.04
env:
@@ -152,13 +143,13 @@ jobs:
- uses: actions/download-artifact@v4
with:
path: nightly_release
pattern: clio_server_*
- name: Prepare files
shell: bash
run: |
cp ${{ github.workspace }}/.github/workflows/nightly_notes.md "${RUNNER_TEMP}/nightly_notes.md"
cd nightly_release
rm -r clio_*tests*
for d in $(ls); do
archive_name=$(ls $d)
mv ${d}/${archive_name} ./
@@ -181,21 +172,9 @@ jobs:
--target $GITHUB_SHA --notes-file "${RUNNER_TEMP}/nightly_notes.md" \
./nightly_release/clio_server*
build_and_publish_docker_image:
uses: ./.github/workflows/build_clio_docker_image.yml
needs: run_tests
secrets: inherit
with:
tags: |
type=raw,value=nightly
type=raw,value=${{ github.sha }}
artifact_name: clio_server_Linux_Release
strip_binary: true
publish_image: ${{ github.event_name != 'pull_request' }}
create_issue_on_failure:
needs: [build, run_tests, nightly_release, build_and_publish_docker_image]
if: ${{ always() && contains(needs.*.result, 'failure') && github.event_name != 'pull_request' }}
needs: [build, run_tests, nightly_release]
if: ${{ always() && contains(needs.*.result, 'failure') }}
runs-on: ubuntu-20.04
permissions:
contents: write

View File

@@ -18,19 +18,30 @@ jobs:
name: Build and push docker image
runs-on: [self-hosted, heavy]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/build_docker_image
env:
DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }}
DOCKERHUB_PW: ${{ secrets.DOCKERHUB_PW }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
if: ${{ github.event_name != 'pull_request' }}
uses: docker/login-action@v3
with:
image_name: rippleci/clio_ci
push_image: ${{ github.event_name != 'pull_request' }}
directory: docker/ci
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_PW }}
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/metadata-action@v5
id: meta
with:
images: rippleci/clio_ci
tags: |
type=raw,value=latest
type=raw,value=gcc_12_clang_16
type=raw,value=${{ github.sha }}
type=raw,value=${{ env.GITHUB_SHA }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: ${{ github.workspace }}/docker/ci
platforms: linux/amd64,linux/arm64
description: CI image for XRPLF/clio.
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}

View File

@@ -23,7 +23,7 @@ jobs:
- name: Upload coverage report
if: ${{ hashFiles('build/coverage_report.xml') != '' }}
uses: wandalen/wretry.action@v3.7.2
uses: wandalen/wretry.action@v1.4.10
with:
action: codecov/codecov-action@v4
with: |

2
.gitignore vendored
View File

@@ -8,4 +8,4 @@
.DS_Store
CMakeUserPresets.json
config.json
src/util/build/Build.cpp
src/main/impl/Build.cpp

View File

@@ -21,7 +21,7 @@ git config --local core.hooksPath .githooks
```
## Git hooks dependencies
The pre-commit hook requires `clang-format >= 19.0.0` and `cmake-format` to be installed on your machine.
The pre-commit hook requires `clang-format >= 18.0.0` and `cmake-format` to be installed on your machine.
`clang-format` can be installed using `brew` on macOS and default package manager on Linux.
`cmake-format` can be installed using `pip`.
The hook will also attempt to automatically use `doxygen` to verify that everything public in the codebase is covered by doc comments. If `doxygen` is not installed, the hook will raise a warning suggesting to install `doxygen` for future commits.
@@ -72,9 +72,6 @@ git push --force
Clio uses `ccache` to speed up compilation. If you want to use it, please make sure it is installed on your machine.
CMake will automatically detect it and use it if it is available.
## Opening a pull request
When a pull request is open CI will perform checks on the new code.
Title of the pull request and squashed commit should follow [conventional commits specification](https://www.conventionalcommits.org/en/v1.0.0/).
## Fixing issues found during code review
While your code is in review, it's possible that some changes will be requested by reviewer(s).
@@ -105,7 +102,7 @@ The button for that is near the bottom of the PR's page on GitHub.
This is a non-exhaustive list of recommended style guidelines. These are not always strictly enforced and serve as a way to keep the codebase coherent.
## Formatting
Code must conform to `clang-format` version 19, unless the result would be unreasonably difficult to read or maintain.
Code must conform to `clang-format` version 18, unless the result would be unreasonably difficult to read or maintain.
In most cases the pre-commit hook will take care of formatting and will fix any issues automatically.
To manually format your code, use `clang-format -i <your changed files>` for C++ files and `cmake-format -i <your changed files>` for CMake files.

View File

@@ -12,5 +12,5 @@ target_sources(
include(deps/gbench)
target_include_directories(clio_benchmark PRIVATE .)
target_link_libraries(clio_benchmark PUBLIC clio_etl benchmark::benchmark_main)
target_link_libraries(clio_benchmark PUBLIC clio benchmark::benchmark_main)
set_target_properties(clio_benchmark PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})

View File

@@ -31,6 +31,7 @@
#include <cstdint>
#include <latch>
#include <optional>
#include <stdexcept>
#include <thread>
#include <vector>

View File

@@ -17,12 +17,11 @@
*/
//==============================================================================
#include "util/build/Build.hpp"
#include "main/Build.hpp"
#include <string>
namespace util::build {
namespace Build {
static constexpr char versionString[] = "@CLIO_VERSION@";
std::string const&
@@ -39,4 +38,4 @@ getClioFullVersionString()
return value;
}
} // namespace util::build
} // namespace Build

View File

@@ -8,7 +8,7 @@ if (lint)
endif ()
message(STATUS "Using clang-tidy from CLIO_CLANG_TIDY_BIN")
else ()
find_program(_CLANG_TIDY_BIN NAMES "clang-tidy-19" "clang-tidy" REQUIRED)
find_program(_CLANG_TIDY_BIN NAMES "clang-tidy-18" "clang-tidy" REQUIRED)
endif ()
if (NOT _CLANG_TIDY_BIN)

View File

@@ -45,4 +45,4 @@ endif ()
message(STATUS "Build version: ${CLIO_VERSION}")
configure_file(${CMAKE_CURRENT_LIST_DIR}/Build.cpp.in ${CMAKE_CURRENT_LIST_DIR}/../src/util/build/Build.cpp)
configure_file(${CMAKE_CURRENT_LIST_DIR}/Build.cpp.in ${CMAKE_CURRENT_LIST_DIR}/../src/main/impl/Build.cpp)

View File

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

View File

@@ -1,15 +0,0 @@
# CI image for XRPLF/clio
This image contains an environment to build [Clio](https://github.com/XRPLF/clio), check code and documentation.
It is used in [Clio Github Actions](https://github.com/XRPLF/clio/actions) but can also be used to compile Clio locally.
The image is based on Ubuntu 20.04 and contains:
- clang 16
- gcc 12.3
- doxygen 1.10
- gh 2.40
- ccache 4.8.3
- conan
- and some other useful tools
Conan is set up to build Clio without any additional steps. There are two preset conan profiles: `clang` and `gcc` to use corresponding compiler.

View File

@@ -6,10 +6,10 @@ SHELL ["/bin/bash", "-c"]
USER root
WORKDIR /root
ENV CCACHE_VERSION=4.10.2 \
LLVM_TOOLS_VERSION=19 \
ENV CCACHE_VERSION=4.8.3 \
LLVM_TOOLS_VERSION=18 \
GH_VERSION=2.40.0 \
DOXYGEN_VERSION=1.12.0
DOXYGEN_VERSION=1.10.0
# Add repositories
RUN apt-get -qq update \

View File

@@ -1,23 +0,0 @@
# Clio official docker image
[Clio](https://github.com/XRPLF/clio) is an XRP Ledger API server optimized for RPC calls over WebSocket or JSON-RPC.
It stores validated historical ledger and transaction data in a space efficient format.
This image contains `clio_server` binary allowing users to run Clio easily.
## Clio configuration file
Please note that while Clio requires a configuration file, this image doesn't include any default config.
Your configuration file should be mounted under the path `/opt/clio/etc/config.json`.
Clio repository provides an [example](https://github.com/XRPLF/clio/blob/develop/docs/examples/config/example-config.json) of the configuration file.
Config file recommendations:
- Set `log_to_console` to `false` if you want to avoid logs being written to `stdout`.
- Set `log_directory` to `/opt/clio/log` to store logs in a volume.
## Usage
The following command can be used to run Clio in docker (assuming server's port is `51233` in your config):
```bash
docker run -d -v <path to your config.json>:/opt/clio/etc/config.json -v <path to store logs>:/opt/clio/log -p 51233:51233 rippleci/clio
```

View File

@@ -1,16 +0,0 @@
FROM ubuntu:22.04
COPY ./clio_server /opt/clio/bin/clio_server
RUN ln -s /opt/clio/bin/clio_server /usr/local/bin/clio_server && \
mkdir -p /opt/clio/etc/ && \
mkdir -p /opt/clio/log/ && \
groupadd -g 10001 clio && \
useradd -u 10000 -g 10001 -s /bin/bash clio && \
chown clio:clio /opt/clio/log && \
apt update && \
apt install -y libatomic1
USER clio
ENTRYPOINT ["/opt/clio/bin/clio_server"]
CMD ["--conf", "/opt/clio/etc/config.json"]

View File

@@ -1,3 +1,4 @@
version: '3.7'
services:
clio_develop:
image: rippleci/clio_ci:latest

View File

@@ -141,43 +141,3 @@ If you wish to develop against a `rippled` instance running in standalone mode t
1. Advance the `rippled` ledger to at least ledger 256.
2. Wait 10 minutes before first starting Clio against this standalone node.
## Building with a Custom `libxrpl`
Sometimes, during development, you need to build against a custom version of `libxrpl`. (For example, you may be developing compatibility for a proposed amendment that is not yet merged to the main `rippled` codebase.) To build Clio with compatibility for a custom fork or branch of `rippled`, follow these steps:
1. First, pull/clone the appropriate `rippled` fork and switch to the branch you want to build. For example, the following example uses an in-development build with [XLS-33d Multi-Purpose Tokens](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033d-multi-purpose-tokens):
```sh
git clone https://github.com/shawnxie999/rippled/
cd rippled
git switch mpt-1.1
```
2. Export a custom package to your local Conan store using a user/channel:
```sh
conan export . my/feature
```
3. Patch your local Clio build to use the right package.
Edit `conanfile.py` (from the Clio repository root). Replace the `xrpl` requirement with the custom package version from the previous step. This must also include the current version number from your `rippled` branch. For example:
```py
# ... (excerpt from conanfile.py)
requires = [
'boost/1.82.0',
'cassandra-cpp-driver/2.17.0',
'fmt/10.1.1',
'protobuf/3.21.9',
'grpc/1.50.1',
'openssl/1.1.1u',
'xrpl/2.3.0-b1@my/feature', # Update this line
'libbacktrace/cci.20210118'
]
```
4. Build Clio as you would have before.
See [Building Clio](#building-clio) for details.

View File

@@ -31,7 +31,7 @@
"etl_sources": [
{
"ip": "127.0.0.1",
"ws_port": "6005",
"ws_port": "6006",
"grpc_port": "50051"
}
],
@@ -39,9 +39,6 @@
"cache_timeout": 0.250, // in seconds, could be 0, which means no cache
"request_timeout": 10.0 // time for Clio to wait for rippled to reply on a forwarded request (default is 10 seconds)
},
"rpc": {
"cache_timeout": 0.5 // in seconds, could be 0, which means no cache for rpc
},
"dos_guard": {
// Comma-separated list of IPs to exclude from rate limiting
"whitelist": [
@@ -70,14 +67,7 @@
"admin_password": "xrp",
// If local_admin is true, Clio will consider requests come from 127.0.0.1 as admin requests
// It's true by default unless admin_password is set,'local_admin' : true and 'admin_password' can not be set at the same time
"local_admin": false,
"processing_policy": "parallel", // Could be "sequent" or "parallel".
// For sequent policy request from one client connection will be processed one by one and the next one will not be read before
// the previous one is processed. For parallel policy Clio will take all requests and process them in parallel and
// send a reply for each request whenever it is ready.
"parallel_requests_limit": 10, // Optional parameter, used only if "processing_strategy" is "parallel". It limits the number of requests for one client connection processed in parallel. Infinite if not specified.
// Max number of responses to queue up before sent successfully. If a client's waiting queue is too long, the server will close the connection.
"ws_max_sending_queue_size": 1500
"local_admin": false
},
// Time in seconds for graceful shutdown. Defaults to 10 seconds. Not fully implemented yet.
"graceful_period": 10.0,

View File

@@ -14,7 +14,7 @@ You can find an example docker-compose file, with Prometheus and Grafana configs
## Using `clang-tidy` for static analysis
The minimum [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) version required is 19.0.
The minimum [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) version required is 17.0.
Clang-tidy can be run by Cmake when building the project. To achieve this, you just need to provide the option `-o lint=True` for the `conan install` command:
@@ -26,5 +26,5 @@ By default Cmake will try to find `clang-tidy` automatically in your system.
To force Cmake to use your desired binary, set the `CLIO_CLANG_TIDY_BIN` environment variable to the path of the `clang-tidy` binary. For example:
```sh
export CLIO_CLANG_TIDY_BIN=/opt/homebrew/opt/llvm@19/bin/clang-tidy
export CLIO_CLANG_TIDY_BIN=/opt/homebrew/opt/llvm@17/bin/clang-tidy
```

View File

@@ -1,9 +1,7 @@
add_subdirectory(util)
add_subdirectory(data)
add_subdirectory(etl)
add_subdirectory(etlng)
add_subdirectory(feed)
add_subdirectory(rpc)
add_subdirectory(web)
add_subdirectory(app)
add_subdirectory(main)

View File

@@ -1,4 +0,0 @@
add_library(clio_app)
target_sources(clio_app PRIVATE CliArgs.cpp ClioApplication.cpp)
target_link_libraries(clio_app PUBLIC clio_etl clio_etlng clio_feed clio_web clio_rpc)

View File

@@ -1,70 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "app/CliArgs.hpp"
#include "util/build/Build.hpp"
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/positional_options.hpp>
#include <boost/program_options/value_semantic.hpp>
#include <boost/program_options/variables_map.hpp>
#include <cstdlib>
#include <iostream>
#include <string>
#include <utility>
namespace app {
CliArgs::Action
CliArgs::parse(int argc, char const* argv[])
{
namespace po = boost::program_options;
// 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("help") != 0u) {
std::cout << "Clio server " << util::build::getClioFullVersionString() << "\n\n" << description;
return Action{Action::Exit{EXIT_SUCCESS}};
}
if (parsed.count("version") != 0u) {
std::cout << util::build::getClioFullVersionString() << '\n';
return Action{Action::Exit{EXIT_SUCCESS}};
}
auto configPath = parsed["conf"].as<std::string>();
return Action{Action::Run{std::move(configPath)}};
}
} // namespace app

View File

@@ -1,96 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include "util/OverloadSet.hpp"
#include <string>
#include <variant>
namespace app {
/**
* @brief Parsed command line arguments representation.
*/
class CliArgs {
public:
/**
* @brief Default configuration path.
*/
static constexpr char defaultConfigPath[] = "/etc/opt/clio/config.json";
/**
* @brief An action parsed from the command line.
*/
class Action {
public:
/** @brief Run action. */
struct Run {
/** @brief Configuration file path. */
std::string configPath;
};
/** @brief Exit action. */
struct Exit {
/** @brief Exit code. */
int exitCode;
};
/**
* @brief Construct an action from a Run.
*
* @param action Run action.
*/
template <typename ActionType>
requires std::is_same_v<ActionType, Run> or std::is_same_v<ActionType, Exit>
explicit Action(ActionType&& action) : action_(std::forward<ActionType>(action))
{
}
/**
* @brief Apply a function to the action.
*
* @tparam Processors Action processors types. Must be callable with the action type and return int.
* @param processors Action processors.
* @return Exit code.
*/
template <typename... Processors>
int
apply(Processors&&... processors) const
{
return std::visit(util::OverloadSet{std::forward<Processors>(processors)...}, action_);
}
private:
std::variant<Run, Exit> action_;
};
/**
* @brief Parse command line arguments.
*
* @param argc Number of arguments.
* @param argv Array of arguments.
* @return Parsed command line arguments.
*/
static Action
parse(int argc, char const* argv[]);
};
} // namespace app

View File

@@ -1,142 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "app/ClioApplication.hpp"
#include "data/AmendmentCenter.hpp"
#include "data/BackendFactory.hpp"
#include "etl/ETLService.hpp"
#include "etl/LoadBalancer.hpp"
#include "etl/NetworkValidatedLedgers.hpp"
#include "feed/SubscriptionManager.hpp"
#include "rpc/Counters.hpp"
#include "rpc/RPCEngine.hpp"
#include "rpc/WorkQueue.hpp"
#include "rpc/common/impl/HandlerProvider.hpp"
#include "util/build/Build.hpp"
#include "util/config/Config.hpp"
#include "util/log/Logger.hpp"
#include "util/prometheus/Prometheus.hpp"
#include "web/RPCServerHandler.hpp"
#include "web/Server.hpp"
#include "web/dosguard/DOSGuard.hpp"
#include "web/dosguard/IntervalSweepHandler.hpp"
#include "web/dosguard/WhitelistHandler.hpp"
#include <boost/asio/io_context.hpp>
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <thread>
#include <vector>
namespace app {
namespace {
/**
* @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)
{
std::vector<std::thread> v;
v.reserve(numThreads - 1);
for (auto i = numThreads - 1; i > 0; --i)
v.emplace_back([&ioc] { ioc.run(); });
ioc.run();
for (auto& t : v)
t.join();
}
} // namespace
ClioApplication::ClioApplication(util::Config const& config) : config_(config), signalsHandler_{config_}
{
LOG(util::LogService::info()) << "Clio version: " << util::build::getClioFullVersionString();
PrometheusService::init(config);
}
int
ClioApplication::run()
{
auto const threads = config_.valueOr("io_threads", 2);
if (threads <= 0) {
LOG(util::LogService::fatal()) << "io_threads is less than 1";
return EXIT_FAILURE;
}
LOG(util::LogService::info()) << "Number of io threads = " << threads;
// 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
auto whitelistHandler = web::dosguard::WhitelistHandler{config_};
auto dosGuard = web::dosguard::DOSGuard{config_, whitelistHandler};
auto sweepHandler = web::dosguard::IntervalSweepHandler{config_, ioc, dosGuard};
// Interface to the database
auto backend = data::make_Backend(config_);
// Manages clients subscribed to streams
auto subscriptions = feed::SubscriptionManager::make_SubscriptionManager(config_, backend);
// Tracks which ledgers have been validated by the network
auto ledgers = etl::NetworkValidatedLedgers::make_ValidatedLedgers();
// Handles the connection to one or more rippled nodes.
// ETL uses the balancer to extract data.
// The server uses the balancer to forward RPCs to a rippled node.
// The balancer itself publishes to streams (transactions_proposed and accounts_proposed)
auto balancer = etl::LoadBalancer::make_LoadBalancer(config_, ioc, backend, subscriptions, ledgers);
// ETL is responsible for writing and publishing to streams. In read-only mode, ETL only publishes
auto etl = etl::ETLService::make_ETLService(config_, ioc, backend, subscriptions, balancer, ledgers);
auto workQueue = rpc::WorkQueue::make_WorkQueue(config_);
auto counters = rpc::Counters::make_Counters(workQueue);
auto const amendmentCenter = std::make_shared<data::AmendmentCenter const>(backend);
auto const handlerProvider = std::make_shared<rpc::impl::ProductionHandlerProvider const>(
config_, backend, subscriptions, balancer, etl, amendmentCenter, counters
);
using RPCEngineType = rpc::RPCEngine<etl::LoadBalancer, rpc::Counters>;
auto const rpcEngine =
RPCEngineType::make_RPCEngine(config_, backend, balancer, dosGuard, workQueue, counters, handlerProvider);
// Init the web server
auto handler =
std::make_shared<web::RPCServerHandler<RPCEngineType, etl::ETLService>>(config_, backend, rpcEngine, etl);
auto const httpServer = web::make_HttpServer(config_, ioc, dosGuard, handler);
// Blocks until stopped.
// When stopped, shared_ptrs fall out of scope
// Calls destructors on all resources, and destructs in order
start(ioc, threads);
return EXIT_SUCCESS;
}
} // namespace app

View File

@@ -1,51 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include "util/SignalsHandler.hpp"
#include "util/config//Config.hpp"
namespace app {
/**
* @brief The main application class
*/
class ClioApplication {
util::Config const& config_;
util::SignalsHandler signalsHandler_;
public:
/**
* @brief Construct a new ClioApplication object
*
* @param config The configuration of the application
*/
ClioApplication(util::Config const& config);
/**
* @brief Run the application
*
* @return exit code
*/
int
run();
};
} // namespace app

View File

@@ -1,203 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "data/AmendmentCenter.hpp"
#include "data/BackendInterface.hpp"
#include "data/Types.hpp"
#include "util/Assert.hpp"
#include <boost/asio/spawn.hpp>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/digest.h>
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <map>
#include <memory>
#include <optional>
#include <ranges>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
#include <vector>
namespace {
std::unordered_set<std::string>&
SUPPORTED_AMENDMENTS()
{
static std::unordered_set<std::string> amendments = {};
return amendments;
}
bool
lookupAmendment(auto const& allAmendments, std::vector<ripple::uint256> const& ledgerAmendments, std::string_view name)
{
namespace rg = std::ranges;
if (auto const am = rg::find(allAmendments, name, &data::Amendment::name); am != rg::end(allAmendments))
return rg::find(ledgerAmendments, am->feature) != rg::end(ledgerAmendments);
return false;
}
} // namespace
namespace data {
namespace impl {
WritingAmendmentKey::WritingAmendmentKey(std::string amendmentName) : AmendmentKey{std::move(amendmentName)}
{
ASSERT(not SUPPORTED_AMENDMENTS().contains(name), "Attempt to register the same amendment twice");
SUPPORTED_AMENDMENTS().insert(name);
}
} // namespace impl
AmendmentKey::operator std::string const&() const
{
return name;
}
AmendmentKey::operator std::string_view() const
{
return name;
}
AmendmentKey::operator ripple::uint256() const
{
return Amendment::GetAmendmentId(name);
}
AmendmentCenter::AmendmentCenter(std::shared_ptr<data::BackendInterface> const& backend) : backend_{backend}
{
namespace rg = std::ranges;
namespace vs = std::views;
rg::copy(
ripple::allAmendments() | vs::transform([&](auto const& p) {
auto const& [name, support] = p;
return Amendment{
.name = name,
.feature = Amendment::GetAmendmentId(name),
.isSupportedByXRPL = support != ripple::AmendmentSupport::Unsupported,
.isSupportedByClio = rg::find(SUPPORTED_AMENDMENTS(), name) != rg::end(SUPPORTED_AMENDMENTS()),
.isRetired = support == ripple::AmendmentSupport::Retired
};
}),
std::back_inserter(all_)
);
for (auto const& am : all_ | vs::filter([](auto const& am) { return am.isSupportedByClio; }))
supported_.insert_or_assign(am.name, am);
}
bool
AmendmentCenter::isSupported(AmendmentKey const& key) const
{
return supported_.contains(key);
}
std::map<std::string, Amendment> const&
AmendmentCenter::getSupported() const
{
return supported_;
}
std::vector<Amendment> const&
AmendmentCenter::getAll() const
{
return all_;
}
bool
AmendmentCenter::isEnabled(AmendmentKey const& key, uint32_t seq) const
{
return data::synchronous([this, &key, seq](auto yield) { return isEnabled(yield, key, seq); });
}
bool
AmendmentCenter::isEnabled(boost::asio::yield_context yield, AmendmentKey const& key, uint32_t seq) const
{
if (auto const listAmendments = fetchAmendmentsList(yield, seq); listAmendments)
return lookupAmendment(all_, *listAmendments, key);
return false;
}
std::vector<bool>
AmendmentCenter::isEnabled(boost::asio::yield_context yield, std::vector<AmendmentKey> const& keys, uint32_t seq) const
{
namespace rg = std::ranges;
if (auto const listAmendments = fetchAmendmentsList(yield, seq); listAmendments) {
std::vector<bool> out;
rg::transform(keys, std::back_inserter(out), [this, &listAmendments](auto const& key) {
return lookupAmendment(all_, *listAmendments, key);
});
return out;
}
return std::vector<bool>(keys.size(), false);
}
Amendment const&
AmendmentCenter::getAmendment(AmendmentKey const& key) const
{
ASSERT(supported_.contains(key), "The amendment '{}' must be present in supported amendments list", key.name);
return supported_.at(key);
}
Amendment const&
AmendmentCenter::operator[](AmendmentKey const& key) const
{
return getAmendment(key);
}
ripple::uint256
Amendment::GetAmendmentId(std::string_view name)
{
return ripple::sha512Half(ripple::Slice(name.data(), name.size()));
}
std::optional<std::vector<ripple::uint256>>
AmendmentCenter::fetchAmendmentsList(boost::asio::yield_context yield, uint32_t seq) const
{
// the amendments should always be present on the ledger
auto const amendments = backend_->fetchLedgerObject(ripple::keylet::amendments().key, seq, yield);
if (not amendments.has_value())
throw std::runtime_error("Amendments ledger object must be present in the database");
ripple::SLE const amendmentsSLE{
ripple::SerialIter{amendments->data(), amendments->size()}, ripple::keylet::amendments().key
};
return amendmentsSLE[~ripple::sfAmendments];
}
} // namespace data

View File

@@ -1,259 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include "data/AmendmentCenterInterface.hpp"
#include "data/BackendInterface.hpp"
#include "data/Types.hpp"
#include <boost/asio/spawn.hpp>
#include <boost/preprocessor.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/variadic/to_seq.hpp>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/digest.h>
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#define REGISTER(name) \
inline static impl::WritingAmendmentKey const name = \
impl::WritingAmendmentKey(std::string(BOOST_PP_STRINGIZE(name)))
namespace data {
namespace impl {
struct WritingAmendmentKey : AmendmentKey {
explicit WritingAmendmentKey(std::string amendmentName);
};
} // namespace impl
/**
* @brief List of supported amendments
*/
struct Amendments {
// NOTE: if Clio wants to report it supports an Amendment it should be listed here.
// Whether an amendment is obsolete and/or supported by libxrpl is extracted directly from libxrpl.
// If an amendment is in the list below it just means Clio did whatever changes needed to support it.
// Most of the time it's going to be no changes at all.
/** @cond */
REGISTER(OwnerPaysFee);
REGISTER(Flow);
REGISTER(FlowCross);
REGISTER(fix1513);
REGISTER(DepositAuth);
REGISTER(Checks);
REGISTER(fix1571);
REGISTER(fix1543);
REGISTER(fix1623);
REGISTER(DepositPreauth);
REGISTER(fix1515);
REGISTER(fix1578);
REGISTER(MultiSignReserve);
REGISTER(fixTakerDryOfferRemoval);
REGISTER(fixMasterKeyAsRegularKey);
REGISTER(fixCheckThreading);
REGISTER(fixPayChanRecipientOwnerDir);
REGISTER(DeletableAccounts);
REGISTER(fixQualityUpperBound);
REGISTER(RequireFullyCanonicalSig);
REGISTER(fix1781);
REGISTER(HardenedValidations);
REGISTER(fixAmendmentMajorityCalc);
REGISTER(NegativeUNL);
REGISTER(TicketBatch);
REGISTER(FlowSortStrands);
REGISTER(fixSTAmountCanonicalize);
REGISTER(fixRmSmallIncreasedQOffers);
REGISTER(CheckCashMakesTrustLine);
REGISTER(ExpandedSignerList);
REGISTER(NonFungibleTokensV1_1);
REGISTER(fixTrustLinesToSelf);
REGISTER(fixRemoveNFTokenAutoTrustLine);
REGISTER(ImmediateOfferKilled);
REGISTER(DisallowIncoming);
REGISTER(XRPFees);
REGISTER(fixUniversalNumber);
REGISTER(fixNonFungibleTokensV1_2);
REGISTER(fixNFTokenRemint);
REGISTER(fixReducedOffersV1);
REGISTER(Clawback);
REGISTER(AMM);
REGISTER(XChainBridge);
REGISTER(fixDisallowIncomingV1);
REGISTER(DID);
REGISTER(fixFillOrKill);
REGISTER(fixNFTokenReserve);
REGISTER(fixInnerObjTemplate);
REGISTER(fixAMMOverflowOffer);
REGISTER(PriceOracle);
REGISTER(fixEmptyDID);
REGISTER(fixXChainRewardRounding);
REGISTER(fixPreviousTxnID);
REGISTER(fixAMMv1_1);
REGISTER(NFTokenMintOffer);
REGISTER(fixReducedOffersV2);
REGISTER(fixEnforceNFTokenTrustline);
REGISTER(fixInnerObjTemplate2);
REGISTER(fixNFTokenPageLinks);
REGISTER(InvariantsV1_1);
REGISTER(MPTokensV1);
REGISTER(fixAMMv1_2);
REGISTER(AMMClawback);
REGISTER(Credentials);
// Obsolete but supported by libxrpl
REGISTER(CryptoConditionsSuite);
REGISTER(NonFungibleTokensV1);
REGISTER(fixNFTokenDirV1);
REGISTER(fixNFTokenNegOffer);
// Retired amendments
REGISTER(MultiSign);
REGISTER(TrustSetAuth);
REGISTER(FeeEscalation);
REGISTER(PayChan);
REGISTER(fix1368);
REGISTER(CryptoConditions);
REGISTER(Escrow);
REGISTER(TickSize);
REGISTER(fix1373);
REGISTER(EnforceInvariants);
REGISTER(SortedDirectories);
REGISTER(fix1201);
REGISTER(fix1512);
REGISTER(fix1523);
REGISTER(fix1528);
/** @endcond */
};
#undef REGISTER
/**
* @brief Knowledge center for amendments within XRPL
*/
class AmendmentCenter : public AmendmentCenterInterface {
std::shared_ptr<data::BackendInterface> backend_;
std::map<std::string, Amendment> supported_;
std::vector<Amendment> all_;
public:
/**
* @brief Construct a new AmendmentCenter instance
*
* @param backend The backend
*/
explicit AmendmentCenter(std::shared_ptr<data::BackendInterface> const& backend);
/**
* @brief Check whether an amendment is supported by Clio
*
* @param key The key of the amendment to check
* @return true if supported; false otherwise
*/
[[nodiscard]] bool
isSupported(AmendmentKey const& key) const final;
/**
* @brief Get all supported amendments as a map
*
* @return The amendments supported by Clio
*/
[[nodiscard]] std::map<std::string, Amendment> const&
getSupported() const final;
/**
* @brief Get all known amendments
*
* @return All known amendments as a vector
*/
[[nodiscard]] std::vector<Amendment> const&
getAll() const final;
/**
* @brief Check whether an amendment was/is enabled for a given sequence
*
* @param key The key of the amendment to check
* @param seq The sequence to check for
* @return true if enabled; false otherwise
*/
[[nodiscard]] bool
isEnabled(AmendmentKey const& key, uint32_t seq) const final;
/**
* @brief Check whether an amendment was/is enabled for a given sequence
*
* @param yield The coroutine context to use
* @param key The key of the amendment to check
* @param seq The sequence to check for
* @return true if enabled; false otherwise
*/
[[nodiscard]] bool
isEnabled(boost::asio::yield_context yield, AmendmentKey const& key, uint32_t seq) const final;
/**
* @brief Check whether an amendment was/is enabled for a given sequence
*
* @param yield The coroutine context to use
* @param keys The keys of the amendments to check
* @param seq The sequence to check for
* @return A vector of bools representing enabled state for each of the given keys
*/
[[nodiscard]] std::vector<bool>
isEnabled(boost::asio::yield_context yield, std::vector<AmendmentKey> const& keys, uint32_t seq) const final;
/**
* @brief Get an amendment
*
* @param key The key of the amendment to get
* @return The amendment as a const ref; asserts if the amendment is unknown
*/
[[nodiscard]] Amendment const&
getAmendment(AmendmentKey const& key) const final;
/**
* @brief Get an amendment by its key
* @param key The amendment key from @see Amendments
* @return The amendment as a const ref; asserts if the amendment is unknown
*/
[[nodiscard]] Amendment const&
operator[](AmendmentKey const& key) const final;
private:
[[nodiscard]] std::optional<std::vector<ripple::uint256>>
fetchAmendmentsList(boost::asio::yield_context yield, uint32_t seq) const;
};
} // namespace data

View File

@@ -1,116 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include "data/Types.hpp"
#include <boost/asio/spawn.hpp>
#include <cstdint>
#include <map>
#include <string>
#include <vector>
namespace data {
/**
* @brief The interface of an amendment center
*/
class AmendmentCenterInterface {
public:
virtual ~AmendmentCenterInterface() = default;
/**
* @brief Check whether an amendment is supported by Clio
*
* @param key The key of the amendment to check
* @return true if supported; false otherwise
*/
[[nodiscard]] virtual bool
isSupported(AmendmentKey const& key) const = 0;
/**
* @brief Get all supported amendments as a map
*
* @return The amendments supported by Clio
*/
[[nodiscard]] virtual std::map<std::string, Amendment> const&
getSupported() const = 0;
/**
* @brief Get all known amendments
*
* @return All known amendments as a vector
*/
[[nodiscard]] virtual std::vector<Amendment> const&
getAll() const = 0;
/**
* @brief Check whether an amendment was/is enabled for a given sequence
*
* @param key The key of the amendment to check
* @param seq The sequence to check for
* @return true if enabled; false otherwise
*/
[[nodiscard]] virtual bool
isEnabled(AmendmentKey const& key, uint32_t seq) const = 0;
/**
* @brief Check whether an amendment was/is enabled for a given sequence
*
* @param yield The coroutine context to use
* @param key The key of the amendment to check
* @param seq The sequence to check for
* @return true if enabled; false otherwise
*/
[[nodiscard]] virtual bool
isEnabled(boost::asio::yield_context yield, AmendmentKey const& key, uint32_t seq) const = 0;
/**
* @brief Check whether an amendment was/is enabled for a given sequence
*
* @param yield The coroutine context to use
* @param keys The keys of the amendments to check
* @param seq The sequence to check for
* @return A vector of bools representing enabled state for each of the given keys
*/
[[nodiscard]] virtual std::vector<bool>
isEnabled(boost::asio::yield_context yield, std::vector<AmendmentKey> const& keys, uint32_t seq) const = 0;
/**
* @brief Get an amendment
*
* @param key The key of the amendment to get
* @return The amendment as a const ref; asserts if the amendment is unknown
*/
[[nodiscard]] virtual Amendment const&
getAmendment(AmendmentKey const& key) const = 0;
/**
* @brief Get an amendment by its key
*
* @param key The amendment key from @see Amendments
* @return The amendment as a const ref; asserts if the amendment is unknown
*/
[[nodiscard]] virtual Amendment const&
operator[](AmendmentKey const& key) const = 0;
};
} // namespace data

View File

@@ -51,7 +51,8 @@ make_Backend(util::Config const& config)
auto const type = config.value<std::string>("database.type");
std::shared_ptr<BackendInterface> backend = nullptr;
if (boost::iequals(type, "cassandra")) {
// TODO: retire `cassandra-new` by next release after 2.0
if (boost::iequals(type, "cassandra") or boost::iequals(type, "cassandra-new")) {
auto cfg = config.section("database." + type);
backend = std::make_shared<data::cassandra::CassandraBackend>(data::cassandra::SettingsProvider{cfg}, readOnly);
}

View File

@@ -24,13 +24,13 @@
#include "util/log/Logger.hpp"
#include <boost/asio/spawn.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/Serializer.h>
#include <ripple/basics/base_uint.h>
#include <ripple/basics/strHex.h>
#include <ripple/protocol/Fees.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/Serializer.h>
#include <chrono>
#include <cstddef>
@@ -93,6 +93,7 @@ BackendInterface::fetchLedgerObject(
return obj;
}
LOG(gLog.trace()) << "Cache miss - " << ripple::strHex(key);
auto dbObj = doFetchLedgerObject(key, sequence, yield);
if (!dbObj) {
LOG(gLog.trace()) << "Missed cache and missed in db";
@@ -102,19 +103,6 @@ BackendInterface::fetchLedgerObject(
return dbObj;
}
std::optional<std::uint32_t>
BackendInterface::fetchLedgerObjectSeq(
ripple::uint256 const& key,
std::uint32_t const sequence,
boost::asio::yield_context yield
) const
{
auto seq = doFetchLedgerObjectSeq(key, sequence, yield);
if (!seq)
LOG(gLog.trace()) << "Missed in db";
return seq;
}
std::vector<Blob>
BackendInterface::fetchLedgerObjects(
std::vector<ripple::uint256> const& keys,

View File

@@ -31,10 +31,10 @@
#include <boost/json.hpp>
#include <boost/json/object.hpp>
#include <boost/utility/result_of.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <ripple/basics/base_uint.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/Fees.h>
#include <ripple/protocol/LedgerHeader.h>
#include <chrono>
#include <cstddef>
@@ -364,25 +364,6 @@ public:
boost::asio::yield_context yield
) const = 0;
/**
* @brief Fetches all holders' balances for a MPTIssuanceID
*
* @param mptID MPTIssuanceID you wish you query.
* @param limit Paging limit.
* @param cursorIn Optional cursor to allow us to pick up from where we last left off.
* @param ledgerSequence The ledger sequence to fetch for
* @param yield Currently executing coroutine.
* @return std::vector<Blob> of MPToken balances and an optional marker
*/
virtual MPTHoldersAndCursor
fetchMPTHolders(
ripple::uint192 const& mptID,
std::uint32_t const limit,
std::optional<ripple::AccountID> const& cursorIn,
std::uint32_t const ledgerSequence,
boost::asio::yield_context yield
) const = 0;
/**
* @brief Fetches a specific ledger object.
*
@@ -397,19 +378,6 @@ public:
std::optional<Blob>
fetchLedgerObject(ripple::uint256 const& key, std::uint32_t sequence, boost::asio::yield_context yield) const;
/**
* @brief Fetches a specific ledger object sequence.
*
* Currently the real fetch happens in doFetchLedgerObjectSeq
*
* @param key The key of the object
* @param sequence The ledger sequence to fetch for
* @param yield The coroutine context
* @return The sequence in unit32_t on success; nullopt otherwise
*/
std::optional<std::uint32_t>
fetchLedgerObjectSeq(ripple::uint256 const& key, std::uint32_t sequence, boost::asio::yield_context yield) const;
/**
* @brief Fetches all ledger objects by their keys.
*
@@ -439,18 +407,6 @@ public:
virtual std::optional<Blob>
doFetchLedgerObject(ripple::uint256 const& key, std::uint32_t sequence, boost::asio::yield_context yield) const = 0;
/**
* @brief The database-specific implementation for fetching a ledger object sequence.
*
* @param key The key to fetch for
* @param sequence The ledger sequence to fetch for
* @param yield The coroutine context
* @return The sequence in unit32_t on success; nullopt otherwise
*/
virtual std::optional<std::uint32_t>
doFetchLedgerObjectSeq(ripple::uint256 const& key, std::uint32_t sequence, boost::asio::yield_context yield)
const = 0;
/**
* @brief The database-specific implementation for fetching ledger objects.
*
@@ -636,14 +592,6 @@ public:
virtual void
writeNFTTransactions(std::vector<NFTTransactionsData> const& data) = 0;
/**
* @brief Write accounts that started holding onto a MPT.
*
* @param data A vector of MPT ID and account pairs
*/
virtual void
writeMPTHolders(std::vector<MPTHolderData> const& data) = 0;
/**
* @brief Write a new successor.
*

View File

@@ -1,8 +1,7 @@
add_library(clio_data)
target_sources(
clio_data
PRIVATE AmendmentCenter.cpp
BackendCounters.cpp
PRIVATE BackendCounters.cpp
BackendInterface.cpp
LedgerCache.cpp
cassandra/impl/Future.cpp

View File

@@ -22,6 +22,7 @@
#include "data/BackendInterface.hpp"
#include "data/DBHelpers.hpp"
#include "data/Types.hpp"
#include "data/cassandra/Concepts.hpp"
#include "data/cassandra/Handle.hpp"
#include "data/cassandra/Schema.hpp"
#include "data/cassandra/SettingsProvider.hpp"
@@ -35,13 +36,13 @@
#include <boost/asio/spawn.hpp>
#include <boost/json/object.hpp>
#include <cassandra.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/nft.h>
#include <ripple/basics/Blob.h>
#include <ripple/basics/base_uint.h>
#include <ripple/basics/strHex.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/LedgerHeader.h>
#include <ripple/protocol/nft.h>
#include <atomic>
#include <chrono>
@@ -93,7 +94,7 @@ public:
, executor_{settingsProvider_.getSettings(), handle_}
{
if (auto const res = handle_.connect(); not res)
throw std::runtime_error("Could not connect to databse: " + res.error());
throw std::runtime_error("Could not connect to Cassandra: " + res.error());
if (not readOnly) {
if (auto const res = handle_.execute(schema_.createKeyspace); not res) {
@@ -335,8 +336,8 @@ public:
auto const& result = res.value();
if (not result.hasRows()) {
LOG(log_.warn()) << "Could not fetch all transaction hashes - no rows; ledger = "
<< std::to_string(ledgerSequence);
LOG(log_.error()) << "Could not fetch all transaction hashes - no rows; ledger = "
<< std::to_string(ledgerSequence);
return {};
}
@@ -345,7 +346,7 @@ public:
hashes.push_back(std::move(hash));
auto end = std::chrono::system_clock::now();
LOG(log_.debug()) << "Fetched " << hashes.size() << " transaction hashes from database in "
LOG(log_.debug()) << "Fetched " << hashes.size() << " transaction hashes from Cassandra in "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " milliseconds";
@@ -547,45 +548,6 @@ public:
return ret;
}
MPTHoldersAndCursor
fetchMPTHolders(
ripple::uint192 const& mptID,
std::uint32_t const limit,
std::optional<ripple::AccountID> const& cursorIn,
std::uint32_t const ledgerSequence,
boost::asio::yield_context yield
) const override
{
auto const holderEntries = executor_.read(
yield, schema_->selectMPTHolders, mptID, cursorIn.value_or(ripple::AccountID(0)), Limit{limit}
);
auto const& holderResults = holderEntries.value();
if (not holderResults.hasRows()) {
LOG(log_.debug()) << "No rows returned";
return {};
}
std::vector<ripple::uint256> mptKeys;
std::optional<ripple::AccountID> cursor;
for (auto const [holder] : extract<ripple::AccountID>(holderResults)) {
mptKeys.push_back(ripple::keylet::mptoken(mptID, holder).key);
cursor = holder;
}
auto mptObjects = doFetchLedgerObjects(mptKeys, ledgerSequence, yield);
auto it = std::remove_if(mptObjects.begin(), mptObjects.end(), [](Blob const& mpt) { return mpt.empty(); });
mptObjects.erase(it, mptObjects.end());
ASSERT(mptKeys.size() <= limit, "Number of keys can't exceed the limit");
if (mptKeys.size() == limit)
return {mptObjects, cursor};
return {mptObjects, {}};
}
std::optional<Blob>
doFetchLedgerObject(ripple::uint256 const& key, std::uint32_t const sequence, boost::asio::yield_context yield)
const override
@@ -605,25 +567,6 @@ public:
return std::nullopt;
}
std::optional<std::uint32_t>
doFetchLedgerObjectSeq(ripple::uint256 const& key, std::uint32_t const sequence, boost::asio::yield_context yield)
const override
{
LOG(log_.debug()) << "Fetching ledger object for seq " << sequence << ", key = " << ripple::to_string(key);
if (auto const res = executor_.read(yield, schema_->selectObject, key, sequence); res) {
if (auto const result = res->template get<Blob, std::uint32_t>(); result) {
auto [_, seq] = result.value();
return seq;
}
LOG(log_.debug()) << "Could not fetch ledger object sequence - no rows";
} else {
LOG(log_.error()) << "Could not fetch ledger object sequence: " << res.error();
}
return std::nullopt;
}
std::optional<TransactionAndMetadata>
fetchTransaction(ripple::uint256 const& hash, boost::asio::yield_context yield) const override
{
@@ -697,7 +640,7 @@ public:
});
ASSERT(numHashes == results.size(), "Number of hashes and results must match");
LOG(log_.debug()) << "Fetched " << numHashes << " transactions from database in " << timeDiff
LOG(log_.debug()) << "Fetched " << numHashes << " transactions from Cassandra in " << timeDiff
<< " milliseconds";
return results;
}
@@ -817,7 +760,7 @@ public:
if (keys.empty())
return {};
LOG(log_.debug()) << "Fetched " << keys.size() << " diff hashes from database in " << timeDiff
LOG(log_.debug()) << "Fetched " << keys.size() << " diff hashes from Cassandra in " << timeDiff
<< " milliseconds";
auto const objs = fetchLedgerObjects(keys, ledgerSequence, yield);
@@ -905,7 +848,7 @@ public:
std::string&& metadata
) override
{
LOG(log_.trace()) << "Writing txn to database";
LOG(log_.trace()) << "Writing txn to cassandra";
executor_.write(schema_->insertLedgerTransaction, seq, hash);
executor_.write(
@@ -944,17 +887,6 @@ public:
executor_.write(std::move(statements));
}
void
writeMPTHolders(std::vector<MPTHolderData> const& data) override
{
std::vector<Statement> statements;
statements.reserve(data.size());
for (auto [mptId, holder] : data)
statements.push_back(schema_->insertMPTHolder.bind(std::move(mptId), std::move(holder)));
executor_.write(std::move(statements));
}
void
startWrites() const override
{

View File

@@ -23,16 +23,16 @@
#include "util/Assert.hpp"
#include <boost/container/flat_set.hpp>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAccount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/TxMeta.h>
#include <ripple/basics/Blob.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/basics/base_uint.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/Serializer.h>
#include <ripple/protocol/TxMeta.h>
#include <cstddef>
#include <cstdint>
@@ -172,14 +172,6 @@ struct NFTsData {
}
};
/**
* @brief Represents an MPT and holder pair
*/
struct MPTHolderData {
ripple::uint192 mptID;
ripple::AccountID holder;
};
/**
* @brief Check whether the supplied object is an offer.
*

View File

@@ -22,7 +22,7 @@
#include "data/Types.hpp"
#include "util/Assert.hpp"
#include <xrpl/basics/base_uint.h>
#include <ripple/basics/base_uint.h>
#include <cstddef>
#include <cstdint>

View File

@@ -24,8 +24,8 @@
#include "util/prometheus/Label.hpp"
#include "util/prometheus/Prometheus.hpp"
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/hardened_hash.h>
#include <ripple/basics/base_uint.h>
#include <ripple/basics/hardened_hash.h>
#include <atomic>
#include <condition_variable>

View File

@@ -19,13 +19,11 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/AccountID.h>
#include <ripple/basics/base_uint.h>
#include <ripple/protocol/AccountID.h>
#include <concepts>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
@@ -233,14 +231,6 @@ struct NFTsAndCursor {
std::optional<ripple::uint256> cursor;
};
/**
* @brief Represents an array of MPTokens
*/
struct MPTHoldersAndCursor {
std::vector<Blob> mptokens;
std::optional<ripple::AccountID> cursor;
};
/**
* @brief Stores a range of sequences as a min and max pair.
*/
@@ -249,69 +239,6 @@ struct LedgerRange {
std::uint32_t maxSequence = 0;
};
/**
* @brief Represents an amendment in the XRPL
*/
struct Amendment {
std::string name;
ripple::uint256 feature;
bool isSupportedByXRPL = false;
bool isSupportedByClio = false;
bool isRetired = false;
/**
* @brief Get the amendment Id from its name
*
* @param name The name of the amendment
* @return The amendment Id as uint256
*/
static ripple::uint256
GetAmendmentId(std::string_view const name);
/**
* @brief Equality comparison operator
* @param other The object to compare to
* @return Whether the objects are equal
*/
bool
operator==(Amendment const& other) const
{
return name == other.name;
}
};
/**
* @brief A helper for amendment name to feature conversions
*/
struct AmendmentKey {
std::string name;
/**
* @brief Construct a new AmendmentKey
* @param val Anything convertible to a string
*/
AmendmentKey(std::convertible_to<std::string> auto&& val) : name{std::forward<decltype(val)>(val)}
{
}
/** @brief Conversion to string */
operator std::string const&() const;
/** @brief Conversion to string_view */
operator std::string_view() const;
/** @brief Conversion to uint256 */
operator ripple::uint256() const;
/**
* @brief Comparison operators
* @param other The object to compare to
* @return Whether the objects are equal, greater or less
*/
auto
operator<=>(AmendmentKey const& other) const = default;
};
constexpr ripple::uint256 firstKey{"0000000000000000000000000000000000000000000000000000000000000000"};
constexpr ripple::uint256 lastKey{"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"};
constexpr ripple::uint256 hi192{"0000000000000000000000000000000000000000000000001111111111111111"};

View File

@@ -257,19 +257,6 @@ public:
qualifiedTableName(settingsProvider_.get(), "nf_token_transactions")
));
statements.emplace_back(fmt::format(
R"(
CREATE TABLE IF NOT EXISTS {}
(
mpt_id blob,
holder blob,
PRIMARY KEY (mpt_id, holder)
)
WITH CLUSTERING ORDER BY (holder ASC)
)",
qualifiedTableName(settingsProvider_.get(), "mp_token_holders")
));
return statements;
}();
@@ -406,17 +393,6 @@ public:
));
}();
PreparedStatement insertMPTHolder = [this]() {
return handle_.get().prepare(fmt::format(
R"(
INSERT INTO {}
(mpt_id, holder)
VALUES (?, ?)
)",
qualifiedTableName(settingsProvider_.get(), "mp_token_holders")
));
}();
PreparedStatement insertLedgerHeader = [this]() {
return handle_.get().prepare(fmt::format(
R"(
@@ -711,20 +687,6 @@ public:
));
}();
PreparedStatement selectMPTHolders = [this]() {
return handle_.get().prepare(fmt::format(
R"(
SELECT holder
FROM {}
WHERE mpt_id = ?
AND holder > ?
ORDER BY holder ASC
LIMIT ?
)",
qualifiedTableName(settingsProvider_.get(), "mp_token_holders")
));
}();
PreparedStatement selectLedgerByHash = [this]() {
return handle_.get().prepare(fmt::format(
R"(

View File

@@ -21,7 +21,6 @@
#include "data/cassandra/impl/ManagedObject.hpp"
#include "data/cassandra/impl/SslContext.hpp"
#include "util/OverloadSet.hpp"
#include "util/log/Logger.hpp"
#include <cassandra.h>
@@ -32,9 +31,16 @@
#include <variant>
namespace {
constexpr auto clusterDeleter = [](CassCluster* ptr) { cass_cluster_free(ptr); };
template <typename... Ts>
struct overloadSet : Ts... {
using Ts::operator()...;
};
// explicit deduction guide (not needed as of C++20, but clang be clang)
template <typename... Ts>
overloadSet(Ts...) -> overloadSet<Ts...>;
}; // namespace
namespace data::cassandra::impl {
@@ -84,7 +90,7 @@ void
Cluster::setupConnection(Settings const& settings)
{
std::visit(
util::OverloadSet{
overloadSet{
[this](Settings::ContactPoints const& points) { setupContactPoints(points); },
[this](Settings::SecureConnectionBundle const& bundle) { setupSecureBundle(bundle); }
},

View File

@@ -22,7 +22,7 @@
#include "data/cassandra/impl/ManagedObject.hpp"
#include <cassandra.h>
#include <xrpl/basics/base_uint.h>
#include <ripple/basics/base_uint.h>
#include <cstdint>
#include <stdexcept>

View File

@@ -21,11 +21,10 @@
#include "data/cassandra/impl/ManagedObject.hpp"
#include "data/cassandra/impl/Tuple.hpp"
#include "util/UnsupportedType.hpp"
#include <cassandra.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/AccountID.h>
#include <ripple/basics/base_uint.h>
#include <ripple/protocol/AccountID.h>
#include <cstddef>
#include <cstdint>
@@ -42,6 +41,9 @@
namespace data::cassandra::impl {
template <typename>
static constexpr bool unsupported_v = false;
template <typename Type>
inline Type
extractColumn(CassRow const* row, std::size_t idx)
@@ -101,7 +103,7 @@ extractColumn(CassRow const* row, std::size_t idx)
output = static_cast<DecayedType>(out);
} else {
// type not supported for extraction
static_assert(util::Unsupported<DecayedType>);
static_assert(unsupported_v<DecayedType>);
}
return output;

View File

@@ -23,13 +23,12 @@
#include "data/cassandra/impl/Collection.hpp"
#include "data/cassandra/impl/ManagedObject.hpp"
#include "data/cassandra/impl/Tuple.hpp"
#include "util/UnsupportedType.hpp"
#include <cassandra.h>
#include <fmt/core.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STAccount.h>
#include <ripple/basics/base_uint.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/STAccount.h>
#include <cstddef>
#include <cstdint>
@@ -45,6 +44,9 @@ namespace data::cassandra::impl {
class Statement : public ManagedObject<CassStatement> {
static constexpr auto deleter = [](CassStatement* ptr) { cass_statement_free(ptr); };
template <typename>
static constexpr bool unsupported_v = false;
public:
/**
* @brief Construct a new statement with optionally provided arguments.
@@ -106,9 +108,9 @@ public:
using UintByteTupleType = std::tuple<uint32_t, ripple::uint256>;
using ByteVectorType = std::vector<ripple::uint256>;
if constexpr (std::is_same_v<DecayedType, ripple::uint256> || std::is_same_v<DecayedType, ripple::uint192>) {
if constexpr (std::is_same_v<DecayedType, ripple::uint256>) {
auto const rc = bindBytes(value.data(), value.size());
throwErrorIfNeeded(rc, "Bind ripple::base_uint");
throwErrorIfNeeded(rc, "Bind ripple::uint256");
} else if constexpr (std::is_same_v<DecayedType, ripple::AccountID>) {
auto const rc = bindBytes(value.data(), value.size());
throwErrorIfNeeded(rc, "Bind ripple::AccountID");
@@ -139,7 +141,7 @@ public:
throwErrorIfNeeded(rc, "Bind int64");
} else {
// type not supported for binding
static_assert(util::Unsupported<DecayedType>);
static_assert(unsupported_v<DecayedType>);
}
}
};

View File

@@ -20,10 +20,9 @@
#pragma once
#include "data/cassandra/impl/ManagedObject.hpp"
#include "util/UnsupportedType.hpp"
#include <cassandra.h>
#include <xrpl/basics/base_uint.h>
#include <ripple/basics/base_uint.h>
#include <cstddef>
#include <cstdint>
@@ -39,6 +38,9 @@ namespace data::cassandra::impl {
class Tuple : public ManagedObject<CassTuple> {
static constexpr auto deleter = [](CassTuple* ptr) { cass_tuple_free(ptr); };
template <typename>
static constexpr bool unsupported_v = false;
public:
/* implicit */ Tuple(CassTuple* ptr);
@@ -89,12 +91,15 @@ public:
throwErrorIfNeeded(rc, "Bind ripple::uint256");
} else {
// type not supported for binding
static_assert(util::Unsupported<DecayedType>);
static_assert(unsupported_v<DecayedType>);
}
}
};
class TupleIterator : public ManagedObject<CassIterator> {
template <typename>
static constexpr bool unsupported_v = false;
public:
/* implicit */ TupleIterator(CassIterator* ptr);
@@ -136,7 +141,7 @@ private:
output = static_cast<DecayedType>(out);
} else {
// type not supported for extraction
static_assert(util::Unsupported<DecayedType>);
static_assert(unsupported_v<DecayedType>);
}
return output;

View File

@@ -10,8 +10,7 @@ target_sources(
NetworkValidatedLedgers.cpp
NFTHelpers.cpp
Source.cpp
MPTHelpers.cpp
impl/AmendmentBlockHandler.cpp
impl/ForwardingCache.cpp
impl/ForwardingSource.cpp
impl/GrpcSource.cpp
impl/SubscriptionSource.cpp

View File

@@ -21,7 +21,7 @@
#include "util/Assert.hpp"
#include <xrpl/basics/base_uint.h>
#include <ripple/basics/base_uint.h>
#include <cstddef>
#include <vector>

View File

@@ -20,7 +20,7 @@
/** @file */
#pragma once
#include <xrpl/basics/base_uint.h>
#include <ripple/basics/base_uint.h>
#include <condition_variable>
#include <cstddef>

View File

@@ -30,8 +30,8 @@
#include "util/log/Logger.hpp"
#include <boost/asio/io_context.hpp>
#include <xrpl/beast/core/CurrentThreadName.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <ripple/beast/core/CurrentThreadName.h>
#include <ripple/protocol/LedgerHeader.h>
#include <chrono>
#include <cstddef>

View File

@@ -22,11 +22,11 @@
#include "data/BackendInterface.hpp"
#include "data/LedgerCache.hpp"
#include "etl/CacheLoader.hpp"
#include "etl/ETLHelpers.hpp"
#include "etl/ETLState.hpp"
#include "etl/LoadBalancer.hpp"
#include "etl/NetworkValidatedLedgersInterface.hpp"
#include "etl/SystemState.hpp"
#include "etl/impl/AmendmentBlockHandler.hpp"
#include "etl/impl/AmendmentBlock.hpp"
#include "etl/impl/ExtractionDataPipe.hpp"
#include "etl/impl/Extractor.hpp"
#include "etl/impl/LedgerFetcher.hpp"
@@ -37,10 +37,11 @@
#include "util/log/Logger.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/json/object.hpp>
#include <grpcpp/grpcpp.h>
#include <org/xrpl/rpc/v1/get_ledger.pb.h>
#include <xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
#include <ripple/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
#include <cstddef>
#include <cstdint>
@@ -52,6 +53,9 @@
struct AccountTransactionsData;
struct NFTTransactionsData;
struct NFTsData;
namespace feed {
class SubscriptionManager;
} // namespace feed
/**
* @brief This namespace contains everything to do with the ETL and ETL sources.
@@ -81,7 +85,7 @@ class ETLService {
using ExtractorType = etl::impl::Extractor<DataPipeType, LedgerFetcherType>;
using LedgerLoaderType = etl::impl::LedgerLoader<LoadBalancerType, LedgerFetcherType>;
using LedgerPublisherType = etl::impl::LedgerPublisher<CacheType>;
using AmendmentBlockHandlerType = etl::impl::AmendmentBlockHandler;
using AmendmentBlockHandlerType = etl::impl::AmendmentBlockHandler<>;
using TransformerType =
etl::impl::Transformer<DataPipeType, LedgerLoaderType, LedgerPublisherType, AmendmentBlockHandlerType>;

View File

@@ -24,7 +24,7 @@
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <xrpl/protocol/jss.h>
#include <ripple/protocol/jss.h>
#include <cstdint>
#include <optional>

View File

@@ -23,7 +23,6 @@
#include <boost/json.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
@@ -47,11 +46,8 @@ struct ETLState {
static std::optional<ETLState>
fetchETLStateFromSource(Forward& source) noexcept
{
auto const serverInfoRippled = data::synchronous([&source](auto yield) -> std::optional<boost::json::object> {
if (auto result = source.forwardToRippled({{"command", "server_info"}}, std::nullopt, {}, yield)) {
return std::move(result).value();
}
return std::nullopt;
auto const serverInfoRippled = data::synchronous([&source](auto yield) {
return source.forwardToRippled({{"command", "server_info"}}, std::nullopt, {}, yield);
});
if (serverInfoRippled)

View File

@@ -24,10 +24,8 @@
#include "etl/NetworkValidatedLedgersInterface.hpp"
#include "etl/Source.hpp"
#include "feed/SubscriptionManagerInterface.hpp"
#include "rpc/Errors.hpp"
#include "util/Assert.hpp"
#include "util/Random.hpp"
#include "util/ResponseExpirationCache.hpp"
#include "util/log/Logger.hpp"
#include <boost/asio/io_context.hpp>
@@ -35,14 +33,12 @@
#include <boost/json/array.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <fmt/core.h>
#include <algorithm>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <stdexcept>
@@ -81,10 +77,7 @@ LoadBalancer::LoadBalancer(
{
auto const forwardingCacheTimeout = config.valueOr<float>("forwarding.cache_timeout", 0.f);
if (forwardingCacheTimeout > 0.f) {
forwardingCache_ = util::ResponseExpirationCache{
Config::toMilliseconds(forwardingCacheTimeout),
{"server_info", "server_state", "server_definitions", "fee", "ledger_closed"}
};
forwardingCache_ = impl::ForwardingCache{Config::toMilliseconds(forwardingCacheTimeout)};
}
static constexpr std::uint32_t MAX_DOWNLOAD = 256;
@@ -116,13 +109,10 @@ LoadBalancer::LoadBalancer(
validatedLedgers,
forwardingTimeout,
[this]() {
if (not hasForwardingSource_.lock().get())
chooseForwardingSource();
},
[this](bool wasForwarding) {
if (wasForwarding)
if (not hasForwardingSource_)
chooseForwardingSource();
},
[this]() { chooseForwardingSource(); },
[this]() {
if (forwardingCache_.has_value())
forwardingCache_->invalidate();
@@ -221,7 +211,7 @@ LoadBalancer::fetchLedger(
return response;
}
std::expected<boost::json::object, rpc::ClioError>
std::optional<boost::json::object>
LoadBalancer::forwardToRippled(
boost::json::object const& request,
std::optional<std::string> const& clientIp,
@@ -229,13 +219,9 @@ LoadBalancer::forwardToRippled(
boost::asio::yield_context yield
)
{
if (not request.contains("command"))
return std::unexpected{rpc::ClioError::rpcCOMMAND_IS_MISSING};
auto const cmd = boost::json::value_to<std::string>(request.at("command"));
if (forwardingCache_) {
if (auto cachedResponse = forwardingCache_->get(cmd); cachedResponse) {
return std::move(cachedResponse).value();
if (auto cachedResponse = forwardingCache_->get(request); cachedResponse) {
return cachedResponse;
}
}
@@ -247,26 +233,20 @@ LoadBalancer::forwardToRippled(
auto xUserValue = isAdmin ? ADMIN_FORWARDING_X_USER_VALUE : USER_FORWARDING_X_USER_VALUE;
std::optional<boost::json::object> response;
rpc::ClioError error = rpc::ClioError::etlCONNECTION_ERROR;
while (numAttempts < sources_.size()) {
auto res = sources_[sourceIdx]->forwardToRippled(request, clientIp, xUserValue, yield);
if (res) {
response = std::move(res).value();
if (auto res = sources_[sourceIdx]->forwardToRippled(request, clientIp, xUserValue, yield)) {
response = std::move(res);
break;
}
error = std::max(error, res.error()); // Choose the best result between all sources
sourceIdx = (sourceIdx + 1) % sources_.size();
++numAttempts;
}
if (response) {
if (forwardingCache_ and not response->contains("error"))
forwardingCache_->put(cmd, *response);
return std::move(response).value();
}
if (response and forwardingCache_ and not response->contains("error"))
forwardingCache_->put(request, *response);
return std::unexpected{error};
return response;
}
boost::json::value
@@ -334,13 +314,11 @@ LoadBalancer::getETLState() noexcept
void
LoadBalancer::chooseForwardingSource()
{
LOG(log_.info()) << "Choosing a new source to forward subscriptions";
auto hasForwardingSourceLock = hasForwardingSource_.lock();
hasForwardingSourceLock.get() = false;
hasForwardingSource_ = false;
for (auto& source : sources_) {
if (not hasForwardingSourceLock.get() and source->isConnected()) {
if (not hasForwardingSource_ and source->isConnected()) {
source->setForwarding(true);
hasForwardingSourceLock.get() = true;
hasForwardingSource_ = true;
} else {
source->setForwarding(false);
}

View File

@@ -23,10 +23,8 @@
#include "etl/ETLState.hpp"
#include "etl/NetworkValidatedLedgersInterface.hpp"
#include "etl/Source.hpp"
#include "etl/impl/ForwardingCache.hpp"
#include "feed/SubscriptionManagerInterface.hpp"
#include "rpc/Errors.hpp"
#include "util/Mutex.hpp"
#include "util/ResponseExpirationCache.hpp"
#include "util/config/Config.hpp"
#include "util/log/Logger.hpp"
@@ -38,11 +36,11 @@
#include <grpcpp/grpcpp.h>
#include <org/xrpl/rpc/v1/get_ledger.pb.h>
#include <org/xrpl/rpc/v1/ledger.pb.h>
#include <xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
#include <ripple/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <string>
@@ -69,17 +67,14 @@ private:
util::Logger log_{"ETL"};
// Forwarding cache must be destroyed after sources because sources have a callback to invalidate cache
std::optional<util::ResponseExpirationCache> forwardingCache_;
std::optional<impl::ForwardingCache> forwardingCache_;
std::optional<std::string> forwardingXUserValue_;
std::vector<SourcePtr> sources_;
std::optional<ETLState> etlState_;
std::uint32_t downloadRanges_ =
DEFAULT_DOWNLOAD_RANGES; /*< The number of markers to use when downloading initial ledger */
// Using mutext instead of atomic_bool because choosing a new source to
// forward messages should be done with a mutual exclusion otherwise there will be a race condition
util::Mutex<bool> hasForwardingSource_{false};
std::atomic_bool hasForwardingSource_{false};
public:
/**
@@ -186,9 +181,9 @@ public:
* @param clientIp The IP address of the peer, if known
* @param isAdmin Whether the request is from an admin
* @param yield The coroutine context
* @return Response received from rippled node as JSON object on success or error on failure
* @return Response received from rippled node as JSON object on success; nullopt on failure
*/
std::expected<boost::json::object, rpc::ClioError>
std::optional<boost::json::object>
forwardToRippled(
boost::json::object const& request,
std::optional<std::string> const& clientIp,

View File

@@ -1,83 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "data/DBHelpers.hpp"
#include <ripple/protocol/STBase.h>
#include <ripple/protocol/STTx.h>
#include <ripple/protocol/TxMeta.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFormats.h>
#include <optional>
#include <string>
namespace etl {
/**
* @brief Get the MPToken created from a transaction
*
* @param txMeta Transaction metadata
* @return MPT and holder account pair
*/
static std::optional<MPTHolderData>
getMPTokenAuthorize(ripple::TxMeta const& txMeta)
{
for (ripple::STObject const& node : txMeta.getNodes()) {
if (node.getFieldU16(ripple::sfLedgerEntryType) != ripple::ltMPTOKEN)
continue;
if (node.getFName() == ripple::sfCreatedNode) {
auto const& newMPT = node.peekAtField(ripple::sfNewFields).downcast<ripple::STObject>();
return MPTHolderData{newMPT[ripple::sfMPTokenIssuanceID], newMPT.getAccountID(ripple::sfAccount)};
}
}
return {};
}
std::optional<MPTHolderData>
getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx)
{
if (txMeta.getResultTER() != ripple::tesSUCCESS || sttx.getTxnType() != ripple::TxType::ttMPTOKEN_AUTHORIZE)
return {};
return getMPTokenAuthorize(txMeta);
}
std::optional<MPTHolderData>
getMPTHolderFromObj(std::string const& key, std::string const& blob)
{
ripple::STLedgerEntry const sle =
ripple::STLedgerEntry(ripple::SerialIter{blob.data(), blob.size()}, ripple::uint256::fromVoid(key.data()));
if (sle.getFieldU16(ripple::sfLedgerEntryType) != ripple::ltMPTOKEN)
return {};
auto const mptIssuanceID = sle[ripple::sfMPTokenIssuanceID];
auto const holder = sle.getAccountID(ripple::sfAccount);
return MPTHolderData{mptIssuanceID, holder};
}
} // namespace etl

View File

@@ -1,50 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
/** @file */
#pragma once
#include "data/DBHelpers.hpp"
#include <ripple/protocol/STTx.h>
#include <ripple/protocol/TxMeta.h>
namespace etl {
/**
* @brief Pull MPT data from TX via ETLService.
*
* @param txMeta Transaction metadata
* @param sttx The transaction
* @return The MPTIssuanceID and holder pair as a optional
*/
std::optional<MPTHolderData>
getMPTHolderFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx);
/**
* @brief Pull MPT data from ledger object via loadInitialLedger.
*
* @param key The owner key
* @param blob Object data as blob
* @return The MPTIssuanceID and holder pair as a optional
*/
std::optional<MPTHolderData>
getMPTHolderFromObj(std::string const& key, std::string const& blob);
} // namespace etl

View File

@@ -20,20 +20,20 @@
#include "data/DBHelpers.hpp"
#include <fmt/core.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STBase.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/TxMeta.h>
#include <ripple/basics/base_uint.h>
#include <ripple/basics/strHex.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/LedgerFormats.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/STArray.h>
#include <ripple/protocol/STBase.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/STObject.h>
#include <ripple/protocol/STTx.h>
#include <ripple/protocol/Serializer.h>
#include <ripple/protocol/TER.h>
#include <ripple/protocol/TxFormats.h>
#include <ripple/protocol/TxMeta.h>
#include <algorithm>
#include <cstdint>
@@ -116,8 +116,8 @@ getNFTokenMintData(ripple::TxMeta const& txMeta, ripple::STTx const& sttx)
}
}
std::ranges::sort(finalIDs);
std::ranges::sort(prevIDs);
std::sort(finalIDs.begin(), finalIDs.end());
std::sort(prevIDs.begin(), prevIDs.end());
// Find the first NFT ID that doesn't match. We're looking for an
// added NFT, so the one we want will be the mismatch in finalIDs.
@@ -294,12 +294,14 @@ getNFTokenCancelOfferData(ripple::TxMeta const& txMeta, ripple::STTx const& sttx
txs.emplace_back(tokenID, txMeta, sttx.getTransactionID());
}
// Deduplicate any transactions based on tokenID
std::ranges::sort(txs, [](NFTTransactionsData const& a, NFTTransactionsData const& b) {
return a.tokenID < b.tokenID;
// Deduplicate any transactions based on tokenID/txIdx combo. Can't just
// use txIdx because in this case one tx can cancel offers for several
// NFTs.
std::sort(txs.begin(), txs.end(), [](NFTTransactionsData const& a, NFTTransactionsData const& b) {
return a.tokenID < b.tokenID && a.transactionIndex < b.transactionIndex;
});
auto last = std::unique(txs.begin(), txs.end(), [](NFTTransactionsData const& a, NFTTransactionsData const& b) {
return a.tokenID == b.tokenID;
return a.tokenID == b.tokenID && a.transactionIndex == b.transactionIndex;
});
txs.erase(last, txs.end());
return {txs, {}};
@@ -356,21 +358,4 @@ getNFTDataFromObj(std::uint32_t const seq, std::string const& key, std::string c
return nfts;
}
std::vector<NFTsData>
getUniqueNFTsDatas(std::vector<NFTsData> const& nfts)
{
std::vector<NFTsData> results = nfts;
std::ranges::sort(results, [](NFTsData const& a, NFTsData const& b) {
return a.tokenID == b.tokenID ? a.transactionIndex > b.transactionIndex : a.tokenID > b.tokenID;
});
auto const last = std::unique(results.begin(), results.end(), [](NFTsData const& a, NFTsData const& b) {
return a.tokenID == b.tokenID;
});
results.erase(last, results.end());
return results;
}
} // namespace etl

View File

@@ -22,8 +22,8 @@
#include "data/DBHelpers.hpp"
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TxMeta.h>
#include <ripple/protocol/STTx.h>
#include <ripple/protocol/TxMeta.h>
#include <cstdint>
#include <optional>
@@ -104,14 +104,4 @@ getNFTDataFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx);
std::vector<NFTsData>
getNFTDataFromObj(std::uint32_t seq, std::string const& key, std::string const& blob);
/**
* @brief Get the unique NFTs data from a vector of NFTsData happening in the same ledger. For example, if a NFT has
* both accept offer and burn happening in the same ledger,we only keep the final state of the NFT.
* @param nfts The NFTs data to filter, happening in the same ledger
* @return The unique NFTs data
*/
std::vector<NFTsData>
getUniqueNFTsDatas(std::vector<NFTsData> const& nfts);
} // namespace etl

View File

@@ -22,7 +22,6 @@
#include "data/BackendInterface.hpp"
#include "etl/NetworkValidatedLedgersInterface.hpp"
#include "feed/SubscriptionManagerInterface.hpp"
#include "rpc/Errors.hpp"
#include "util/config/Config.hpp"
#include "util/log/Logger.hpp"
@@ -35,7 +34,6 @@
#include <chrono>
#include <cstdint>
#include <expected>
#include <functional>
#include <memory>
#include <optional>
@@ -53,7 +51,7 @@ namespace etl {
class SourceBase {
public:
using OnConnectHook = std::function<void()>;
using OnDisconnectHook = std::function<void(bool)>;
using OnDisconnectHook = std::function<void()>;
using OnLedgerClosedHook = std::function<void()>;
virtual ~SourceBase() = default;
@@ -133,9 +131,9 @@ public:
* @param forwardToRippledClientIp IP of the client forwarding this request if known
* @param xUserValue Value of the X-User header
* @param yield The coroutine context
* @return Response on success or error on failure
* @return Response wrapped in an optional on success; nullopt otherwise
*/
virtual std::expected<boost::json::object, rpc::ClioError>
virtual std::optional<boost::json::object>
forwardToRippled(
boost::json::object const& request,
std::optional<std::string> const& forwardToRippledClientIp,

View File

@@ -0,0 +1,95 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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 "etl/SystemState.hpp"
#include "util/log/Logger.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/steady_timer.hpp>
#include <chrono>
#include <functional>
namespace etl::impl {
struct AmendmentBlockAction {
void
operator()()
{
static util::Logger const log{"ETL"};
LOG(log.fatal()) << "Can't process new ledgers: The current ETL source is not compatible with the version of "
<< "the libxrpl Clio is currently using. Please upgrade Clio to a newer version.";
}
};
template <typename ActionCallableType = AmendmentBlockAction>
class AmendmentBlockHandler {
std::reference_wrapper<boost::asio::io_context> ctx_;
std::reference_wrapper<SystemState> state_;
boost::asio::steady_timer timer_;
std::chrono::milliseconds interval_;
ActionCallableType action_;
public:
template <typename DurationType = std::chrono::seconds>
AmendmentBlockHandler(
boost::asio::io_context& ioc,
SystemState& state,
DurationType interval = DurationType{1},
ActionCallableType&& action = ActionCallableType()
)
: ctx_{std::ref(ioc)}
, state_{std::ref(state)}
, timer_{ioc}
, interval_{std::chrono::duration_cast<std::chrono::milliseconds>(interval)}
, action_{std::move(action)}
{
}
~AmendmentBlockHandler()
{
boost::asio::post(ctx_.get(), [this]() { timer_.cancel(); });
}
void
onAmendmentBlock()
{
state_.get().isAmendmentBlocked = true;
startReportingTimer();
}
private:
void
startReportingTimer()
{
action_();
timer_.expires_after(interval_);
timer_.async_wait([this](auto ec) {
if (!ec)
boost::asio::post(ctx_.get(), [this] { startReportingTimer(); });
});
}
};
} // namespace etl::impl

View File

@@ -1,56 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "etl/impl/AmendmentBlockHandler.hpp"
#include "etl/SystemState.hpp"
#include "util/log/Logger.hpp"
#include <boost/asio/io_context.hpp>
#include <chrono>
#include <functional>
#include <utility>
namespace etl::impl {
AmendmentBlockHandler::ActionType const AmendmentBlockHandler::defaultAmendmentBlockAction = []() {
static util::Logger const log{"ETL"};
LOG(log.fatal()) << "Can't process new ledgers: The current ETL source is not compatible with the version of "
<< "the libxrpl Clio is currently using. Please upgrade Clio to a newer version.";
};
AmendmentBlockHandler::AmendmentBlockHandler(
boost::asio::io_context& ioc,
SystemState& state,
std::chrono::steady_clock::duration interval,
ActionType action
)
: state_{std::ref(state)}, repeat_{ioc}, interval_{interval}, action_{std::move(action)}
{
}
void
AmendmentBlockHandler::onAmendmentBlock()
{
state_.get().isAmendmentBlocked = true;
repeat_.start(interval_, action_);
}
} // namespace etl::impl

View File

@@ -1,59 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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 "etl/SystemState.hpp"
#include "util/Repeat.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/steady_timer.hpp>
#include <chrono>
#include <functional>
namespace etl::impl {
class AmendmentBlockHandler {
public:
using ActionType = std::function<void()>;
private:
std::reference_wrapper<SystemState> state_;
util::Repeat repeat_;
std::chrono::steady_clock::duration interval_;
ActionType action_;
public:
static ActionType const defaultAmendmentBlockAction;
AmendmentBlockHandler(
boost::asio::io_context& ioc,
SystemState& state,
std::chrono::steady_clock::duration interval = std::chrono::seconds{1},
ActionType action = defaultAmendmentBlockAction
);
void
onAmendmentBlock();
};
} // namespace etl::impl

View File

@@ -22,7 +22,6 @@
#include "data/BackendInterface.hpp"
#include "data/Types.hpp"
#include "etl/ETLHelpers.hpp"
#include "etl/MPTHelpers.hpp"
#include "etl/NFTHelpers.hpp"
#include "util/Assert.hpp"
#include "util/log/Logger.hpp"
@@ -31,9 +30,9 @@
#include <grpcpp/grpcpp.h>
#include <grpcpp/support/status.h>
#include <org/xrpl/rpc/v1/get_ledger_data.pb.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
#include <ripple/basics/base_uint.h>
#include <ripple/basics/strHex.h>
#include <ripple/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
#include <cstddef>
#include <cstdint>
@@ -155,11 +154,6 @@ public:
backend.writeSuccessor(std::move(lastKey_), request_.ledger().sequence(), std::string{obj.key()});
lastKey_ = obj.key();
backend.writeNFTs(getNFTDataFromObj(request_.ledger().sequence(), obj.key(), obj.data()));
auto const maybeMPTHolder = getMPTHolderFromObj(obj.key(), obj.data());
if (maybeMPTHolder)
backend.writeMPTHolders({*maybeMPTHolder});
backend.writeLedgerObject(
std::move(*obj.mutable_key()), request_.ledger().sequence(), std::move(*obj.mutable_data())
);

View File

@@ -19,7 +19,7 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <ripple/basics/base_uint.h>
#include <cstdint>
#include <vector>

View File

@@ -28,9 +28,9 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/context/detail/config.hpp>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <ripple/basics/Blob.h>
#include <ripple/basics/base_uint.h>
#include <ripple/basics/strHex.h>
#include <algorithm>
#include <atomic>

View File

@@ -23,7 +23,7 @@
#include "data/Types.hpp"
#include "etl/impl/BaseCursorProvider.hpp"
#include <xrpl/basics/base_uint.h>
#include <ripple/basics/base_uint.h>
#include <algorithm>
#include <cstddef>

View File

@@ -24,7 +24,7 @@
#include "etl/impl/BaseCursorProvider.hpp"
#include "util/Assert.hpp"
#include <xrpl/basics/base_uint.h>
#include <ripple/basics/base_uint.h>
#include <algorithm>
#include <cstddef>

View File

@@ -25,8 +25,8 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/asio/io_context.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <ripple/basics/base_uint.h>
#include <ripple/basics/strHex.h>
#include <algorithm>
#include <cstddef>

View File

@@ -25,7 +25,7 @@
#include "util/Profiler.hpp"
#include "util/log/Logger.hpp"
#include <xrpl/beast/core/CurrentThreadName.h>
#include <ripple/beast/core/CurrentThreadName.h>
#include <chrono>
#include <cstdint>

View File

@@ -17,56 +17,88 @@
*/
//==============================================================================
#include "util/ResponseExpirationCache.hpp"
#include "etl/impl/ForwardingCache.hpp"
#include "util/Assert.hpp"
#include <boost/json/object.hpp>
#include <boost/json/value_to.hpp>
#include <chrono>
#include <mutex>
#include <optional>
#include <shared_mutex>
#include <string>
#include <unordered_set>
#include <utility>
namespace util {
namespace etl::impl {
namespace {
std::optional<std::string>
getCommand(boost::json::object const& request)
{
if (not request.contains("command")) {
return std::nullopt;
}
return boost::json::value_to<std::string>(request.at("command"));
}
} // namespace
void
ResponseExpirationCache::Entry::put(boost::json::object response)
CacheEntry::put(boost::json::object response)
{
response_ = std::move(response);
lastUpdated_ = std::chrono::steady_clock::now();
}
std::optional<boost::json::object>
ResponseExpirationCache::Entry::get() const
CacheEntry::get() const
{
return response_;
}
std::chrono::steady_clock::time_point
ResponseExpirationCache::Entry::lastUpdated() const
CacheEntry::lastUpdated() const
{
return lastUpdated_;
}
void
ResponseExpirationCache::Entry::invalidate()
CacheEntry::invalidate()
{
response_.reset();
}
bool
ResponseExpirationCache::shouldCache(std::string const& cmd)
std::unordered_set<std::string> const
ForwardingCache::CACHEABLE_COMMANDS{"server_info", "server_state", "server_definitions", "fee", "ledger_closed"};
ForwardingCache::ForwardingCache(std::chrono::steady_clock::duration const cacheTimeout) : cacheTimeout_{cacheTimeout}
{
return cache_.contains(cmd);
for (auto const& command : CACHEABLE_COMMANDS) {
cache_.emplace(command, CacheEntry{});
}
}
bool
ForwardingCache::shouldCache(boost::json::object const& request)
{
auto const command = getCommand(request);
return command.has_value() and CACHEABLE_COMMANDS.contains(*command);
}
std::optional<boost::json::object>
ResponseExpirationCache::get(std::string const& cmd) const
ForwardingCache::get(boost::json::object const& request) const
{
auto it = cache_.find(cmd);
auto const command = getCommand(request);
if (not command.has_value()) {
return std::nullopt;
}
auto it = cache_.find(*command);
if (it == cache_.end())
return std::nullopt;
@@ -78,19 +110,20 @@ ResponseExpirationCache::get(std::string const& cmd) const
}
void
ResponseExpirationCache::put(std::string const& cmd, boost::json::object const& response)
ForwardingCache::put(boost::json::object const& request, boost::json::object const& response)
{
if (not shouldCache(cmd))
auto const command = getCommand(request);
if (not command.has_value() or not shouldCache(request))
return;
ASSERT(cache_.contains(cmd), "Command is not in the cache: {}", cmd);
ASSERT(cache_.contains(*command), "Command is not in the cache: {}", *command);
auto entry = cache_[cmd].lock<std::unique_lock>();
auto entry = cache_[*command].lock<std::unique_lock>();
entry->put(response);
}
void
ResponseExpirationCache::invalidate()
ForwardingCache::invalidate()
{
for (auto& [_, entry] : cache_) {
auto entryLock = entry.lock<std::unique_lock>();
@@ -98,4 +131,4 @@ ResponseExpirationCache::invalidate()
}
}
} // namespace util
} // namespace etl::impl

View File

@@ -30,92 +30,90 @@
#include <unordered_map>
#include <unordered_set>
namespace util {
namespace etl::impl {
/**
* @brief Cache of requests' responses with TTL support and configurable cachable commands
* @brief A class to store a cache entry.
*/
class ResponseExpirationCache {
/**
* @brief A class to store a cache entry.
*/
class Entry {
std::chrono::steady_clock::time_point lastUpdated_;
std::optional<boost::json::object> response_;
public:
/**
* @brief Put a response into the cache
*
* @param response The response to store
*/
void
put(boost::json::object response);
/**
* @brief Get the response from the cache
*
* @return The response
*/
std::optional<boost::json::object>
get() const;
/**
* @brief Get the last time the cache was updated
*
* @return The last time the cache was updated
*/
std::chrono::steady_clock::time_point
lastUpdated() const;
/**
* @brief Invalidate the cache entry
*/
void
invalidate();
};
std::chrono::steady_clock::duration cacheTimeout_;
std::unordered_map<std::string, util::Mutex<Entry, std::shared_mutex>> cache_;
bool
shouldCache(std::string const& cmd);
class CacheEntry {
std::chrono::steady_clock::time_point lastUpdated_;
std::optional<boost::json::object> response_;
public:
/**
* @brief Construct a new Cache object
* @brief Put a response into the cache
*
* @param response The response to store
*/
void
put(boost::json::object response);
/**
* @brief Get the response from the cache
*
* @return The response
*/
std::optional<boost::json::object>
get() const;
/**
* @brief Get the last time the cache was updated
*
* @return The last time the cache was updated
*/
std::chrono::steady_clock::time_point
lastUpdated() const;
/**
* @brief Invalidate the cache entry
*/
void
invalidate();
};
/**
* @brief A class to store a cache of forwarding responses
*/
class ForwardingCache {
std::chrono::steady_clock::duration cacheTimeout_;
std::unordered_map<std::string, util::Mutex<CacheEntry, std::shared_mutex>> cache_;
public:
static std::unordered_set<std::string> const CACHEABLE_COMMANDS;
/**
* @brief Construct a new Forwarding Cache object
*
* @param cacheTimeout The time for cache entries to expire
* @param cmds The commands that should be cached
*/
ResponseExpirationCache(
std::chrono::steady_clock::duration cacheTimeout,
std::unordered_set<std::string> const& cmds
)
: cacheTimeout_(cacheTimeout)
{
for (auto const& command : cmds) {
cache_.emplace(command, Entry{});
}
}
ForwardingCache(std::chrono::steady_clock::duration cacheTimeout);
/**
* @brief Check if a request should be cached
*
* @param request The request to check
* @return true if the request should be cached and false otherwise
*/
[[nodiscard]] static bool
shouldCache(boost::json::object const& request);
/**
* @brief Get a response from the cache
*
* @param cmd The command to get the response for
* @param request The request to get the response for
* @return The response if it exists or std::nullopt otherwise
*/
[[nodiscard]] std::optional<boost::json::object>
get(std::string const& cmd) const;
get(boost::json::object const& request) const;
/**
* @brief Put a response into the cache if the request should be cached
*
* @param cmd The command to store the response for
* @param request The request to store the response for
* @param response The response to store
*/
void
put(std::string const& cmd, boost::json::object const& response);
put(boost::json::object const& request, boost::json::object const& response);
/**
* @brief Invalidate all entries in the cache
@@ -123,4 +121,5 @@ public:
void
invalidate();
};
} // namespace util
} // namespace etl::impl

View File

@@ -19,7 +19,6 @@
#include "etl/impl/ForwardingSource.hpp"
#include "rpc/Errors.hpp"
#include "util/log/Logger.hpp"
#include <boost/asio/spawn.hpp>
@@ -56,7 +55,7 @@ ForwardingSource::ForwardingSource(
);
}
std::expected<boost::json::object, rpc::ClioError>
std::optional<boost::json::object>
ForwardingSource::forwardToRippled(
boost::json::object const& request,
std::optional<std::string> const& forwardToRippledClientIp,
@@ -75,26 +74,18 @@ ForwardingSource::forwardToRippled(
auto expectedConnection = connectionBuilder.connect(yield);
if (not expectedConnection) {
LOG(log_.debug()) << "Couldn't connect to rippled to forward request.";
return std::unexpected{rpc::ClioError::etlCONNECTION_ERROR};
return std::nullopt;
}
auto& connection = expectedConnection.value();
auto writeError = connection->write(boost::json::serialize(request), yield, forwardingTimeout_);
if (writeError) {
LOG(log_.debug()) << "Error sending request to rippled to forward request.";
return std::unexpected{rpc::ClioError::etlREQUEST_ERROR};
return std::nullopt;
}
auto response = connection->read(yield, forwardingTimeout_);
if (not response) {
if (auto errorCode = response.error().errorCode();
errorCode.has_value() and errorCode->value() == boost::system::errc::timed_out) {
LOG(log_.debug()) << "Request to rippled timed out";
return std::unexpected{rpc::ClioError::etlREQUEST_TIMEOUT};
}
LOG(log_.debug()) << "Error sending request to rippled to forward request.";
return std::unexpected{rpc::ClioError::etlREQUEST_ERROR};
return std::nullopt;
}
boost::json::value parsedResponse;
@@ -103,8 +94,8 @@ ForwardingSource::forwardToRippled(
if (not parsedResponse.is_object())
throw std::runtime_error("response is not an object");
} catch (std::exception const& e) {
LOG(log_.debug()) << "Error parsing response from rippled: " << e.what() << ". Response: " << *response;
return std::unexpected{rpc::ClioError::etlINVALID_RESPONSE};
LOG(log_.error()) << "Error parsing response from rippled: " << e.what() << ". Response: " << *response;
return std::nullopt;
}
auto responseObject = parsedResponse.as_object();

View File

@@ -19,7 +19,6 @@
#pragma once
#include "rpc/Errors.hpp"
#include "util/log/Logger.hpp"
#include "util/requests/WsConnection.hpp"
@@ -27,7 +26,6 @@
#include <boost/json/object.hpp>
#include <chrono>
#include <expected>
#include <optional>
#include <string>
#include <string_view>
@@ -56,9 +54,9 @@ public:
* @param forwardToRippledClientIp IP of the client forwarding this request if known
* @param xUserValue Optional value for X-User header
* @param yield The coroutine context
* @return Response on success or error on failure
* @return Response wrapped in an optional on success; nullopt otherwise
*/
std::expected<boost::json::object, rpc::ClioError>
std::optional<boost::json::object>
forwardToRippled(
boost::json::object const& request,
std::optional<std::string> const& forwardToRippledClientIp,

View File

@@ -24,7 +24,7 @@
#include "util/Assert.hpp"
#include "util/log/Logger.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <fmt/core.h>
#include <grpcpp/client_context.h>
@@ -39,7 +39,6 @@
#include <exception>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
@@ -47,17 +46,12 @@
namespace etl::impl {
GrpcSource::GrpcSource(std::string const& ip, std::string const& grpcPort, std::shared_ptr<BackendInterface> backend)
: log_(fmt::format("GrpcSource[{}:{}]", ip, grpcPort)), backend_(std::move(backend))
: log_(fmt::format("ETL_Grpc[{}:{}]", ip, grpcPort)), backend_(std::move(backend))
{
try {
boost::asio::io_context ctx;
boost::asio::ip::tcp::resolver resolver{ctx};
auto const resolverResult = resolver.resolve(ip, grpcPort);
if (resolverResult.empty()) {
throw std::runtime_error("Failed to resolve " + ip + ":" + grpcPort);
}
boost::asio::ip::tcp::endpoint const endpoint{boost::asio::ip::make_address(ip), std::stoi(grpcPort)};
std::stringstream ss;
ss << resolverResult.begin()->endpoint();
ss << endpoint;
grpc::ChannelArguments chArgs;
chArgs.SetMaxReceiveMessageSize(-1);
stub_ = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(

View File

@@ -24,7 +24,7 @@
#include <grpcpp/support/status.h>
#include <org/xrpl/rpc/v1/get_ledger.pb.h>
#include <xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
#include <ripple/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
#include <cstdint>
#include <memory>

View File

@@ -23,7 +23,7 @@
#include "util/log/Logger.hpp"
#include <grpcpp/grpcpp.h>
#include <xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
#include <ripple/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
#include <cstdint>
#include <memory>

View File

@@ -22,7 +22,6 @@
#include "data/BackendInterface.hpp"
#include "data/DBHelpers.hpp"
#include "data/Types.hpp"
#include "etl/MPTHelpers.hpp"
#include "etl/NFTHelpers.hpp"
#include "etl/SystemState.hpp"
#include "etl/impl/LedgerFetcher.hpp"
@@ -31,13 +30,13 @@
#include "util/Profiler.hpp"
#include "util/log/Logger.hpp"
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/core/CurrentThreadName.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/TxMeta.h>
#include <ripple/basics/base_uint.h>
#include <ripple/basics/strHex.h>
#include <ripple/beast/core/CurrentThreadName.h>
#include <ripple/protocol/LedgerHeader.h>
#include <ripple/protocol/STTx.h>
#include <ripple/protocol/Serializer.h>
#include <ripple/protocol/TxMeta.h>
#include <chrono>
#include <cstddef>
@@ -56,7 +55,6 @@ struct FormattedTransactionsData {
std::vector<AccountTransactionsData> accountTxData;
std::vector<NFTTransactionsData> nfTokenTxData;
std::vector<NFTsData> nfTokensData;
std::vector<MPTHolderData> mptHoldersData;
};
namespace etl::impl {
@@ -126,10 +124,6 @@ public:
if (maybeNFT)
result.nfTokensData.push_back(*maybeNFT);
auto const maybeMPTHolder = getMPTHolderFromTx(txMeta, sttx);
if (maybeMPTHolder)
result.mptHoldersData.push_back(*maybeMPTHolder);
result.accountTxData.emplace_back(txMeta, sttx.getTransactionID());
static constexpr std::size_t KEY_SIZE = 32;
std::string keyStr{reinterpret_cast<char const*>(sttx.getTransactionID().data()), KEY_SIZE};
@@ -142,7 +136,20 @@ public:
);
}
result.nfTokensData = getUniqueNFTsDatas(result.nfTokensData);
// Remove all but the last NFTsData for each id. unique removes all but the first of a group, so we want to
// reverse sort by transaction index
std::sort(result.nfTokensData.begin(), result.nfTokensData.end(), [](NFTsData const& a, NFTsData const& b) {
return a.tokenID > b.tokenID && a.transactionIndex > b.transactionIndex;
});
// Now we can unique the NFTs by tokenID.
auto last = std::unique(
result.nfTokensData.begin(),
result.nfTokensData.end(),
[](NFTsData const& a, NFTsData const& b) { return a.tokenID == b.tokenID; }
);
result.nfTokensData.erase(last, result.nfTokensData.end());
return result;
}
@@ -246,7 +253,6 @@ public:
backend_->writeAccountTransactions(std::move(insertTxResult.accountTxData));
backend_->writeNFTs(insertTxResult.nfTokensData);
backend_->writeNFTTransactions(insertTxResult.nfTokenTxData);
backend_->writeMPTHolders(insertTxResult.mptHoldersData);
}
backend_->finishWrites(sequence);

View File

@@ -31,12 +31,12 @@
#include <boost/asio/io_context.hpp>
#include <boost/asio/strand.hpp>
#include <xrpl/basics/chrono.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/Serializer.h>
#include <ripple/basics/chrono.h>
#include <ripple/protocol/Fees.h>
#include <ripple/protocol/LedgerHeader.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/STObject.h>
#include <ripple/protocol/Serializer.h>
#include <chrono>
#include <cstddef>
@@ -166,16 +166,13 @@ public:
LOG(log_.info()) << "Publishing ledger " << std::to_string(lgrInfo.seq);
if (!state_.get().isWriting) {
LOG(log_.info()) << "Updating ledger range for read node.";
LOG(log_.info()) << "Updating cache";
if (!cache_.get().isDisabled()) {
std::vector<data::LedgerObject> const diff = data::synchronousAndRetryOnTimeout([&](auto yield) {
return backend_->fetchLedgerDiff(lgrInfo.seq, yield);
});
cache_.get().update(diff, lgrInfo.seq);
}
std::vector<data::LedgerObject> const diff = data::synchronousAndRetryOnTimeout([&](auto yield) {
return backend_->fetchLedgerDiff(lgrInfo.seq, yield);
});
cache_.get().update(diff, lgrInfo.seq);
backend_->updateRange(lgrInfo.seq);
}

View File

@@ -23,7 +23,6 @@
#include "etl/impl/ForwardingSource.hpp"
#include "etl/impl/GrpcSource.hpp"
#include "etl/impl/SubscriptionSource.hpp"
#include "rpc/Errors.hpp"
#include <boost/asio/spawn.hpp>
#include <boost/json/object.hpp>
@@ -32,14 +31,12 @@
#include <chrono>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace etl::impl {
/**
@@ -208,9 +205,9 @@ public:
* @param forwardToRippledClientIp IP of the client forwarding this request if known
* @param xUserValue Optional value of the X-User header
* @param yield The coroutine context
* @return Response or ClioError
* @return Response wrapped in an optional on success; nullopt otherwise
*/
std::expected<boost::json::object, rpc::ClioError>
std::optional<boost::json::object>
forwardToRippled(
boost::json::object const& request,
std::optional<std::string> const& forwardToRippledClientIp,

View File

@@ -24,8 +24,6 @@
#include "rpc/JS.hpp"
#include "util/Retry.hpp"
#include "util/log/Logger.hpp"
#include "util/prometheus/Label.hpp"
#include "util/prometheus/Prometheus.hpp"
#include "util/requests/Types.hpp"
#include <boost/algorithm/string/classification.hpp>
@@ -42,7 +40,7 @@
#include <boost/json/serialize.hpp>
#include <boost/json/value_to.hpp>
#include <fmt/core.h>
#include <xrpl/protocol/jss.h>
#include <ripple/protocol/jss.h>
#include <algorithm>
#include <chrono>
@@ -68,28 +66,22 @@ SubscriptionSource::SubscriptionSource(
OnConnectHook onConnect,
OnDisconnectHook onDisconnect,
OnLedgerClosedHook onLedgerClosed,
std::chrono::steady_clock::duration const wsTimeout,
std::chrono::steady_clock::duration const connectionTimeout,
std::chrono::steady_clock::duration const retryDelay
)
: log_(fmt::format("SubscriptionSource[{}:{}]", ip, wsPort))
: log_(fmt::format("GrpcSource[{}:{}]", ip, wsPort))
, wsConnectionBuilder_(ip, wsPort)
, validatedLedgers_(std::move(validatedLedgers))
, subscriptions_(std::move(subscriptions))
, strand_(boost::asio::make_strand(ioContext))
, wsTimeout_(wsTimeout)
, retry_(util::makeRetryExponentialBackoff(retryDelay, RETRY_MAX_DELAY, strand_))
, onConnect_(std::move(onConnect))
, onDisconnect_(std::move(onDisconnect))
, onLedgerClosed_(std::move(onLedgerClosed))
, lastMessageTimeSecondsSinceEpoch_(PrometheusService::gaugeInt(
"subscription_source_last_message_time",
util::prometheus::Labels({{"source", fmt::format("{}:{}", ip, wsPort)}}),
"Seconds since epoch of the last message received from rippled subscription streams"
))
{
wsConnectionBuilder_.addHeader({boost::beast::http::field::user_agent, "clio-client"})
.addHeader({"X-User", "clio-client"})
.setConnectionTimeout(wsTimeout_);
.setConnectionTimeout(connectionTimeout);
}
SubscriptionSource::~SubscriptionSource()
@@ -141,7 +133,6 @@ void
SubscriptionSource::setForwarding(bool isForwarding)
{
isForwarding_ = isForwarding;
LOG(log_.info()) << "Forwarding set to " << isForwarding_;
}
std::chrono::steady_clock::time_point
@@ -175,22 +166,20 @@ SubscriptionSource::subscribe()
}
wsConnection_ = std::move(connection).value();
isConnected_ = true;
onConnect_();
auto const& subscribeCommand = getSubscribeCommandJson();
auto const writeErrorOpt = wsConnection_->write(subscribeCommand, yield, wsTimeout_);
auto const writeErrorOpt = wsConnection_->write(subscribeCommand, yield);
if (writeErrorOpt) {
handleError(writeErrorOpt.value(), yield);
return;
}
isConnected_ = true;
LOG(log_.info()) << "Connected";
onConnect_();
retry_.reset();
while (!stop_) {
auto const message = wsConnection_->read(yield, wsTimeout_);
auto const message = wsConnection_->read(yield);
if (not message) {
handleError(message.error(), yield);
return;
@@ -235,11 +224,10 @@ SubscriptionSource::handleMessage(std::string const& message)
auto validatedLedgers = boost::json::value_to<std::string>(result.at(JS(validated_ledgers)));
setValidatedRange(std::move(validatedLedgers));
}
LOG(log_.debug()) << "Received a message on ledger subscription stream. Message: " << object;
LOG(log_.info()) << "Received a message on ledger subscription stream. Message : " << object;
} else if (object.contains(JS(type)) && object.at(JS(type)) == JS_LedgerClosed) {
LOG(log_.debug()) << "Received a message of type 'ledgerClosed' on ledger subscription stream. Message: "
<< object;
LOG(log_.info()) << "Received a message on ledger subscription stream. Message : " << object;
if (object.contains(JS(ledger_index))) {
ledgerIndex = object.at(JS(ledger_index)).as_int64();
}
@@ -257,13 +245,10 @@ SubscriptionSource::handleMessage(std::string const& message)
// 2 - Validated transaction
// Only forward proposed transaction, validated transactions are sent by Clio itself
if (object.contains(JS(transaction)) and !object.contains(JS(meta))) {
LOG(log_.debug()) << "Forwarding proposed transaction: " << object;
subscriptions_->forwardProposedTransaction(object);
} else if (object.contains(JS(type)) && object.at(JS(type)) == JS_ValidationReceived) {
LOG(log_.debug()) << "Forwarding validation: " << object;
subscriptions_->forwardValidation(object);
} else if (object.contains(JS(type)) && object.at(JS(type)) == JS_ManifestReceived) {
LOG(log_.debug()) << "Forwarding manifest: " << object;
subscriptions_->forwardManifest(object);
}
}
@@ -276,7 +261,7 @@ SubscriptionSource::handleMessage(std::string const& message)
return std::nullopt;
} catch (std::exception const& e) {
LOG(log_.error()) << "Exception in handleMessage: " << e.what();
LOG(log_.error()) << "Exception in handleMessage : " << e.what();
return util::requests::RequestError{fmt::format("Error handling message: {}", e.what())};
}
}
@@ -285,14 +270,16 @@ void
SubscriptionSource::handleError(util::requests::RequestError const& error, boost::asio::yield_context yield)
{
isConnected_ = false;
bool const wasForwarding = isForwarding_.exchange(false);
isForwarding_ = false;
if (not stop_) {
LOG(log_.info()) << "Disconnected";
onDisconnect_(wasForwarding);
onDisconnect_();
}
if (wsConnection_ != nullptr) {
wsConnection_->close(yield);
auto const err = wsConnection_->close(yield);
if (err) {
LOG(log_.error()) << "Error closing websocket connection: " << err->message();
}
wsConnection_.reset();
}
@@ -319,11 +306,7 @@ SubscriptionSource::logError(util::requests::RequestError const& error) const
void
SubscriptionSource::setLastMessageTime()
{
lastMessageTimeSecondsSinceEpoch_.get().set(
std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count()
);
auto lock = lastMessageTime_.lock();
lock.get() = std::chrono::steady_clock::now();
lastMessageTime_.lock().get() = std::chrono::steady_clock::now();
}
void

View File

@@ -19,13 +19,12 @@
#pragma once
#include "etl/NetworkValidatedLedgersInterface.hpp"
#include "etl/ETLHelpers.hpp"
#include "etl/Source.hpp"
#include "feed/SubscriptionManagerInterface.hpp"
#include "util/Mutex.hpp"
#include "util/Retry.hpp"
#include "util/log/Logger.hpp"
#include "util/prometheus/Gauge.hpp"
#include "util/requests/Types.hpp"
#include "util/requests/WsConnection.hpp"
@@ -38,7 +37,6 @@
#include <atomic>
#include <chrono>
#include <cstdint>
#include <functional>
#include <future>
#include <memory>
#include <optional>
@@ -73,8 +71,6 @@ private:
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
std::chrono::steady_clock::duration wsTimeout_;
util::Retry retry_;
OnConnectHook onConnect_;
@@ -87,11 +83,9 @@ private:
util::Mutex<std::chrono::steady_clock::time_point> lastMessageTime_;
std::reference_wrapper<util::prometheus::GaugeInt> lastMessageTimeSecondsSinceEpoch_;
std::future<void> runFuture_;
static constexpr std::chrono::seconds WS_TIMEOUT{30};
static constexpr std::chrono::seconds CONNECTION_TIMEOUT{30};
static constexpr std::chrono::seconds RETRY_MAX_DELAY{30};
static constexpr std::chrono::seconds RETRY_DELAY{1};
@@ -105,11 +99,11 @@ public:
* @param wsPort The port of the source
* @param validatedLedgers The network validated ledgers object
* @param subscriptions The subscription manager object
* @param onConnect The onConnect hook. Called when the connection is established
* @param onDisconnect The onDisconnect hook. Called when the connection is lost
* @param onNewLedger The onNewLedger hook. Called when a new ledger is received
* @param onLedgerClosed The onLedgerClosed hook. Called when the ledger is closed but only if the source is
* forwarding
* @param wsTimeout A timeout for websocket operations. Defaults to 30 seconds
* @param connectionTimeout The connection timeout. Defaults to 30 seconds
* @param retryDelay The retry delay. Defaults to 1 second
*/
SubscriptionSource(
@@ -121,7 +115,7 @@ public:
OnConnectHook onConnect,
OnDisconnectHook onDisconnect,
OnLedgerClosedHook onLedgerClosed,
std::chrono::steady_clock::duration const wsTimeout = WS_TIMEOUT,
std::chrono::steady_clock::duration const connectionTimeout = CONNECTION_TIMEOUT,
std::chrono::steady_clock::duration const retryDelay = RETRY_DELAY
);

View File

@@ -23,7 +23,7 @@
#include "data/DBHelpers.hpp"
#include "data/Types.hpp"
#include "etl/SystemState.hpp"
#include "etl/impl/AmendmentBlockHandler.hpp"
#include "etl/impl/AmendmentBlock.hpp"
#include "etl/impl/LedgerLoader.hpp"
#include "util/Assert.hpp"
#include "util/LedgerUtils.hpp"
@@ -31,11 +31,11 @@
#include "util/log/Logger.hpp"
#include <grpcpp/grpcpp.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/core/CurrentThreadName.h>
#include <xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <ripple/basics/base_uint.h>
#include <ripple/basics/strHex.h>
#include <ripple/beast/core/CurrentThreadName.h>
#include <ripple/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
#include <ripple/protocol/LedgerHeader.h>
#include <chrono>
#include <cstdint>
@@ -213,7 +213,6 @@ private:
backend_->writeAccountTransactions(std::move(insertTxResultOp->accountTxData));
backend_->writeNFTs(insertTxResultOp->nfTokensData);
backend_->writeNFTTransactions(insertTxResultOp->nfTokenTxData);
backend_->writeMPTHolders(insertTxResultOp->mptHoldersData);
auto [success, duration] =
::util::timed<std::chrono::duration<double>>([&]() { return backend_->finishWrites(lgrInfo.seq); });

View File

@@ -1,5 +0,0 @@
add_library(clio_etlng INTERFACE)
# target_sources(clio_etlng PRIVATE )
target_link_libraries(clio_etlng INTERFACE clio_data)

View File

@@ -1,129 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include "util/Concepts.hpp"
#include <boost/json/object.hpp>
#include <fmt/core.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/proto/org/xrpl/rpc/v1/get_ledger.pb.h>
#include <xrpl/proto/org/xrpl/rpc/v1/ledger.pb.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/TxMeta.h>
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
namespace etlng::model {
/**
* @brief A specification for the Registry.
*
* This specification simply defines the transaction types that are to be filtered out from the incoming transactions by
* the Registry for its `onTransaction` and `onInitialTransaction` hooks.
* It's a compilation error to list the same transaction type more than once.
*/
template <ripple::TxType... Types>
requires(util::hasNoDuplicates(Types...))
struct Spec {
static constexpr bool SpecTag = true;
/**
* @brief Checks if the transaction type was requested.
*
* @param type The transaction type
* @return true if the transaction was requested; false otherwise
*/
[[nodiscard]] constexpr static bool
wants(ripple::TxType type) noexcept
{
return ((Types == type) || ...);
}
};
/**
* @brief Represents a single transaction on the ledger.
*/
struct Transaction {
std::string raw; // raw binary blob
std::string metaRaw;
// unpacked blob and meta
ripple::STTx sttx;
ripple::TxMeta meta;
// commonly used stuff
ripple::uint256 id;
std::string key; // key is the above id as a string of 32 characters
ripple::TxType type;
};
/**
* @brief Represents a single object on the ledger.
*/
struct Object {
/**
* @brief Modification type for the object.
*/
enum class ModType : int {
Unspecified = 0,
Created = 1,
Modified = 2,
Deleted = 3,
};
ripple::uint256 key;
std::string keyRaw;
ripple::Blob data;
std::string dataRaw;
std::string successor;
std::string predecessor;
ModType type;
};
/**
* @brief Represents a book successor.
*/
struct BookSuccessor {
std::string firstBook;
std::string bookBase;
};
/**
* @brief Represents an entire ledger diff worth of transactions and objects.
*/
struct LedgerData {
std::vector<Transaction> transactions;
std::vector<Object> objects;
std::optional<std::vector<BookSuccessor>> successors;
ripple::LedgerHeader header;
std::string rawHeader;
uint32_t seq;
};
} // namespace etlng::model

View File

@@ -1,108 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include "etlng/Models.hpp"
#include <cstdint>
#include <string>
#include <vector>
namespace etlng {
/**
* @brief The interface for a registry that can dispatch transactions and objects to extensions.
*
* This class defines the interface for dispatching data through to extensions.
*
* @note
* The registry itself consists of Extensions.
* Each extension must define at least one valid hook:
* - for ongoing ETL dispatch:
* - void onLedgerData(etlng::model::LedgerData const&)
* - void onTransaction(uint32_t, etlng::model::Transaction const&)
* - void onObject(uint32_t, etlng::model::Object const&)
* - for initial ledger load
* - void onInitialData(etlng::model::LedgerData const&)
* - void onInitialTransaction(uint32_t, etlng::model::Transaction const&)
* - for initial objects (called for each downloaded batch)
* - void onInitialObjects(uint32_t, std::vector<etlng::model::Object> const&, std::string)
* - void onInitialObject(uint32_t, etlng::model::Object const&)
*
* When the registry dispatches (initial)data or objects, each of the above hooks will be called in order on each
* registered extension.
* This means that the order of execution is from left to right (hooks) and top to bottom (registered extensions).
*
* If either `onTransaction` or `onInitialTransaction` are defined, the extension will have to additionally define a
* Specification. The specification lists transaction types to filter from the incoming data such that `onTransaction`
* and `onInitialTransaction` are only called for the transactions that are of interest for the given extension.
*
* The specification is setup like so:
* @code{.cpp}
* struct Ext {
* using spec = etlng::model::Spec<
* ripple::TxType::ttNFTOKEN_BURN,
* ripple::TxType::ttNFTOKEN_ACCEPT_OFFER,
* ripple::TxType::ttNFTOKEN_CREATE_OFFER,
* ripple::TxType::ttNFTOKEN_CANCEL_OFFER,
* ripple::TxType::ttNFTOKEN_MINT>;
*
* static void
* onInitialTransaction(uint32_t, etlng::model::Transaction const&);
* };
* @endcode
*/
struct RegistryInterface {
virtual ~RegistryInterface() = default;
/**
* @brief Dispatch initial objects.
*
* These objects are received during initial ledger load.
*
* @param seq The sequence
* @param data The objects to dispatch
* @param lastKey The predcessor of the first object in data if known; an empty string otherwise
*/
virtual void
dispatchInitialObjects(uint32_t seq, std::vector<model::Object> const& data, std::string lastKey) = 0;
/**
* @brief Dispatch initial ledger data.
*
* The transactions, header and edge keys are received during initial ledger load.
*
* @param data The data to dispatch
*/
virtual void
dispatchInitialData(model::LedgerData const& data) = 0;
/**
* @brief Dispatch an entire ledger diff.
*
* This is used to dispatch incoming diffs through the extensions.
*
* @param data The data to dispatch
*/
virtual void
dispatch(model::LedgerData const& data) = 0;
};
} // namespace etlng

View File

@@ -1,219 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include "etlng/Models.hpp"
#include "etlng/RegistryInterface.hpp"
#include <xrpl/protocol/TxFormats.h>
#include <concepts>
#include <cstdint>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
namespace etlng::impl {
template <typename T>
concept HasLedgerDataHook = requires(T p) {
{ p.onLedgerData(std::declval<etlng::model::LedgerData>()) } -> std::same_as<void>;
};
template <typename T>
concept HasInitialDataHook = requires(T p) {
{ p.onInitialData(std::declval<etlng::model::LedgerData>()) } -> std::same_as<void>;
};
template <typename T>
concept HasTransactionHook = requires(T p) {
{ p.onTransaction(uint32_t{}, std::declval<etlng::model::Transaction>()) } -> std::same_as<void>;
};
template <typename T>
concept HasObjectHook = requires(T p) {
{ p.onObject(uint32_t{}, std::declval<etlng::model::Object>()) } -> std::same_as<void>;
};
template <typename T>
concept HasInitialTransactionHook = requires(T p) {
{ p.onInitialTransaction(uint32_t{}, std::declval<etlng::model::Transaction>()) } -> std::same_as<void>;
};
template <typename T>
concept HasInitialObjectsHook = requires(T p) {
{
p.onInitialObjects(uint32_t{}, std::declval<std::vector<etlng::model::Object>>(), std::string{})
} -> std::same_as<void>;
};
template <typename T>
concept HasInitialObjectHook = requires(T p) {
{ p.onInitialObject(uint32_t{}, std::declval<etlng::model::Object>()) } -> std::same_as<void>;
};
template <typename T>
concept ContainsSpec = std::decay_t<T>::spec::SpecTag;
template <typename T>
concept ContainsValidHook = HasLedgerDataHook<T> or HasInitialDataHook<T> or
(HasTransactionHook<T> and ContainsSpec<T>) or (HasInitialTransactionHook<T> and ContainsSpec<T>) or
HasObjectHook<T> or HasInitialObjectsHook<T> or HasInitialObjectHook<T>;
template <typename T>
concept NoTwoOfKind = not(HasLedgerDataHook<T> and HasTransactionHook<T>) and
not(HasInitialDataHook<T> and HasInitialTransactionHook<T>) and not(HasInitialDataHook<T> and HasObjectHook<T>) and
not(HasInitialObjectsHook<T> and HasInitialObjectHook<T>);
template <typename T>
concept SomeExtension = NoTwoOfKind<T> and ContainsValidHook<T>;
template <SomeExtension... Ps>
class Registry : public RegistryInterface {
std::tuple<Ps...> store_;
static_assert(
(((not HasTransactionHook<std::decay_t<Ps>>) or ContainsSpec<std::decay_t<Ps>>) and ...),
"Spec must be specified when 'onTransaction' function exists."
);
static_assert(
(((not HasInitialTransactionHook<std::decay_t<Ps>>) or ContainsSpec<std::decay_t<Ps>>) and ...),
"Spec must be specified when 'onInitialTransaction' function exists."
);
public:
explicit constexpr Registry(SomeExtension auto&&... exts)
requires(std::is_same_v<std::decay_t<decltype(exts)>, std::decay_t<Ps>> and ...)
: store_(std::forward<Ps>(exts)...)
{
}
~Registry() override = default;
Registry(Registry const&) = delete;
Registry(Registry&&) = default;
Registry&
operator=(Registry const&) = delete;
Registry&
operator=(Registry&&) = default;
constexpr void
dispatch(model::LedgerData const& data) override
{
// send entire batch of data at once
{
auto const expand = [&](auto& p) {
if constexpr (requires { p.onLedgerData(data); }) {
p.onLedgerData(data);
}
};
std::apply([&expand](auto&&... xs) { (expand(xs), ...); }, store_);
}
// send filtered transactions
{
auto const expand = [&]<typename P>(P& p, model::Transaction const& t) {
if constexpr (requires { p.onTransaction(data.seq, t); }) {
if (std::decay_t<P>::spec::wants(t.type))
p.onTransaction(data.seq, t);
}
};
for (auto const& t : data.transactions) {
std::apply([&expand, &t](auto&&... xs) { (expand(xs, t), ...); }, store_);
}
}
// send per object path
{
auto const expand = [&]<typename P>(P&& p, model::Object const& o) {
if constexpr (requires { p.onObject(data.seq, o); }) {
p.onObject(data.seq, o);
}
};
for (auto const& obj : data.objects) {
std::apply([&expand, &obj](auto&&... xs) { (expand(xs, obj), ...); }, store_);
}
}
}
constexpr void
dispatchInitialObjects(uint32_t seq, std::vector<model::Object> const& data, std::string lastKey) override
{
// send entire vector path
{
auto const expand = [&](auto&& p) {
if constexpr (requires { p.onInitialObjects(seq, data, lastKey); }) {
p.onInitialObjects(seq, data, lastKey);
}
};
std::apply([&expand](auto&&... xs) { (expand(xs), ...); }, store_);
}
// send per object path
{
auto const expand = [&]<typename P>(P&& p, model::Object const& o) {
if constexpr (requires { p.onInitialObject(seq, o); }) {
p.onInitialObject(seq, o);
}
};
for (auto const& obj : data) {
std::apply([&expand, &obj](auto&&... xs) { (expand(xs, obj), ...); }, store_);
}
}
}
constexpr void
dispatchInitialData(model::LedgerData const& data) override
{
// send entire batch path
{
auto const expand = [&](auto&& p) {
if constexpr (requires { p.onInitialData(data); }) {
p.onInitialData(data);
}
};
std::apply([&expand](auto&&... xs) { (expand(xs), ...); }, store_);
}
// send per tx path
{
auto const expand = [&]<typename P>(P&& p, model::Transaction const& tx) {
if constexpr (requires { p.onInitialTransaction(data.seq, tx); }) {
if (std::decay_t<P>::spec::wants(tx.type))
p.onInitialTransaction(data.seq, tx);
}
};
for (auto const& tx : data.transactions) {
std::apply([&expand, &tx](auto&&... xs) { (expand(xs, tx), ...); }, store_);
}
}
}
};
} // namespace etlng::impl

View File

@@ -24,10 +24,10 @@
#include <boost/asio/spawn.hpp>
#include <boost/json/object.hpp>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/Book.h>
#include <ripple/protocol/Fees.h>
#include <ripple/protocol/LedgerHeader.h>
#include <cstdint>
#include <string>

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