mirror of
https://github.com/Xahau/xahaud.git
synced 2026-10-22 11:47:09 +00:00
Compare commits
41
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac1e2356e5 | ||
|
|
5de98a6775 | ||
|
|
5c05e107c0 | ||
|
|
3dc41b2a59 | ||
|
|
b9f2914c0e | ||
|
|
023f55e283 | ||
|
|
d20e8ffb56 | ||
|
|
c63ee8cc5f | ||
|
|
64a30b2a15 | ||
|
|
0025355f43 | ||
|
|
7046616cdb | ||
|
|
a2e7f3c645 | ||
|
|
004752d1c2 | ||
|
|
22e6ad21b4 | ||
|
|
3423ee4665 | ||
|
|
a2a1e09c2e | ||
|
|
505c28768a | ||
|
|
d7f320bd17 | ||
|
|
22a94c4429 | ||
|
|
42a33a224d | ||
|
|
ee33d825fc | ||
|
|
9ef66a65b8 | ||
|
|
98fdaa4afa | ||
|
|
d9cc66afe9 | ||
|
|
b260b445e9 | ||
|
|
4e0e108654 | ||
|
|
919d817edc | ||
|
|
0373045bff | ||
|
|
2baa23d14b | ||
|
|
de9d5e53f1 | ||
|
|
80c70ec03d | ||
|
|
2566bcf6fa | ||
|
|
7f37aeac39 | ||
|
|
21fab66a17 | ||
|
|
0f3258d235 | ||
|
|
3fbeb130f8 | ||
|
|
58efea062c | ||
|
|
43d90b8549 | ||
|
|
a3bd76fdc9 | ||
|
|
4de217a2c6 | ||
|
|
5e49dc8474 |
No files matched your search
@@ -15,6 +15,9 @@ env:
|
||||
|
||||
jobs:
|
||||
checkout:
|
||||
# build publishes to build.xahau.tech, so this workflow only runs from
|
||||
# Xahau/xahaud. build and tests are skipped along with this job.
|
||||
if: github.repository == 'Xahau/xahaud'
|
||||
runs-on: [self-hosted, vanity]
|
||||
outputs:
|
||||
checkout_path: ${{ steps.vars.outputs.checkout_path }}
|
||||
@@ -86,7 +89,7 @@ jobs:
|
||||
cleanup:
|
||||
runs-on: [self-hosted, xahaud-build]
|
||||
needs: [tests, checkout]
|
||||
if: always()
|
||||
if: always() && github.repository == 'Xahau/xahaud'
|
||||
steps:
|
||||
- name: Cleanup workspace
|
||||
run: |
|
||||
|
||||
@@ -8,12 +8,6 @@ on:
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
full_matrix:
|
||||
description: "Force full matrix (6 configs)"
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -30,37 +24,14 @@ jobs:
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: escape double quotes
|
||||
id: escape
|
||||
shell: bash
|
||||
env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
run: |
|
||||
ESCAPED_PR_TITLE="${PR_TITLE//\"/\\\"}"
|
||||
echo "title=${ESCAPED_PR_TITLE}" >> "$GITHUB_OUTPUT"
|
||||
- name: Generate build matrix
|
||||
id: set-matrix
|
||||
shell: python
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
|
||||
# Full matrix with all 6 compiler configurations
|
||||
# Each configuration includes all parameters needed by the build job
|
||||
full_matrix = [
|
||||
{
|
||||
"compiler_id": "gcc-11-libstdcxx",
|
||||
"compiler": "gcc",
|
||||
"cc": "gcc-11",
|
||||
"cxx": "g++-11",
|
||||
"compiler_version": 11,
|
||||
"stdlib": "default",
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
},
|
||||
matrix = [
|
||||
{
|
||||
"compiler_id": "gcc-13-libstdcxx",
|
||||
"compiler": "gcc",
|
||||
@@ -93,17 +64,6 @@ jobs:
|
||||
"grpc/*": ["-Wno-missing-template-arg-list-after-template-kw"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"compiler_id": "clang-14-libstdcxx-gcc11",
|
||||
"compiler": "clang",
|
||||
"cc": "clang-14",
|
||||
"cxx": "clang++-14",
|
||||
"compiler_version": 14,
|
||||
"stdlib": "libstdcxx",
|
||||
"clang_gcc_toolchain": 11,
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
},
|
||||
{
|
||||
"compiler_id": "clang-16-libstdcxx-gcc13",
|
||||
"compiler": "clang",
|
||||
@@ -114,117 +74,8 @@ jobs:
|
||||
"clang_gcc_toolchain": 13,
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
},
|
||||
{
|
||||
"compiler_id": "clang-17-libcxx",
|
||||
"compiler": "clang",
|
||||
"cc": "clang-17",
|
||||
"cxx": "clang++-17",
|
||||
"compiler_version": 17,
|
||||
"stdlib": "libcxx",
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
},
|
||||
{
|
||||
# Clang 18 - testing if it's faster than Clang 17 with libc++
|
||||
# Requires patching Conan v1 settings.yml to add version 18
|
||||
"compiler_id": "clang-18-libcxx",
|
||||
"compiler": "clang",
|
||||
"cc": "clang-18",
|
||||
"cxx": "clang++-18",
|
||||
"compiler_version": 18,
|
||||
"stdlib": "libcxx",
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
}
|
||||
]
|
||||
|
||||
# Minimal matrix for PRs and feature branches
|
||||
minimal_matrix = [
|
||||
full_matrix[1], # gcc-13 (middle-ground gcc)
|
||||
full_matrix[2], # clang-20 llvm-cov coverage
|
||||
full_matrix[3] # clang-14 (mature, stable clang)
|
||||
]
|
||||
|
||||
# Determine which matrix to use based on the target branch
|
||||
ref = "${{ github.ref }}"
|
||||
base_ref = "${{ github.base_ref }}" # For PRs, this is the target branch
|
||||
event_name = "${{ github.event_name }}"
|
||||
pr_title = """${{ steps.escape.outputs.title }}"""
|
||||
pr_labels = """${{ join(github.event.pull_request.labels.*.name, ',') }}"""
|
||||
pr_head_sha = "${{ github.event.pull_request.head.sha }}"
|
||||
|
||||
# Get commit message - for PRs, fetch via API since head_commit.message is empty
|
||||
if event_name == "pull_request" and pr_head_sha:
|
||||
try:
|
||||
url = f"https://api.github.com/repos/${{ github.repository }}/commits/{pr_head_sha}"
|
||||
req = urllib.request.Request(url, headers={
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
"Authorization": f"Bearer {os.environ.get('GH_TOKEN', '')}"
|
||||
})
|
||||
with urllib.request.urlopen(req) as response:
|
||||
data = json.load(response)
|
||||
commit_message = data["commit"]["message"]
|
||||
except Exception as e:
|
||||
print(f"Failed to fetch commit message: {e}")
|
||||
commit_message = ""
|
||||
else:
|
||||
commit_message = """${{ github.event.head_commit.message }}"""
|
||||
|
||||
# Debug logging
|
||||
print(f"Event: {event_name}")
|
||||
print(f"Ref: {ref}")
|
||||
print(f"Base ref: {base_ref}")
|
||||
print(f"PR head SHA: {pr_head_sha}")
|
||||
print(f"PR title: {pr_title}")
|
||||
print(f"PR labels: {pr_labels}")
|
||||
print(f"Commit message: {commit_message}")
|
||||
|
||||
# Manual trigger input to force full matrix.
|
||||
manual_full = "${{ github.event.inputs.full_matrix || 'false' }}" == "true"
|
||||
|
||||
# Label/manual overrides, while preserving existing title/commit behavior.
|
||||
force_full = (
|
||||
manual_full
|
||||
or "[ci-nix-full-matrix]" in commit_message
|
||||
or "[ci-nix-full-matrix]" in pr_title
|
||||
or ("ci-full-build" in pr_labels and "ci-nix-full-matrix" in pr_labels)
|
||||
)
|
||||
force_min = (
|
||||
"ci-full-build" in pr_labels
|
||||
)
|
||||
print(f"Force full matrix: {force_full}")
|
||||
print(f"Force min matrix: {force_min}")
|
||||
|
||||
# Check if this is targeting a main branch
|
||||
# For PRs: check base_ref (target branch)
|
||||
# For pushes: check ref (current branch)
|
||||
main_branches = ["refs/heads/dev", "refs/heads/release", "refs/heads/candidate"]
|
||||
|
||||
if force_full:
|
||||
# Override: always use full matrix if forced by manual input or label.
|
||||
use_full = True
|
||||
elif force_min:
|
||||
# Override: always use minimal matrix if ci-full-build label is present.
|
||||
use_full = False
|
||||
elif event_name == "pull_request":
|
||||
# For PRs, base_ref is just the branch name (e.g., "dev", not "refs/heads/dev")
|
||||
# Check if the PR targets release or candidate (more critical branches)
|
||||
use_full = base_ref in ["release", "candidate"]
|
||||
else:
|
||||
# For pushes, ref is the full reference (e.g., "refs/heads/dev")
|
||||
use_full = ref in main_branches
|
||||
|
||||
# Select the appropriate matrix
|
||||
if use_full:
|
||||
if force_full:
|
||||
print(f"Using FULL matrix (7 configs (build x6 + clang-20 llvm-cov coverage)) - forced by [ci-nix-full-matrix] tag")
|
||||
else:
|
||||
print(f"Using FULL matrix (7 configs (build x6 + clang-20 llvm-cov coverage)) - targeting main branch")
|
||||
matrix = full_matrix
|
||||
else:
|
||||
print(f"Using MINIMAL matrix (3 configs) - feature branch/PR")
|
||||
matrix = minimal_matrix
|
||||
|
||||
# Add runs_on based on job_type
|
||||
for entry in matrix:
|
||||
@@ -498,7 +349,7 @@ jobs:
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload coverage report
|
||||
if: matrix.job_type == 'coverage'
|
||||
if: matrix.job_type == 'coverage' && github.repository == 'Xahau/xahaud'
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: ${{ env.COVERAGE_FILE }}
|
||||
|
||||
@@ -26,7 +26,7 @@ Loop: xrpld.app xrpld.nodestore
|
||||
xrpld.app > xrpld.nodestore
|
||||
|
||||
Loop: xrpld.app xrpld.overlay
|
||||
xrpld.overlay ~= xrpld.app
|
||||
xrpld.overlay == xrpld.app
|
||||
|
||||
Loop: xrpld.app xrpld.peerfinder
|
||||
xrpld.app > xrpld.peerfinder
|
||||
@@ -41,7 +41,7 @@ Loop: xrpld.core xrpld.net
|
||||
xrpld.net > xrpld.core
|
||||
|
||||
Loop: xrpld.core xrpld.perflog
|
||||
xrpld.perflog == xrpld.core
|
||||
xrpld.perflog ~= xrpld.core
|
||||
|
||||
Loop: xrpld.net xrpld.rpc
|
||||
xrpld.rpc > xrpld.net
|
||||
|
||||
@@ -12,6 +12,7 @@ libxrpl.server > xrpl.basics
|
||||
libxrpl.server > xrpl.json
|
||||
libxrpl.server > xrpl.protocol
|
||||
libxrpl.server > xrpl.server
|
||||
test.app > test.shamap
|
||||
test.app > test.toplevel
|
||||
test.app > test.unit_test
|
||||
test.app > xrpl.basics
|
||||
@@ -21,6 +22,7 @@ test.app > xrpld.ledger
|
||||
test.app > xrpld.nodestore
|
||||
test.app > xrpld.overlay
|
||||
test.app > xrpld.rpc
|
||||
test.app > xrpld.shamap
|
||||
test.app > xrpl.hook
|
||||
test.app > xrpl.json
|
||||
test.app > xrpl.protocol
|
||||
@@ -32,6 +34,7 @@ test.basics > xrpld.perflog
|
||||
test.basics > xrpld.rpc
|
||||
test.basics > xrpl.json
|
||||
test.basics > xrpl.protocol
|
||||
test.beast > test.unit_test
|
||||
test.beast > xrpl.basics
|
||||
test.conditions > xrpl.basics
|
||||
test.conditions > xrpld.conditions
|
||||
@@ -49,8 +52,6 @@ test.core > test.toplevel
|
||||
test.core > test.unit_test
|
||||
test.core > xrpl.basics
|
||||
test.core > xrpld.core
|
||||
test.core > xrpld.perflog
|
||||
test.core > xrpl.json
|
||||
test.core > xrpl.server
|
||||
test.csf > xrpl.basics
|
||||
test.csf > xrpld.consensus
|
||||
@@ -95,10 +96,12 @@ test.overlay > test.toplevel
|
||||
test.overlay > test.unit_test
|
||||
test.overlay > xrpl.basics
|
||||
test.overlay > xrpld.app
|
||||
test.overlay > xrpld.core
|
||||
test.overlay > xrpld.overlay
|
||||
test.overlay > xrpld.peerfinder
|
||||
test.overlay > xrpld.shamap
|
||||
test.overlay > xrpl.protocol
|
||||
test.overlay > xrpl.resource
|
||||
test.peerfinder > test.beast
|
||||
test.peerfinder > test.unit_test
|
||||
test.peerfinder > xrpl.basics
|
||||
@@ -122,6 +125,7 @@ test.rpc > xrpl.basics
|
||||
test.rpc > xrpld.app
|
||||
test.rpc > xrpld.core
|
||||
test.rpc > xrpld.net
|
||||
test.rpc > xrpld.nodestore
|
||||
test.rpc > xrpld.overlay
|
||||
test.rpc > xrpld.rpc
|
||||
test.rpc > xrpld.shamap
|
||||
@@ -190,6 +194,7 @@ xrpld.overlay > xrpl.basics
|
||||
xrpld.overlay > xrpld.core
|
||||
xrpld.overlay > xrpld.peerfinder
|
||||
xrpld.overlay > xrpld.perflog
|
||||
xrpld.overlay > xrpld.shamap
|
||||
xrpld.overlay > xrpl.json
|
||||
xrpld.overlay > xrpl.protocol
|
||||
xrpld.overlay > xrpl.resource
|
||||
|
||||
+2
-2
@@ -88,7 +88,7 @@ git remote -v >> release.info &&
|
||||
echo "Git status:" >> release.info &&
|
||||
git status -v >> release.info &&
|
||||
echo "Git log [last 20]:" >> release.info &&
|
||||
git log -n 20 >> release.info;
|
||||
git log -n 20 >> release.info || exit 1
|
||||
|
||||
if [[ "$4" == "" ]]; then
|
||||
# Non GH, local building
|
||||
@@ -97,7 +97,7 @@ else
|
||||
# GH Action, runner
|
||||
if [[ "$(git rev-parse --abbrev-ref HEAD)" == "release" ]]; then
|
||||
echo "building on the release branch... placing it in builds/candidate"
|
||||
mkdir /data/builds/candidate
|
||||
mkdir -p /data/builds/candidate
|
||||
cp /io/release-build/xahaud /data/builds/candidate/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
|
||||
cp /io/release-build/release.info /data/builds/candidate/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4.releaseinfo
|
||||
else
|
||||
|
||||
@@ -166,10 +166,13 @@
|
||||
# ws Websockets
|
||||
# wss Secure Websockets
|
||||
# peer Peer Protocol
|
||||
# pwa On-ledger AppLoader documents (featurePWALoader), for a
|
||||
# reverse proxy only. Read "pwa ports" below first.
|
||||
#
|
||||
# Restrictions:
|
||||
#
|
||||
# Only one port may be configured to support the peer protocol.
|
||||
# The pwa protocol cannot be combined with any other protocol.
|
||||
# A port cannot have websocket and non websocket protocols at the
|
||||
# same time. It is possible have both Websockets and Secure Websockets
|
||||
# together in one port.
|
||||
@@ -264,6 +267,119 @@
|
||||
# "secure_gateway" connections, then they will be treated as
|
||||
# "admin" addresses.
|
||||
#
|
||||
# pwa ports
|
||||
#
|
||||
# A port whose protocol is "pwa" serves accounts' on-ledger AppLoader
|
||||
# documents (featurePWALoader) as HTML. This makes the node a web host
|
||||
# for third-party content: anyone can publish a loader to an account
|
||||
# for about a drop per byte, and nothing in protocol takes one down.
|
||||
# Hosting, and a hosting provider's obligations, are yours.
|
||||
#
|
||||
# One host per account, on a zone used for nothing else:
|
||||
#
|
||||
# https://pwa<account-index>.<domain>/<r-address>
|
||||
# https://<domain>/<r-address> 301 to the above
|
||||
# https://<custom host>/ see custom_domains
|
||||
#
|
||||
# The first label of the Host header is "pwa" followed by the decimal
|
||||
# AccountIndex of the account (an account created before AccountIndex
|
||||
# existed has no host); the rest must be one of the port's "domain"
|
||||
# values; the path must be that account's exact r-address. One
|
||||
# trailing slash and a query string are ignored; anything else is
|
||||
# 404. The bare zone never serves a document (every page would share
|
||||
# one origin): it redirects, keeping :port and query.
|
||||
# Never set cookies at or above the zone and never serve your own
|
||||
# site from it. Wildcard DNS and a wildcard certificate cover it.
|
||||
#
|
||||
# Rules enforced at startup and on every request:
|
||||
#
|
||||
# - "pwa" is the port's only protocol; no admin, user/password or
|
||||
# ssl_* keys (terminate TLS at the proxy).
|
||||
# - "domain" is required: the zone(s), comma-separated. The proxy must
|
||||
# forward the Host header unchanged.
|
||||
# - "secure_gateway" is required and may not cover every address.
|
||||
# Every request must come from it carrying X-Forwarded-For, whose
|
||||
# RIGHTMOST entry (the one the proxy wrote) is the client. The
|
||||
# "Forwarded" header is ignored.
|
||||
# - Clients are metered per IPv4 address, per IPv6 /64; a miss costs
|
||||
# what a hit does; over-use is answered 503 until it decays.
|
||||
# - GET and HEAD only; every response closes the connection; only
|
||||
# validated state is served, with an ETag (304 on If-None-Match).
|
||||
# X-NetworkID, X-LedgerSeq, X-LedgerHash and X-PWASeq name the
|
||||
# validated ledger and AppLoaderSequence the document came from, so
|
||||
# a client (a browser extension, say) can vet it against a second
|
||||
# node with the pwa_validate RPC (account, hash of the bytes, host).
|
||||
# - Lookups run on at most two job queue threads, below client RPC;
|
||||
# beyond a bounded backlog they are refused with 503. Put a cache in
|
||||
# front (the ETag makes revalidation cheap).
|
||||
# - The admin RPC "pwa_info" reports requests by outcome and bytes
|
||||
# served; "reset": true zeroes them, "flush_dns": true drops the
|
||||
# custom domain cache (CLI: xahaud pwa_info [reset] [flush_dns]).
|
||||
#
|
||||
# Example (also add port_pwa to the [server] list):
|
||||
#
|
||||
# [port_pwa]
|
||||
# port = 8088
|
||||
# ip = 127.0.0.1
|
||||
# protocol = pwa
|
||||
# secure_gateway = 127.0.0.1
|
||||
# domain = pwapp.example
|
||||
#
|
||||
# with nginx in front, TLS terminated there, Host passed through:
|
||||
#
|
||||
# server {
|
||||
# listen 443 ssl;
|
||||
# server_name pwapp.example ~^pwa[0-9]+\.pwapp\.example$;
|
||||
# ssl_certificate /etc/ssl/wildcard.pwapp.example.pem;
|
||||
# ssl_certificate_key /etc/ssl/wildcard.pwapp.example.key;
|
||||
# location / {
|
||||
# proxy_pass http://127.0.0.1:8088;
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_set_header X-Forwarded-For $remote_addr;
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# custom_domains = 0 | 1
|
||||
#
|
||||
# Default 0. With 1, a request whose Host is outside every zone is
|
||||
# served as the account named by DNS, so a domain owner can point
|
||||
# their own name at your proxy:
|
||||
#
|
||||
# app.example.org. CNAME pwa-proxy.your.example.
|
||||
# _xahau-pwa.app.example.org. TXT "rAbC..."
|
||||
#
|
||||
# The record lives on the _xahau-pwa label beneath the host because
|
||||
# the host is usually a CNAME to your proxy. Exactly one address must
|
||||
# be present there (other TXT strings are ignored; two addresses count
|
||||
# as none). The named account must consent: its Domain field must
|
||||
# equal the host or a parent of it (ignoring case), else 503. Lists
|
||||
# apply as at the account's own host; only the root path is a
|
||||
# document. No usable record is 404 (cached 15 minutes), a resolver
|
||||
# failure 503 (cached 60 s), a record is cached for its TTL (clamped
|
||||
# to 60..3600 s). Lookups run on their own job queue threads, at most
|
||||
# four at a time and one per host; a request that misses the cache is
|
||||
# answered 429 with Retry-After and a refreshing page rather than
|
||||
# waiting, so a flood of Host headers cannot tie up the node. The
|
||||
# admin RPC pwa_info with "flush_dns": true drops the cache. Your
|
||||
# proxy must accept, and hold certificates for, the hosts you will
|
||||
# serve (e.g. Caddy on-demand TLS); xahaud only sees the Host header.
|
||||
#
|
||||
# resolver = <IPv4 address>[:port]
|
||||
#
|
||||
# The recursive resolver for custom_domains; default is the system
|
||||
# resolver. xahaud does not validate DNSSEC, so prefer a validating
|
||||
# resolver on the same host.
|
||||
#
|
||||
# Documents are served with "Content-Security-Policy: sandbox
|
||||
# allow-scripts ...": an opaque origin that cannot read anything from
|
||||
# the node but can still send requests anywhere, including the
|
||||
# visitor's own machine, so keep admin ports behind credentials.
|
||||
#
|
||||
# [pwa_allowlist] and [pwa_blocklist], one r-address per line, apply to
|
||||
# every pwa port: if the allowlist is non-empty only those accounts are
|
||||
# served; blocklisted accounts are never served (403), whatever the
|
||||
# allowlist says. An account on both is a startup error.
|
||||
#
|
||||
# ssl_key = <filename>
|
||||
# ssl_cert = <filename>
|
||||
# ssl_chain = <filename>
|
||||
@@ -448,6 +564,30 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
# [udp_highway]
|
||||
#
|
||||
# 0 or 1.
|
||||
#
|
||||
# 0: Disable the Xahau UDP Superhighway (XUSH). [default]
|
||||
# 1: Enable the Xahau UDP Superhighway (XUSH).
|
||||
#
|
||||
# The UDP Superhighway is a best-effort transport that runs alongside the
|
||||
# peer protocol. When it is enabled the server also listens for UDP
|
||||
# datagrams on its peer port number, and every transaction it relays to its
|
||||
# peers is also sent, over UDP, to a small random set of other servers that
|
||||
# have the highway enabled. It never replaces the peer protocol: anything
|
||||
# sent over the highway is also relayed normally.
|
||||
#
|
||||
# To receive highway traffic, inbound UDP must be allowed to the peer port
|
||||
# (in addition to inbound TCP).
|
||||
#
|
||||
# A server that keeps its address private (peer_private = 1, or any server
|
||||
# configured as a validator) only exchanges highway traffic with the peers
|
||||
# listed in [ips_fixed], and never advertises highway endpoints. Fixed peers
|
||||
# are never advertised to other servers.
|
||||
#
|
||||
#
|
||||
#
|
||||
# [peers_max]
|
||||
#
|
||||
# The largest number of desired peer connections (incoming or outgoing).
|
||||
@@ -674,6 +814,38 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
# [early_manifest_sync]
|
||||
#
|
||||
# Controls whether, while acquiring its first ledger, the server fetches and
|
||||
# reads the on-ledger validator manifests ahead of the rest of that ledger's
|
||||
# state. Doing so lets it resolve validators' current signing keys, including
|
||||
# rotations and revocations published on-ledger, within seconds of starting
|
||||
# a sync rather than once the whole state has arrived.
|
||||
#
|
||||
# The manifests read are those of the master keys on the server's own
|
||||
# validator lists, fetched by key, and as many more from the on-ledger
|
||||
# manifest directory as the manifest cache keeps.
|
||||
#
|
||||
# The cost is a dozen or so request/response rounds with peers. With more
|
||||
# than one peer serving the ledger these overlap with fetching the rest of
|
||||
# its state; with only one, they come first.
|
||||
#
|
||||
# It also lets a server that starts with nothing but the master keys of its
|
||||
# validators and a few peers find its way. Until it has validated a ledger,
|
||||
# while it holds manifests for too few of its validators to make a quorum,
|
||||
# it reads them from up to three of the ledgers its peers report, the most
|
||||
# widely reported first, every 15 seconds. Every manifest read is checked
|
||||
# against its master key's signature, so a ledger reported by a peer that
|
||||
# is not on the network can leave something out but cannot add anything.
|
||||
#
|
||||
# Legal values are:
|
||||
# 1 - Fetch the manifests first, and from peers' ledgers if needed
|
||||
# 0 - Fetch the ledger's state in the usual order, and read the manifests
|
||||
# from it once it is complete
|
||||
#
|
||||
# The default is 1. A ledger without on-ledger manifests costs only the few
|
||||
# nodes on the paths to where their directory and objects would be.
|
||||
#
|
||||
#
|
||||
#
|
||||
# [ledger_history]
|
||||
@@ -1416,6 +1588,19 @@
|
||||
# contains breaking changes that require a new API version number.
|
||||
# They are not ready for public consumption.
|
||||
#
|
||||
# [hook_validate_rpc]
|
||||
#
|
||||
# 0 or 1.
|
||||
#
|
||||
# 0: Disable the hook_validate RPC method
|
||||
# 1: Enable the hook_validate RPC method, which runs the Hook guard
|
||||
# checker and WASM VM validation against caller-supplied code using
|
||||
# the current open ledger's rules.
|
||||
#
|
||||
# The default is 0 on networked nodes and 1 in standalone mode (-a).
|
||||
# When enabled the method is available to non-admin (user role) clients,
|
||||
# so consider the CPU cost before enabling it on a public-facing node.
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
# 10. Example Settings
|
||||
|
||||
@@ -151,4 +151,3 @@ D686F2538F410C9D0D856788E98E3579595DAF7B38D38887F81ECAC934B06040 HooksUpdate1
|
||||
4B8466415FAB32FFA89D9DCBE166A42340115771DF611A7160F8D7439C87ECD8 fixNSDelete
|
||||
EDB4EE4C524E16BDD91D9A529332DED08DCAAA51CC6DC897ACFA1A0ED131C5B6 fix240819
|
||||
8063140E9260799D6716756B891CEC3E7006C4E4F277AB84670663A88F94B9C4 fixPageCap
|
||||
88693F108C3CD8A967F3F4253A32DEF5E35F9406ACD2A11B88B11D90865763A9 fix240911
|
||||
@@ -97,6 +97,7 @@ target_link_libraries (ripple_syslibs
|
||||
crypt32
|
||||
>
|
||||
$<$<NOT:$<BOOL:${MSVC}>>:dl>
|
||||
$<$<NOT:$<BOOL:${MSVC}>>:resolv>
|
||||
$<$<NOT:$<OR:$<BOOL:${MSVC}>,$<BOOL:${APPLE}>>>:rt>)
|
||||
|
||||
if (NOT MSVC)
|
||||
|
||||
@@ -124,7 +124,7 @@ option(beast_no_unit_test_inline
|
||||
"Prevents unit test definitions from being inserted into global table"
|
||||
OFF)
|
||||
option(single_io_service_thread
|
||||
"Restricts the number of threads calling io_service::run to one. \
|
||||
"Restricts the number of threads calling io_context::run to one. \
|
||||
This can be useful when debugging."
|
||||
OFF)
|
||||
option(boost_show_deprecated
|
||||
|
||||
@@ -32,9 +32,9 @@ target_link_libraries(ripple_boost
|
||||
Boost::date_time
|
||||
Boost::filesystem
|
||||
Boost::json
|
||||
Boost::process
|
||||
Boost::program_options
|
||||
Boost::regex
|
||||
Boost::system
|
||||
Boost::iostreams
|
||||
Boost::thread)
|
||||
if(Boost_COMPILER)
|
||||
|
||||
+129
-127
@@ -2,158 +2,160 @@ from conan import ConanFile
|
||||
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
|
||||
import re
|
||||
|
||||
class Xrpl(ConanFile):
|
||||
name = 'xrpl'
|
||||
|
||||
license = 'ISC'
|
||||
author = 'John Freeman <[email protected]>'
|
||||
url = 'https://github.com/xrplf/rippled'
|
||||
description = 'The XRP Ledger'
|
||||
settings = 'os', 'compiler', 'build_type', 'arch'
|
||||
class Xrpl(ConanFile):
|
||||
name = "xrpl"
|
||||
|
||||
license = "ISC"
|
||||
author = "John Freeman <[email protected]>"
|
||||
url = "https://github.com/xrplf/rippled"
|
||||
description = "The XRP Ledger"
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
options = {
|
||||
'assertions': [True, False],
|
||||
'coverage': [True, False],
|
||||
'fPIC': [True, False],
|
||||
'jemalloc': [True, False],
|
||||
'rocksdb': [True, False],
|
||||
'shared': [True, False],
|
||||
'static': [True, False],
|
||||
'tests': [True, False],
|
||||
'unity': [True, False],
|
||||
'xrpld': [True, False],
|
||||
'with_wasmedge': [True, False],
|
||||
'tool_requires_b2': [True, False],
|
||||
"assertions": [True, False],
|
||||
"coverage": [True, False],
|
||||
"fPIC": [True, False],
|
||||
"jemalloc": [True, False],
|
||||
"rocksdb": [True, False],
|
||||
"shared": [True, False],
|
||||
"static": [True, False],
|
||||
"tests": [True, False],
|
||||
"unity": [True, False],
|
||||
"xrpld": [True, False],
|
||||
"with_wasmedge": [True, False],
|
||||
"tool_requires_b2": [True, False],
|
||||
}
|
||||
|
||||
requires = [
|
||||
'date/3.0.3',
|
||||
'grpc/1.50.1',
|
||||
'libarchive/3.7.6',
|
||||
'magic_enum/0.9.5',
|
||||
'nudb/2.0.8',
|
||||
'openssl/3.6.0',
|
||||
'soci/4.0.3@xahaud/stable',
|
||||
'xxhash/0.8.2',
|
||||
'zlib/1.3.1',
|
||||
"date/3.0.3",
|
||||
"grpc/1.50.1",
|
||||
"libarchive/3.7.6",
|
||||
"magic_enum/0.9.5",
|
||||
"nudb/2.0.9",
|
||||
"openssl/3.6.0",
|
||||
"soci/4.0.3@xahaud/stable",
|
||||
"xxhash/0.8.2",
|
||||
"zlib/1.3.1",
|
||||
]
|
||||
|
||||
tool_requires = [
|
||||
'protobuf/3.21.12',
|
||||
"protobuf/3.21.12",
|
||||
]
|
||||
|
||||
default_options = {
|
||||
'assertions': False,
|
||||
'coverage': False,
|
||||
'fPIC': True,
|
||||
'jemalloc': False,
|
||||
'rocksdb': True,
|
||||
'shared': False,
|
||||
'static': True,
|
||||
'tests': False,
|
||||
'unity': False,
|
||||
'xrpld': False,
|
||||
'with_wasmedge': True,
|
||||
'tool_requires_b2': False,
|
||||
|
||||
'date/*:header_only': False,
|
||||
'grpc/*:shared': False,
|
||||
'grpc/*:secure': True,
|
||||
'libarchive/*:shared': False,
|
||||
'libarchive/*:with_acl': False,
|
||||
'libarchive/*:with_bzip2': False,
|
||||
'libarchive/*:with_cng': False,
|
||||
'libarchive/*:with_expat': False,
|
||||
'libarchive/*:with_iconv': False,
|
||||
'libarchive/*:with_libxml2': False,
|
||||
'libarchive/*:with_lz4': True,
|
||||
'libarchive/*:with_lzma': False,
|
||||
'libarchive/*:with_lzo': False,
|
||||
'libarchive/*:with_nettle': False,
|
||||
'libarchive/*:with_openssl': False,
|
||||
'libarchive/*:with_pcreposix': False,
|
||||
'libarchive/*:with_xattr': False,
|
||||
'libarchive/*:with_zlib': False,
|
||||
'lz4/*:shared': False,
|
||||
'openssl/*:shared': False,
|
||||
'protobuf/*:shared': False,
|
||||
'protobuf/*:with_zlib': True,
|
||||
'rocksdb/*:enable_sse': False,
|
||||
'rocksdb/*:lite': False,
|
||||
'rocksdb/*:shared': False,
|
||||
'rocksdb/*:use_rtti': True,
|
||||
'rocksdb/*:with_jemalloc': False,
|
||||
'rocksdb/*:with_lz4': True,
|
||||
'rocksdb/*:with_snappy': True,
|
||||
'snappy/*:shared': False,
|
||||
'soci/*:shared': False,
|
||||
'soci/*:with_sqlite3': True,
|
||||
'soci/*:with_boost': True,
|
||||
'xxhash/*:shared': False,
|
||||
"assertions": False,
|
||||
"coverage": False,
|
||||
"fPIC": True,
|
||||
"jemalloc": False,
|
||||
"rocksdb": True,
|
||||
"shared": False,
|
||||
"static": True,
|
||||
"tests": False,
|
||||
"unity": False,
|
||||
"xrpld": False,
|
||||
"with_wasmedge": True,
|
||||
"tool_requires_b2": False,
|
||||
"boost/*:without_cobalt": True,
|
||||
"date/*:header_only": False,
|
||||
"grpc/*:shared": False,
|
||||
"grpc/*:secure": True,
|
||||
"libarchive/*:shared": False,
|
||||
"libarchive/*:with_acl": False,
|
||||
"libarchive/*:with_bzip2": False,
|
||||
"libarchive/*:with_cng": False,
|
||||
"libarchive/*:with_expat": False,
|
||||
"libarchive/*:with_iconv": False,
|
||||
"libarchive/*:with_libxml2": False,
|
||||
"libarchive/*:with_lz4": True,
|
||||
"libarchive/*:with_lzma": False,
|
||||
"libarchive/*:with_lzo": False,
|
||||
"libarchive/*:with_nettle": False,
|
||||
"libarchive/*:with_openssl": False,
|
||||
"libarchive/*:with_pcreposix": False,
|
||||
"libarchive/*:with_xattr": False,
|
||||
"libarchive/*:with_zlib": False,
|
||||
"lz4/*:shared": False,
|
||||
"openssl/*:shared": False,
|
||||
"protobuf/*:shared": False,
|
||||
"protobuf/*:with_zlib": True,
|
||||
"rocksdb/*:enable_sse": False,
|
||||
"rocksdb/*:lite": False,
|
||||
"rocksdb/*:shared": False,
|
||||
"rocksdb/*:use_rtti": True,
|
||||
"rocksdb/*:with_jemalloc": False,
|
||||
"rocksdb/*:with_lz4": True,
|
||||
"rocksdb/*:with_snappy": True,
|
||||
"snappy/*:shared": False,
|
||||
"soci/*:shared": False,
|
||||
"soci/*:with_sqlite3": True,
|
||||
"soci/*:with_boost": True,
|
||||
"xxhash/*:shared": False,
|
||||
}
|
||||
|
||||
def set_version(self):
|
||||
path = f'{self.recipe_folder}/src/libxrpl/protocol/BuildInfo.cpp'
|
||||
regex = r'versionString\s?=\s?\"(.*)\"'
|
||||
with open(path, 'r') as file:
|
||||
path = f"{self.recipe_folder}/src/libxrpl/protocol/BuildInfo.cpp"
|
||||
regex = r"versionString\s?=\s?\"(.*)\""
|
||||
with open(path, "r") as file:
|
||||
matches = (re.search(regex, line) for line in file)
|
||||
match = next(m for m in matches if m)
|
||||
self.version = match.group(1)
|
||||
|
||||
def build_requirements(self):
|
||||
# These provide build tools (protoc, grpc plugins) that run during build
|
||||
self.tool_requires('grpc/1.50.1')
|
||||
self.tool_requires("grpc/1.50.1")
|
||||
# Explicitly require b2 (e.g. for building from source for glibc compatibility)
|
||||
if self.options.tool_requires_b2:
|
||||
self.tool_requires('b2/5.3.2')
|
||||
self.tool_requires("b2/5.3.2")
|
||||
|
||||
def configure(self):
|
||||
if self.settings.compiler == 'apple-clang':
|
||||
self.options['boost/*'].visibility = 'global'
|
||||
if self.settings.compiler == "apple-clang":
|
||||
self.options["boost/*"].visibility = "global"
|
||||
|
||||
def requirements(self):
|
||||
# Force sqlite3 version to avoid conflicts with soci
|
||||
self.requires('sqlite3/3.47.0', override=True)
|
||||
self.requires("sqlite3/3.47.0", override=True)
|
||||
# Force our custom snappy build for all dependencies
|
||||
self.requires('snappy/1.1.10@xahaud/stable', override=True)
|
||||
self.requires("snappy/1.1.10@xahaud/stable", override=True)
|
||||
# Force boost version for all dependencies to avoid conflicts
|
||||
self.requires('boost/1.86.0', override=True)
|
||||
self.requires('lz4/1.10.0', force=True)
|
||||
self.requires("boost/1.91.0", override=True)
|
||||
self.requires("lz4/1.10.0", force=True)
|
||||
|
||||
if self.options.with_wasmedge:
|
||||
self.requires('wasmedge/0.11.2@xahaud/stable')
|
||||
self.requires("wasmedge/0.11.2@xahaud/stable")
|
||||
if self.options.jemalloc:
|
||||
self.requires('jemalloc/5.3.0')
|
||||
self.requires("jemalloc/5.3.0")
|
||||
if self.options.rocksdb:
|
||||
self.requires('rocksdb/6.29.5')
|
||||
self.requires("rocksdb/6.29.5")
|
||||
|
||||
exports_sources = (
|
||||
'CMakeLists.txt',
|
||||
'bin/getRippledInfo',
|
||||
'cfg/*',
|
||||
'cmake/*',
|
||||
'external/*',
|
||||
'include/*',
|
||||
'src/*',
|
||||
"CMakeLists.txt",
|
||||
"bin/getRippledInfo",
|
||||
"cfg/*",
|
||||
"cmake/*",
|
||||
"external/*",
|
||||
"include/*",
|
||||
"src/*",
|
||||
)
|
||||
|
||||
def layout(self):
|
||||
cmake_layout(self)
|
||||
# Fix this setting to follow the default introduced in Conan 1.48
|
||||
# to align with our build instructions.
|
||||
self.folders.generators = 'build/generators'
|
||||
self.folders.generators = "build/generators"
|
||||
|
||||
generators = "CMakeDeps"
|
||||
|
||||
generators = 'CMakeDeps'
|
||||
def generate(self):
|
||||
tc = CMakeToolchain(self)
|
||||
tc.variables['tests'] = self.options.tests
|
||||
tc.variables['assert'] = self.options.assertions
|
||||
tc.variables['coverage'] = self.options.coverage
|
||||
tc.variables['jemalloc'] = self.options.jemalloc
|
||||
tc.variables['rocksdb'] = self.options.rocksdb
|
||||
tc.variables['BUILD_SHARED_LIBS'] = self.options.shared
|
||||
tc.variables['static'] = self.options.static
|
||||
tc.variables['unity'] = self.options.unity
|
||||
tc.variables['xrpld'] = self.options.xrpld
|
||||
tc.variables["tests"] = self.options.tests
|
||||
tc.variables["assert"] = self.options.assertions
|
||||
tc.variables["coverage"] = self.options.coverage
|
||||
tc.variables["jemalloc"] = self.options.jemalloc
|
||||
tc.variables["rocksdb"] = self.options.rocksdb
|
||||
tc.variables["BUILD_SHARED_LIBS"] = self.options.shared
|
||||
tc.variables["static"] = self.options.static
|
||||
tc.variables["unity"] = self.options.unity
|
||||
tc.variables["xrpld"] = self.options.xrpld
|
||||
tc.generate()
|
||||
|
||||
def build(self):
|
||||
@@ -168,29 +170,29 @@ class Xrpl(ConanFile):
|
||||
cmake.install()
|
||||
|
||||
def package_info(self):
|
||||
libxrpl = self.cpp_info.components['libxrpl']
|
||||
libxrpl = self.cpp_info.components["libxrpl"]
|
||||
libxrpl.libs = [
|
||||
'xrpl',
|
||||
'xrpl.libpb',
|
||||
'ed25519',
|
||||
'secp256k1',
|
||||
"xrpl",
|
||||
"xrpl.libpb",
|
||||
"ed25519",
|
||||
"secp256k1",
|
||||
]
|
||||
# TODO: Fix the protobufs to include each other relative to
|
||||
# `include/`, not `include/ripple/proto/`.
|
||||
libxrpl.includedirs = ['include', 'include/ripple/proto']
|
||||
libxrpl.includedirs = ["include", "include/ripple/proto"]
|
||||
libxrpl.requires = [
|
||||
'boost::boost',
|
||||
'date::date',
|
||||
'grpc::grpc++',
|
||||
'libarchive::libarchive',
|
||||
'lz4::lz4',
|
||||
'nudb::nudb',
|
||||
'openssl::crypto',
|
||||
'protobuf::libprotobuf',
|
||||
'soci::soci',
|
||||
'sqlite3::sqlite',
|
||||
'xxhash::xxhash',
|
||||
'zlib::zlib',
|
||||
"boost::boost",
|
||||
"date::date",
|
||||
"grpc::grpc++",
|
||||
"libarchive::libarchive",
|
||||
"lz4::lz4",
|
||||
"nudb::nudb",
|
||||
"openssl::crypto",
|
||||
"protobuf::libprotobuf",
|
||||
"soci::soci",
|
||||
"sqlite3::sqlite",
|
||||
"xxhash::xxhash",
|
||||
"zlib::zlib",
|
||||
]
|
||||
if self.options.rocksdb:
|
||||
libxrpl.requires.append('rocksdb::librocksdb')
|
||||
libxrpl.requires.append("rocksdb::librocksdb")
|
||||
@@ -47,5 +47,6 @@
|
||||
#define MEM_OVERLAP -43
|
||||
#define TOO_MANY_STATE_MODIFICATIONS -44
|
||||
#define TOO_MANY_NAMESPACES -45
|
||||
#define TOO_LITTLE_ENTROPY -46
|
||||
#define HOOK_ERROR_CODES
|
||||
#endif //HOOK_ERROR_CODES
|
||||
@@ -339,6 +339,12 @@ prepare(
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
|
||||
extern int64_t
|
||||
dice(uint32_t sides);
|
||||
|
||||
extern int64_t
|
||||
util_random(uint32_t write_ptr, uint32_t write_len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+23
-23
@@ -22,29 +22,29 @@ APPLY_HOOK="$SCRIPT_DIR/../include/xrpl/hook/hook_api.macro"
|
||||
return s;
|
||||
}
|
||||
|
||||
{
|
||||
line = $0;
|
||||
|
||||
# Skip block comments
|
||||
if (line ~ /\/\*/) {
|
||||
next;
|
||||
}
|
||||
|
||||
# Look for comment lines that start with // and contain function signature
|
||||
if (line ~ /^[[:space:]]*\/\/[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*\(/) {
|
||||
# Remove leading // and trim
|
||||
sub(/^[[:space:]]*\/\/[[:space:]]*/, "", line);
|
||||
line = trim(line);
|
||||
|
||||
# Check if function name is "_g" to add attribute
|
||||
if (line ~ /[[:space:]]+_g[[:space:]]*\(/) {
|
||||
# Insert __attribute__((noduplicate)) before _g
|
||||
sub(/[[:space:]]+_g/, " __attribute__((noduplicate)) _g", line);
|
||||
}
|
||||
|
||||
# printf("\n");
|
||||
|
||||
printf("extern %s\n\n", line);
|
||||
# Each entry is HOOK_API_DEFINITION(RET, NAME, (PARAMS), AMENDMENT),
|
||||
# possibly spread over several lines. Collect until the parentheses
|
||||
# balance, then split on the first two commas; the parameter tuple
|
||||
# is the first parenthesised group.
|
||||
function emit(s, i, ret, name, p, q, params) {
|
||||
sub(/^[[:space:]]*HOOK_API_DEFINITION[[:space:]]*\(/, "", s);
|
||||
gsub(/[[:space:]]+/, " ", s);
|
||||
i = index(s, ","); ret = trim(substr(s, 1, i - 1)); s = substr(s, i + 1);
|
||||
i = index(s, ","); name = trim(substr(s, 1, i - 1)); s = substr(s, i + 1);
|
||||
p = index(s, "("); q = index(s, ")");
|
||||
params = trim(substr(s, p + 1, q - p - 1));
|
||||
if (name == "_g")
|
||||
name = "__attribute__((noduplicate)) _g";
|
||||
printf("extern %s %s(%s);\n\n", ret, name, params);
|
||||
}
|
||||
|
||||
/^[[:space:]]*HOOK_API_DEFINITION[[:space:]]*\(/ { buf = ""; collecting = 1; }
|
||||
|
||||
collecting {
|
||||
buf = buf " " $0;
|
||||
if (gsub(/\(/, "(", buf) == gsub(/\)/, ")", buf)) {
|
||||
collecting = 0;
|
||||
emit(buf);
|
||||
}
|
||||
}
|
||||
' "$APPLY_HOOK"
|
||||
|
||||
+1
-1
@@ -46,8 +46,8 @@
|
||||
|
||||
#include "error.h"
|
||||
#include "extern.h"
|
||||
#include "sfcodes.h"
|
||||
#include "macro.h"
|
||||
#include "sfcodes.h"
|
||||
#include "tts.h"
|
||||
|
||||
#include "ls_flags.h"
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#define sfHookApiVersion ((1U << 16U) + 20U)
|
||||
#define sfHookStateScale ((1U << 16U) + 21U)
|
||||
#define sfLedgerFixType ((1U << 16U) + 22U)
|
||||
#define sfEntropyCount ((1U << 16U) + 99U)
|
||||
#define sfEntropyDenominator ((1U << 16U) + 100U)
|
||||
#define sfNetworkID ((2U << 16U) + 1U)
|
||||
#define sfFlags ((2U << 16U) + 2U)
|
||||
#define sfSourceTag ((2U << 16U) + 3U)
|
||||
@@ -80,6 +82,7 @@
|
||||
#define sfRewardTime ((2U << 16U) + 98U)
|
||||
#define sfRewardLgrFirst ((2U << 16U) + 99U)
|
||||
#define sfRewardLgrLast ((2U << 16U) + 100U)
|
||||
#define sfAppLoaderSequence ((2U << 16U) + 101U)
|
||||
#define sfIndexNext ((3U << 16U) + 1U)
|
||||
#define sfIndexPrevious ((3U << 16U) + 2U)
|
||||
#define sfBookNode ((3U << 16U) + 3U)
|
||||
@@ -152,6 +155,8 @@
|
||||
#define sfEscrowID ((5U << 16U) + 35U)
|
||||
#define sfURITokenID ((5U << 16U) + 36U)
|
||||
#define sfDomainID ((5U << 16U) + 37U)
|
||||
#define sfNextRandomDigest ((5U << 16U) + 89U)
|
||||
#define sfRandomData ((5U << 16U) + 90U)
|
||||
#define sfManifestID ((5U << 16U) + 91U)
|
||||
#define sfHookOnOutgoing ((5U << 16U) + 93U)
|
||||
#define sfHookOnIncoming ((5U << 16U) + 94U)
|
||||
@@ -222,6 +227,7 @@
|
||||
#define sfProvider ((7U << 16U) + 30U)
|
||||
#define sfMPTokenMetadata ((7U << 16U) + 31U)
|
||||
#define sfCredentialType ((7U << 16U) + 32U)
|
||||
#define sfAppLoader ((7U << 16U) + 96U)
|
||||
#define sfHookName ((7U << 16U) + 97U)
|
||||
#define sfRemarkValue ((7U << 16U) + 98U)
|
||||
#define sfRemarkName ((7U << 16U) + 99U)
|
||||
@@ -289,6 +295,7 @@
|
||||
#define sfXChainCreateAccountAttestationCollectionElement ((14U << 16U) + 31U)
|
||||
#define sfPriceData ((14U << 16U) + 32U)
|
||||
#define sfCredential ((14U << 16U) + 33U)
|
||||
#define sfRandomDigestEntry ((14U << 16U) + 88U)
|
||||
#define sfManifest ((14U << 16U) + 90U)
|
||||
#define sfAmountEntry ((14U << 16U) + 91U)
|
||||
#define sfMintURIToken ((14U << 16U) + 92U)
|
||||
@@ -321,6 +328,7 @@
|
||||
#define sfAuthorizeCredentials ((15U << 16U) + 26U)
|
||||
#define sfUnauthorizeCredentials ((15U << 16U) + 27U)
|
||||
#define sfAcceptedCredentials ((15U << 16U) + 28U)
|
||||
#define sfRandomDigests ((15U << 16U) + 91U)
|
||||
#define sfAmounts ((15U << 16U) + 92U)
|
||||
#define sfHookEmissions ((15U << 16U) + 93U)
|
||||
#define sfImportVLKeys ((15U << 16U) + 94U)
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
#define ttNFTOKEN_MODIFY 70
|
||||
#define ttPERMISSIONED_DOMAIN_SET 71
|
||||
#define ttPERMISSIONED_DOMAIN_DELETE 72
|
||||
#define ttENTROPY 89
|
||||
#define ttMANIFEST_SET 91
|
||||
#define ttCRON 92
|
||||
#define ttCRON_SET 93
|
||||
|
||||
@@ -1,75 +1,33 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or 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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED
|
||||
#define RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED
|
||||
|
||||
#include <xrpl/beast/type_name.h>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** Manages all counted object types. */
|
||||
class CountedObjects
|
||||
{
|
||||
public:
|
||||
static CountedObjects&
|
||||
getInstance() noexcept;
|
||||
|
||||
using Entry = std::pair<std::string, int>;
|
||||
using List = std::vector<Entry>;
|
||||
|
||||
List
|
||||
getCounts(int minimumThreshold) const;
|
||||
|
||||
public:
|
||||
/** Implementation for @ref CountedObject.
|
||||
|
||||
@internal
|
||||
*/
|
||||
class Counter
|
||||
{
|
||||
public:
|
||||
Counter(std::string name) noexcept : name_(std::move(name)), count_(0)
|
||||
{
|
||||
// Insert ourselves at the front of the lock-free linked list
|
||||
CountedObjects& instance = CountedObjects::getInstance();
|
||||
Counter* head;
|
||||
|
||||
do
|
||||
{
|
||||
head = instance.m_head.load();
|
||||
next_ = head;
|
||||
} while (instance.m_head.exchange(this) != head);
|
||||
|
||||
++instance.m_count;
|
||||
}
|
||||
|
||||
~Counter() noexcept = default;
|
||||
Counter(std::string name) noexcept;
|
||||
|
||||
int
|
||||
increment() noexcept
|
||||
{
|
||||
return ++count_;
|
||||
auto const newCount = ++count_;
|
||||
|
||||
if (auto maxCount = maxCount_.load(); newCount > maxCount)
|
||||
maxCount_.compare_exchange_strong(maxCount, newCount);
|
||||
|
||||
return newCount;
|
||||
}
|
||||
|
||||
int
|
||||
@@ -79,78 +37,161 @@ public:
|
||||
}
|
||||
|
||||
int
|
||||
getCount() const noexcept
|
||||
count() const noexcept
|
||||
{
|
||||
return count_.load();
|
||||
}
|
||||
|
||||
Counter*
|
||||
getNext() const noexcept
|
||||
int
|
||||
max() const noexcept
|
||||
{
|
||||
return next_;
|
||||
return std::max(count_.load(), maxCount_.load());
|
||||
}
|
||||
|
||||
std::string const&
|
||||
getName() const noexcept
|
||||
name() const noexcept
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string const name_;
|
||||
std::atomic<int> count_;
|
||||
friend class CountedObjects;
|
||||
|
||||
Counter* next_;
|
||||
std::atomic<std::uint32_t> count_ = 0;
|
||||
std::atomic<std::uint32_t> maxCount_ = 0;
|
||||
std::string const name_;
|
||||
};
|
||||
|
||||
private:
|
||||
CountedObjects() noexcept;
|
||||
~CountedObjects() noexcept = default;
|
||||
class Iterator
|
||||
{
|
||||
public:
|
||||
using value_type = Counter const;
|
||||
using reference = value_type&;
|
||||
using pointer = value_type*;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
explicit Iterator(Counter* c = nullptr) noexcept : current_(c)
|
||||
{
|
||||
}
|
||||
|
||||
reference
|
||||
operator*() const noexcept
|
||||
{
|
||||
return *current_;
|
||||
}
|
||||
pointer
|
||||
operator->() const noexcept
|
||||
{
|
||||
return current_;
|
||||
}
|
||||
|
||||
Iterator&
|
||||
operator++() noexcept
|
||||
{
|
||||
current_ = current_->next_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator
|
||||
operator++(int) noexcept
|
||||
{
|
||||
auto tmp = *this;
|
||||
++*this;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(Iterator const&) const noexcept = default;
|
||||
|
||||
private:
|
||||
Counter* current_;
|
||||
};
|
||||
|
||||
using List = std::vector<std::pair<std::string, int>>;
|
||||
|
||||
constexpr CountedObjects() noexcept = default;
|
||||
|
||||
static CountedObjects&
|
||||
getInstance() noexcept;
|
||||
|
||||
/** Returns the name and count of every counter with at least
|
||||
`minimumThreshold` live instances. */
|
||||
List
|
||||
getCounts(int minimumThreshold) const
|
||||
{
|
||||
List ret;
|
||||
|
||||
for (auto c = head_.load(); c != nullptr; c = c->next_)
|
||||
if (c->count() >= minimumThreshold)
|
||||
ret.emplace_back(c->name(), c->count());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto
|
||||
begin() const noexcept
|
||||
{
|
||||
return Iterator{head_.load()};
|
||||
}
|
||||
auto
|
||||
end() const noexcept
|
||||
{
|
||||
return Iterator{};
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<int> m_count;
|
||||
std::atomic<Counter*> m_head;
|
||||
friend class Counter;
|
||||
|
||||
std::atomic<Counter*> head_ = nullptr;
|
||||
};
|
||||
|
||||
inline constinit CountedObjects countedObjects;
|
||||
|
||||
inline CountedObjects&
|
||||
CountedObjects::getInstance() noexcept
|
||||
{
|
||||
return countedObjects;
|
||||
}
|
||||
|
||||
inline CountedObjects::Counter::Counter(std::string name) noexcept
|
||||
: name_(std::move(name))
|
||||
{
|
||||
do
|
||||
next_ = countedObjects.head_.load();
|
||||
while (!countedObjects.head_.compare_exchange_weak(next_, this));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Tracks the number of instances of an object.
|
||||
|
||||
Derived classes have their instances counted automatically. This is used
|
||||
for reporting purposes.
|
||||
|
||||
@ingroup ripple_basics
|
||||
*/
|
||||
template <class Object>
|
||||
class CountedObject
|
||||
{
|
||||
private:
|
||||
static auto&
|
||||
getCounter() noexcept
|
||||
{
|
||||
static CountedObjects::Counter c{beast::type_name<Object>()};
|
||||
return c;
|
||||
}
|
||||
static CountedObjects::Counter counter_;
|
||||
|
||||
public:
|
||||
CountedObject() noexcept
|
||||
{
|
||||
getCounter().increment();
|
||||
counter_.increment();
|
||||
}
|
||||
|
||||
CountedObject(CountedObject const&) noexcept
|
||||
{
|
||||
getCounter().increment();
|
||||
counter_.increment();
|
||||
}
|
||||
|
||||
CountedObject&
|
||||
operator=(CountedObject const&) noexcept = default;
|
||||
|
||||
~CountedObject() noexcept
|
||||
{
|
||||
getCounter().decrement();
|
||||
counter_.decrement();
|
||||
}
|
||||
};
|
||||
|
||||
// Instantiation of the static CountedObject<T>::counter_
|
||||
template <class Object>
|
||||
CountedObjects::Counter CountedObject<Object>::counter_{
|
||||
beast::type_name<Object>()};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -23,9 +23,66 @@
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** Add two signed 64-bit integers, returning std::nullopt when the exact
|
||||
* mathematical sum is not representable in std::int64_t.
|
||||
*/
|
||||
[[nodiscard]] constexpr std::optional<std::int64_t>
|
||||
checkedAdd(std::int64_t a, std::int64_t b) noexcept
|
||||
{
|
||||
using L = std::numeric_limits<std::int64_t>;
|
||||
|
||||
if ((b > 0 && a > L::max() - b) || (b < 0 && a < L::min() - b))
|
||||
return std::nullopt;
|
||||
|
||||
return a + b;
|
||||
}
|
||||
|
||||
/** Subtract two signed 64-bit integers, returning std::nullopt when the exact
|
||||
* mathematical difference is not representable in std::int64_t.
|
||||
*/
|
||||
[[nodiscard]] constexpr std::optional<std::int64_t>
|
||||
checkedSub(std::int64_t a, std::int64_t b) noexcept
|
||||
{
|
||||
using L = std::numeric_limits<std::int64_t>;
|
||||
|
||||
if ((b > 0 && a < L::min() + b) || (b < 0 && a > L::max() + b))
|
||||
return std::nullopt;
|
||||
|
||||
return a - b;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
using i64_limits = std::numeric_limits<std::int64_t>;
|
||||
|
||||
static_assert(checkedAdd(0, 0) == 0);
|
||||
static_assert(checkedAdd(1, -1) == 0);
|
||||
static_assert(checkedAdd(-5, 2) == -3);
|
||||
static_assert(!checkedAdd(i64_limits::max(), 1).has_value());
|
||||
static_assert(!checkedAdd(i64_limits::min(), -1).has_value());
|
||||
static_assert(checkedAdd(i64_limits::max() - 1, 1) == i64_limits::max());
|
||||
static_assert(checkedAdd(i64_limits::min(), i64_limits::max()) == -1);
|
||||
static_assert(checkedAdd(i64_limits::max(), i64_limits::min()) == -1);
|
||||
static_assert(!checkedAdd(i64_limits::max(), i64_limits::max()).has_value());
|
||||
static_assert(!checkedAdd(i64_limits::min(), i64_limits::min()).has_value());
|
||||
|
||||
static_assert(checkedSub(0, 0) == 0);
|
||||
static_assert(checkedSub(1, 1) == 0);
|
||||
static_assert(checkedSub(-5, 2) == -7);
|
||||
static_assert(checkedSub(-5, -2) == -3);
|
||||
static_assert(!checkedSub(i64_limits::min(), 1).has_value());
|
||||
static_assert(!checkedSub(i64_limits::max(), -1).has_value());
|
||||
static_assert(checkedSub(i64_limits::min() + 1, 1) == i64_limits::min());
|
||||
static_assert(checkedSub(-1, i64_limits::max()) == i64_limits::min());
|
||||
static_assert(!checkedSub(i64_limits::max(), i64_limits::min()).has_value());
|
||||
static_assert(!checkedSub(i64_limits::min(), i64_limits::max()).has_value());
|
||||
} // namespace detail
|
||||
|
||||
/** Calculate one number divided by another number in percentage.
|
||||
* The result is rounded up to the next integer, and capped in the range [0,100]
|
||||
* E.g. calculatePercent(1, 100) = 1 because 1/100 = 0.010000
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
|
||||
#include <shared_mutex>
|
||||
|
||||
// On Linux (glibc), std::shared_mutex wraps pthread_rwlock_t initialised
|
||||
// with PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP. This means a
|
||||
// pending exclusive lock() blocks new shared (reader) acquisitions,
|
||||
// causing reader starvation when writers contend frequently.
|
||||
//
|
||||
// On macOS / ARM (libc++), std::shared_mutex is already reader-preferring,
|
||||
// so the same code behaves differently across platforms.
|
||||
//
|
||||
// This header provides ReaderPreferringSharedMutex:
|
||||
// - On Linux it wraps pthread_rwlock_t initialised with
|
||||
// PTHREAD_RWLOCK_PREFER_READER_NP, matching macOS semantics.
|
||||
// - On all other platforms it is a type alias for std::shared_mutex.
|
||||
//
|
||||
// The interface is identical to std::shared_mutex, so it works with
|
||||
// std::shared_lock and std::unique_lock.
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
#include <cerrno>
|
||||
#include <pthread.h>
|
||||
#include <system_error>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class ReaderPreferringSharedMutex
|
||||
{
|
||||
pthread_rwlock_t rwlock_;
|
||||
|
||||
public:
|
||||
ReaderPreferringSharedMutex()
|
||||
{
|
||||
pthread_rwlockattr_t attr;
|
||||
pthread_rwlockattr_init(&attr);
|
||||
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_READER_NP);
|
||||
int rc = pthread_rwlock_init(&rwlock_, &attr);
|
||||
pthread_rwlockattr_destroy(&attr);
|
||||
if (rc != 0)
|
||||
throw std::system_error(
|
||||
rc, std::system_category(), "pthread_rwlock_init");
|
||||
}
|
||||
|
||||
~ReaderPreferringSharedMutex()
|
||||
{
|
||||
pthread_rwlock_destroy(&rwlock_);
|
||||
}
|
||||
|
||||
ReaderPreferringSharedMutex(ReaderPreferringSharedMutex const&) = delete;
|
||||
ReaderPreferringSharedMutex&
|
||||
operator=(ReaderPreferringSharedMutex const&) = delete;
|
||||
|
||||
// Exclusive (writer) locking
|
||||
void
|
||||
lock()
|
||||
{
|
||||
pthread_rwlock_wrlock(&rwlock_);
|
||||
}
|
||||
|
||||
bool
|
||||
try_lock()
|
||||
{
|
||||
return pthread_rwlock_trywrlock(&rwlock_) == 0;
|
||||
}
|
||||
|
||||
void
|
||||
unlock()
|
||||
{
|
||||
pthread_rwlock_unlock(&rwlock_);
|
||||
}
|
||||
|
||||
// Shared (reader) locking
|
||||
void
|
||||
lock_shared()
|
||||
{
|
||||
pthread_rwlock_rdlock(&rwlock_);
|
||||
}
|
||||
|
||||
bool
|
||||
try_lock_shared()
|
||||
{
|
||||
return pthread_rwlock_tryrdlock(&rwlock_) == 0;
|
||||
}
|
||||
|
||||
void
|
||||
unlock_shared()
|
||||
{
|
||||
pthread_rwlock_unlock(&rwlock_);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#else // !__linux__
|
||||
|
||||
namespace ripple {
|
||||
|
||||
// macOS, Windows, etc. — std::shared_mutex is already reader-preferring.
|
||||
using ReaderPreferringSharedMutex = std::shared_mutex;
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -22,7 +22,8 @@
|
||||
|
||||
#include <xrpl/basics/Resolver.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -32,7 +33,7 @@ public:
|
||||
explicit ResolverAsio() = default;
|
||||
|
||||
static std::unique_ptr<ResolverAsio>
|
||||
New(boost::asio::io_service&, beast::Journal);
|
||||
New(boost::asio::io_context&, beast::Journal);
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -113,7 +113,7 @@ template <>
|
||||
inline std::size_t
|
||||
extract(SHAMapHash const& key)
|
||||
{
|
||||
return *reinterpret_cast<std::size_t const*>(key.as_uint256().data());
|
||||
return extract(key.as_uint256());
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
+528
-305
@@ -21,195 +21,443 @@
|
||||
#define RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
#include <xrpl/beast/type_name.h>
|
||||
#include <xrpl/basics/spinlock.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <boost/align.hpp>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <boost/predef.h>
|
||||
#include <boost/predef/os.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <bit>
|
||||
|
||||
#if BOOST_OS_LINUX
|
||||
#include <limits>
|
||||
#include <new>
|
||||
|
||||
#ifdef BOOST_OS_LINUX
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
namespace ripple {
|
||||
namespace slab {
|
||||
namespace detail {
|
||||
|
||||
template <typename Type>
|
||||
class SlabAllocator
|
||||
/// Alignment for hugepage support.
|
||||
inline constexpr std::size_t pageSize = megabytes(std::size_t(2));
|
||||
|
||||
/** Allocate a new 2MB-aligned data buffer.
|
||||
|
||||
@param size Size of the buffer to allocate in bytes.
|
||||
@return Pointer to allocated memory, or nullptr on failure.
|
||||
|
||||
@note On Linux, advises the kernel to back the allocation with
|
||||
transparent huge pages if available.
|
||||
*/
|
||||
[[nodiscard, gnu::malloc]] inline std::uint8_t*
|
||||
allocateBuffer(std::size_t size) noexcept
|
||||
{
|
||||
static_assert(
|
||||
sizeof(Type) >= sizeof(std::uint8_t*),
|
||||
"SlabAllocator: the requested object must be larger than a pointer.");
|
||||
auto ptr = reinterpret_cast<std::uint8_t*>(
|
||||
boost::alignment::aligned_alloc(pageSize, size));
|
||||
|
||||
static_assert(alignof(Type) == 8 || alignof(Type) == 4);
|
||||
#if BOOST_OS_LINUX
|
||||
if (ptr != nullptr) [[likely]]
|
||||
madvise(ptr, size, MADV_HUGEPAGE);
|
||||
#endif
|
||||
|
||||
/** A block of memory that is owned by a slab allocator */
|
||||
struct SlabBlock
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/** Deallocate a buffer previously allocated by allocateBuffer.
|
||||
|
||||
@param ptr Pointer to buffer, or nullptr (no-op).
|
||||
*/
|
||||
inline void
|
||||
deallocateBuffer(std::uint8_t* ptr) noexcept
|
||||
{
|
||||
boost::alignment::aligned_free(ptr);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** A block of memory metadata for slab allocators.
|
||||
|
||||
Each block manages a single slab buffer and its associated free list.
|
||||
Blocks are linked together to form a list of slabs per allocator.
|
||||
|
||||
@note slab_t instances are intentionally never destroyed or reclaimed
|
||||
once allocated, to support lock-free iteration of lists of slabs
|
||||
without introducing ABA hazards. The data buffer the class holds
|
||||
can be released independently.
|
||||
*/
|
||||
struct slab_t
|
||||
{
|
||||
/** A node in the intrusive free list.
|
||||
|
||||
Constructed in-place within free memory blocks. The next pointer
|
||||
is const because nodes are never modified after construction;
|
||||
they are simply constructed anew when reused.
|
||||
*/
|
||||
struct item_t
|
||||
{
|
||||
// A mutex to protect the freelist for this block:
|
||||
std::mutex m_;
|
||||
item_t* const next;
|
||||
|
||||
// A linked list of appropriately sized free buffers:
|
||||
std::uint8_t* l_ = nullptr;
|
||||
|
||||
// The next memory block
|
||||
SlabBlock* next_;
|
||||
|
||||
// The underlying memory block:
|
||||
std::uint8_t const* const p_ = nullptr;
|
||||
|
||||
// The extent of the underlying memory block:
|
||||
std::size_t const size_;
|
||||
|
||||
SlabBlock(
|
||||
SlabBlock* next,
|
||||
std::uint8_t* data,
|
||||
std::size_t size,
|
||||
std::size_t item)
|
||||
: next_(next), p_(data), size_(size)
|
||||
constexpr explicit item_t(item_t* n) noexcept : next(n)
|
||||
{
|
||||
// We don't need to grab the mutex here, since we're the only
|
||||
// ones with access at this moment.
|
||||
|
||||
while (data + item <= p_ + size_)
|
||||
{
|
||||
// Use memcpy to avoid unaligned UB
|
||||
// (will optimize to equivalent code)
|
||||
std::memcpy(data, &l_, sizeof(std::uint8_t*));
|
||||
l_ = data;
|
||||
data += item;
|
||||
}
|
||||
}
|
||||
|
||||
~SlabBlock()
|
||||
{
|
||||
// Calling this destructor will release the allocated memory but
|
||||
// will not properly destroy any objects that are constructed in
|
||||
// the block itself.
|
||||
}
|
||||
|
||||
SlabBlock(SlabBlock const& other) = delete;
|
||||
SlabBlock&
|
||||
operator=(SlabBlock const& other) = delete;
|
||||
|
||||
SlabBlock(SlabBlock&& other) = delete;
|
||||
SlabBlock&
|
||||
operator=(SlabBlock&& other) = delete;
|
||||
|
||||
/** Determines whether the given pointer belongs to this allocator */
|
||||
bool
|
||||
own(std::uint8_t const* p) const noexcept
|
||||
{
|
||||
return (p >= p_) && (p < p_ + size_);
|
||||
}
|
||||
|
||||
std::uint8_t*
|
||||
allocate() noexcept
|
||||
{
|
||||
std::uint8_t* ret;
|
||||
|
||||
{
|
||||
std::lock_guard l(m_);
|
||||
|
||||
ret = l_;
|
||||
|
||||
if (ret)
|
||||
{
|
||||
// Use memcpy to avoid unaligned UB
|
||||
// (will optimize to equivalent code)
|
||||
std::memcpy(&l_, ret, sizeof(std::uint8_t*));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Return an item to this allocator's freelist.
|
||||
|
||||
@param ptr The pointer to the chunk of memory being deallocated.
|
||||
|
||||
@note This is a dangerous, private interface; the item being
|
||||
returned should belong to this allocator. Debug builds
|
||||
will check and assert if this is not the case. Release
|
||||
builds will not.
|
||||
*/
|
||||
void
|
||||
deallocate(std::uint8_t* ptr) noexcept
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
own(ptr),
|
||||
"ripple::SlabAllocator::SlabBlock::deallocate : own input");
|
||||
|
||||
std::lock_guard l(m_);
|
||||
|
||||
// Use memcpy to avoid unaligned UB
|
||||
// (will optimize to equivalent code)
|
||||
std::memcpy(ptr, &l_, sizeof(std::uint8_t*));
|
||||
l_ = ptr;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
// A linked list of slabs
|
||||
std::atomic<SlabBlock*> slabs_ = nullptr;
|
||||
// We need this to be trivially destructible so that we do not
|
||||
// have to explicitly invoke the destructor when popping items
|
||||
// from the free list or when deallocating the slab buffer.
|
||||
static_assert(std::is_trivially_destructible_v<item_t>);
|
||||
|
||||
// The alignment requirements of the item we're allocating:
|
||||
std::size_t const itemAlignment_;
|
||||
item_t* head_ = nullptr;
|
||||
slab_t* const next_ = nullptr;
|
||||
std::atomic<std::uint8_t*> data_{nullptr};
|
||||
std::atomic<std::uint8_t> lock_{0};
|
||||
std::uint16_t cycles_ = 0;
|
||||
std::uint32_t outstanding_ = 0;
|
||||
|
||||
// The size of an item, including the extra bytes requested and
|
||||
// any padding needed for alignment purposes:
|
||||
std::size_t const itemSize_;
|
||||
/** Construct a block that is unlinked. */
|
||||
constexpr slab_t() noexcept = default;
|
||||
|
||||
// The size of each individual slab:
|
||||
std::size_t const slabSize_;
|
||||
/** Construct a block with a link to the next block in the chain.
|
||||
|
||||
@param next Pointer to the next block, or nullptr for the tail.
|
||||
*/
|
||||
constexpr explicit slab_t(slab_t* next) noexcept : next_(next)
|
||||
{
|
||||
}
|
||||
|
||||
~slab_t() = default;
|
||||
|
||||
slab_t(slab_t const&) = delete;
|
||||
slab_t&
|
||||
operator=(slab_t const&) = delete;
|
||||
slab_t(slab_t&&) = delete;
|
||||
slab_t&
|
||||
operator=(slab_t&&) = delete;
|
||||
|
||||
/** Attempt to acquire the block's spinlock.
|
||||
|
||||
@return true if lock was acquired, false if already held.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
try_lock() noexcept
|
||||
{
|
||||
return spin_try_lock(lock_);
|
||||
}
|
||||
|
||||
/** Acquire the block's spinlock, blocking until available. */
|
||||
void
|
||||
lock() noexcept
|
||||
{
|
||||
spin_lock(lock_);
|
||||
}
|
||||
|
||||
/** Release the block's spinlock. */
|
||||
void
|
||||
unlock() noexcept
|
||||
{
|
||||
spin_unlock(lock_);
|
||||
}
|
||||
|
||||
/** Attempt to assign a buffer to this block.
|
||||
|
||||
If the block has no buffer, takes ownership and initializes
|
||||
the free list.
|
||||
|
||||
@param buffer Pointer to the buffer to assign.
|
||||
@param slabSize Size of the buffer in bytes.
|
||||
@param itemSize Size of each item in bytes.
|
||||
@return Pointer to first item if assigned, nullptr if block already
|
||||
has a buffer.
|
||||
*/
|
||||
[[nodiscard]] std::uint8_t*
|
||||
try_assign(
|
||||
std::uint8_t* buffer,
|
||||
std::size_t slabSize,
|
||||
std::size_t itemSize) noexcept
|
||||
{
|
||||
std::uint8_t* expected = nullptr;
|
||||
|
||||
if (!data_.compare_exchange_strong(
|
||||
expected, buffer, std::memory_order::acquire))
|
||||
return nullptr;
|
||||
|
||||
spin_lock(lock_);
|
||||
|
||||
head_ = nullptr;
|
||||
outstanding_ = 1;
|
||||
|
||||
auto p = buffer;
|
||||
|
||||
for (std::size_t n = (slabSize / itemSize) - 1; n--; p += itemSize)
|
||||
head_ = std::construct_at(reinterpret_cast<item_t*>(p), head_);
|
||||
|
||||
spin_unlock(lock_);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/** Attempt to allocate from this block.
|
||||
|
||||
@return Pointer to allocated memory, or nullptr if block is empty.
|
||||
*/
|
||||
[[nodiscard]] std::uint8_t*
|
||||
try_allocate() noexcept
|
||||
{
|
||||
spin_lock(lock_);
|
||||
|
||||
auto ret = head_;
|
||||
|
||||
if (ret)
|
||||
{
|
||||
head_ = ret->next;
|
||||
++outstanding_;
|
||||
}
|
||||
|
||||
spin_unlock(lock_);
|
||||
|
||||
return reinterpret_cast<std::uint8_t*>(ret);
|
||||
}
|
||||
|
||||
/** Attempt to return a pointer to this block.
|
||||
|
||||
@param ptr Pointer to memory block.
|
||||
@param slabSize Size of the slab's data buffer.
|
||||
@param releaseBuffer Callback invoked with a pointer to the block's
|
||||
buffer so that it can be released.
|
||||
@return true if ptr belonged to this block and was freed,
|
||||
false otherwise.
|
||||
*/
|
||||
template <typename ReleaseFunc>
|
||||
[[nodiscard]] bool
|
||||
try_deallocate(
|
||||
std::uint8_t* ptr,
|
||||
std::size_t slabSize,
|
||||
ReleaseFunc&& releaseBuffer) noexcept
|
||||
{
|
||||
auto data = data_.load(std::memory_order::acquire);
|
||||
|
||||
if (!data || ptr < data || ptr >= data + slabSize)
|
||||
return false;
|
||||
|
||||
std::uint8_t* buf = nullptr;
|
||||
|
||||
spin_lock(lock_);
|
||||
|
||||
assert(ptr != nullptr);
|
||||
assert(outstanding_ > 0);
|
||||
|
||||
head_ = std::construct_at(reinterpret_cast<item_t*>(ptr), head_);
|
||||
|
||||
// If this block became empty and it is not the first block, we
|
||||
// release its buffer. The first block (at the tail of the list
|
||||
// with next_ == nullptr) is exempt to keep one block ready.
|
||||
if (--outstanding_ == 0 && next_ != nullptr)
|
||||
{
|
||||
head_ = nullptr;
|
||||
buf = data_.exchange(buf, std::memory_order::relaxed);
|
||||
cycles_ += (cycles_ < std::numeric_limits<std::uint16_t>::max());
|
||||
}
|
||||
|
||||
spin_unlock(lock_);
|
||||
|
||||
if (buf != nullptr)
|
||||
releaseBuffer(buf);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Global pool of slab_t objects shared by all slab allocators.
|
||||
|
||||
Provides a fixed-size array of pre-allocated slabs with fallback
|
||||
to heap allocation if exhausted.
|
||||
*/
|
||||
class slab_pool_t
|
||||
{
|
||||
std::array<slab_t, 32768> blocks_{};
|
||||
std::atomic<std::size_t> count_{0};
|
||||
|
||||
public:
|
||||
constexpr slab_pool_t() = default;
|
||||
|
||||
slab_pool_t(slab_pool_t const&) = delete;
|
||||
slab_pool_t&
|
||||
operator=(slab_pool_t const&) = delete;
|
||||
slab_pool_t(slab_pool_t&&) = delete;
|
||||
slab_pool_t&
|
||||
operator=(slab_pool_t&&) = delete;
|
||||
|
||||
/** Acquire a slab from the pool.
|
||||
|
||||
@param next Pointer to link as the slab's next pointer.
|
||||
@return Pointer to acquired block, or nullptr if allocation failed.
|
||||
*/
|
||||
[[nodiscard]] slab_t*
|
||||
acquire(slab_t* next) noexcept
|
||||
{
|
||||
auto idx = count_.load(std::memory_order::relaxed);
|
||||
|
||||
while (idx < blocks_.size())
|
||||
{
|
||||
if (count_.compare_exchange_weak(
|
||||
idx, idx + 1, std::memory_order::relaxed))
|
||||
{
|
||||
return std::construct_at(&blocks_[idx], next);
|
||||
}
|
||||
}
|
||||
|
||||
// Pool exhausted (unlikely!) so fall back to heap
|
||||
return new (std::nothrow) slab_t(next);
|
||||
}
|
||||
};
|
||||
|
||||
/// Single global block pool for all slab allocators.
|
||||
inline constinit slab_pool_t globalSlabPool;
|
||||
|
||||
} // namespace detail
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** A slab allocator for fixed-size memory blocks.
|
||||
|
||||
Allocates memory in large slabs (multiples of 2MB) and sub-allocates
|
||||
fixed-size blocks from them. Provides fast allocation with minimal
|
||||
fragmentation for objects of uniform size.
|
||||
|
||||
@tparam Type The type used to determine minimum block size and alignment.
|
||||
@tparam Align Alignment for allocated blocks (must be >= alignof(Type)).
|
||||
*/
|
||||
template <typename Type, std::size_t Align = alignof(Type)>
|
||||
requires(
|
||||
sizeof(Type) >= sizeof(void*) && Align >= alignof(Type) &&
|
||||
std::has_single_bit(Align))
|
||||
class sized_allocator_t
|
||||
{
|
||||
/// Linked list of slabs for this allocator.
|
||||
std::atomic<detail::slab_t*> slabs_{nullptr};
|
||||
|
||||
/// Cached buffer to avoid thrashing when oscillating around block
|
||||
/// boundaries.
|
||||
std::atomic<std::uint8_t*> cachedBuffer_{nullptr};
|
||||
|
||||
/// Spinlock to serialize the slow allocation path.
|
||||
std::atomic<std::uint8_t> blockLock_{0};
|
||||
|
||||
/// Item size (sizeof(Type) + extra, rounded up for alignment).
|
||||
std::size_t itemSize_;
|
||||
|
||||
/// Size of each slab's data buffer (multiple of 2 MB).
|
||||
std::size_t slabSize_;
|
||||
|
||||
/** Allocate a memory block from the existing slabs.
|
||||
|
||||
Fast path allocation that doesn't acquire the global lock.
|
||||
|
||||
@return Pointer to allocated memory, or nullptr if all slabs are full.
|
||||
*/
|
||||
[[nodiscard]] std::uint8_t*
|
||||
fast_allocate() noexcept
|
||||
{
|
||||
for (auto slab = slabs_.load(std::memory_order::acquire); slab;
|
||||
slab = slab->next_)
|
||||
{
|
||||
if (auto ret = slab->try_allocate())
|
||||
return ret;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/** Allocate a memory block, potentially creating new capacity.
|
||||
|
||||
Slow path allocation that may allocate a new buffer or slab.
|
||||
Must be called with blockLock_ held.
|
||||
|
||||
@return Pointer to allocated memory, or nullptr on failure.
|
||||
*/
|
||||
[[nodiscard]] std::uint8_t*
|
||||
slow_allocate() noexcept
|
||||
{
|
||||
if (auto ret = fast_allocate())
|
||||
return ret;
|
||||
|
||||
// Use a cached buffer first, if we have one, or allocate memory
|
||||
// from the operating system.
|
||||
auto buf = cachedBuffer_.exchange(nullptr, std::memory_order::acquire);
|
||||
|
||||
if (!buf)
|
||||
buf = detail::allocateBuffer(slabSize_);
|
||||
|
||||
if (!buf) [[unlikely]]
|
||||
return nullptr;
|
||||
|
||||
// Try to give buffer to an existing empty slab
|
||||
for (auto slab = slabs_.load(std::memory_order::acquire); slab;
|
||||
slab = slab->next_)
|
||||
{
|
||||
if (auto ret = slab->try_assign(buf, slabSize_, itemSize_))
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Create new slab from global pool
|
||||
auto* slab = detail::globalSlabPool.acquire(
|
||||
slabs_.load(std::memory_order::relaxed));
|
||||
|
||||
if (!slab) [[unlikely]]
|
||||
{
|
||||
detail::deallocateBuffer(buf);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto ret = slab->try_assign(buf, slabSize_, itemSize_);
|
||||
assert(ret);
|
||||
|
||||
// Link the new slab. Since we hold the lock, we can just do a
|
||||
// direct assignment here.
|
||||
slabs_.store(slab, std::memory_order::release);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public:
|
||||
/** Constructs a slab allocator able to allocate objects of a fixed size
|
||||
|
||||
@param count the number of items the slab allocator can allocate; note
|
||||
that a count of 0 is valid and means that the allocator
|
||||
is, effectively, disabled. This can be very useful in some
|
||||
contexts (e.g. when mimimal memory usage is needed) and
|
||||
allows for graceful failure.
|
||||
@param extra Extra bytes per item beyond sizeof(Type).
|
||||
@param minItems Minimum number of items per slab (rounded up to 2MB).
|
||||
*/
|
||||
constexpr explicit SlabAllocator(
|
||||
constexpr explicit sized_allocator_t(
|
||||
std::size_t extra,
|
||||
std::size_t alloc = 0,
|
||||
std::size_t align = 0)
|
||||
: itemAlignment_(align ? align : alignof(Type))
|
||||
, itemSize_(
|
||||
boost::alignment::align_up(sizeof(Type) + extra, itemAlignment_))
|
||||
, slabSize_(alloc)
|
||||
std::size_t minItems)
|
||||
: itemSize_(boost::alignment::align_up(sizeof(Type) + extra, Align))
|
||||
, slabSize_(boost::alignment::align_up(
|
||||
itemSize_ * minItems,
|
||||
detail::pageSize))
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(itemAlignment_ & (itemAlignment_ - 1)) == 0,
|
||||
"ripple::SlabAllocator::SlabAllocator : valid alignment");
|
||||
}
|
||||
|
||||
SlabAllocator(SlabAllocator const& other) = delete;
|
||||
SlabAllocator&
|
||||
operator=(SlabAllocator const& other) = delete;
|
||||
// Data buffers are intentionally not released. C++ destruction order
|
||||
// does not guarantee that all items allocated from this allocator
|
||||
// have been freed by the time this destructor runs.
|
||||
~sized_allocator_t() = default;
|
||||
|
||||
SlabAllocator(SlabAllocator&& other) = delete;
|
||||
SlabAllocator&
|
||||
operator=(SlabAllocator&& other) = delete;
|
||||
sized_allocator_t(sized_allocator_t const&) = delete;
|
||||
sized_allocator_t&
|
||||
operator=(sized_allocator_t const&) = delete;
|
||||
sized_allocator_t(sized_allocator_t&&) = delete;
|
||||
sized_allocator_t&
|
||||
operator=(sized_allocator_t&&) = delete;
|
||||
|
||||
~SlabAllocator()
|
||||
{
|
||||
// FIXME: We can't destroy the memory blocks we've allocated, because
|
||||
// we can't be sure that they are not being used. Cleaning the
|
||||
// shutdown process up could make this possible.
|
||||
}
|
||||
/** Returns the size of memory blocks returned by this allocator.
|
||||
|
||||
/** Returns the size of the memory block this allocator returns. */
|
||||
constexpr std::size_t
|
||||
@return Size of each allocated block in bytes.
|
||||
*/
|
||||
[[nodiscard]] constexpr std::size_t
|
||||
size() const noexcept
|
||||
{
|
||||
return itemSize_;
|
||||
@@ -217,87 +465,36 @@ public:
|
||||
|
||||
/** Returns a suitably aligned pointer, if one is available.
|
||||
|
||||
@return a pointer to a block of memory from the allocator, or
|
||||
nullptr if the allocator can't satisfy this request.
|
||||
@return Pointer to allocated memory, or nullptr on failure.
|
||||
|
||||
@note The gnu::malloc attribute is an optimization hint that can
|
||||
be leveraged by GCC and Clang.
|
||||
*/
|
||||
std::uint8_t*
|
||||
[[nodiscard, gnu::malloc]] std::uint8_t*
|
||||
allocate() noexcept
|
||||
{
|
||||
auto slab = slabs_.load();
|
||||
// Fast path: try existing slabs
|
||||
auto ret = fast_allocate();
|
||||
|
||||
while (slab != nullptr)
|
||||
if (ret == nullptr)
|
||||
{
|
||||
if (auto ret = slab->allocate())
|
||||
return ret;
|
||||
|
||||
slab = slab->next_;
|
||||
// Slow path: need new capacity. Grab the lock to serialize access
|
||||
// to this code path because it is expensive. Then re-check in case
|
||||
// another thread added capacity while we waited.
|
||||
spin_lock(blockLock_);
|
||||
ret = slow_allocate();
|
||||
spin_unlock(blockLock_);
|
||||
}
|
||||
|
||||
// No slab can satisfy our request, so we attempt to allocate a new
|
||||
// one here:
|
||||
std::size_t size = slabSize_;
|
||||
|
||||
// We want to allocate the memory at a 2 MiB boundary, to make it
|
||||
// possible to use hugepage mappings on Linux:
|
||||
auto buf =
|
||||
boost::alignment::aligned_alloc(megabytes(std::size_t(2)), size);
|
||||
|
||||
// clang-format off
|
||||
if (!buf) [[unlikely]]
|
||||
return nullptr;
|
||||
// clang-format on
|
||||
|
||||
#if BOOST_OS_LINUX
|
||||
// When allocating large blocks, attempt to leverage Linux's
|
||||
// transparent hugepage support. It is unclear and difficult
|
||||
// to accurately determine if doing this impacts performance
|
||||
// enough to justify using platform-specific tricks.
|
||||
if (size >= megabytes(std::size_t(4)))
|
||||
madvise(buf, size, MADV_HUGEPAGE);
|
||||
#endif
|
||||
|
||||
// We need to carve out a bit of memory for the slab header
|
||||
// and then align the rest appropriately:
|
||||
auto slabData = reinterpret_cast<void*>(
|
||||
reinterpret_cast<std::uint8_t*>(buf) + sizeof(SlabBlock));
|
||||
auto slabSize = size - sizeof(SlabBlock);
|
||||
|
||||
// This operation is essentially guaranteed not to fail but
|
||||
// let's be careful anyways.
|
||||
if (!boost::alignment::align(
|
||||
itemAlignment_, itemSize_, slabData, slabSize))
|
||||
{
|
||||
boost::alignment::aligned_free(buf);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
slab = new (buf) SlabBlock(
|
||||
slabs_.load(),
|
||||
reinterpret_cast<std::uint8_t*>(slabData),
|
||||
slabSize,
|
||||
itemSize_);
|
||||
|
||||
// Link the new slab
|
||||
while (!slabs_.compare_exchange_weak(
|
||||
slab->next_,
|
||||
slab,
|
||||
std::memory_order_release,
|
||||
std::memory_order_relaxed))
|
||||
{
|
||||
; // Nothing to do
|
||||
}
|
||||
|
||||
return slab->allocate();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Returns the memory block to the allocator.
|
||||
|
||||
@param ptr A pointer to a memory block.
|
||||
@param size If non-zero, a hint as to the size of the block.
|
||||
@return true if this memory block belonged to the allocator and has
|
||||
been released; false otherwise.
|
||||
@param ptr Pointer to memory block.
|
||||
@return true if the block belonged to this allocator and was freed.
|
||||
*/
|
||||
bool
|
||||
[[nodiscard]] bool
|
||||
deallocate(std::uint8_t* ptr) noexcept
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
@@ -305,112 +502,135 @@ public:
|
||||
"ripple::SlabAllocator::SlabAllocator::deallocate : non-null "
|
||||
"input");
|
||||
|
||||
for (auto slab = slabs_.load(); slab != nullptr; slab = slab->next_)
|
||||
auto release = [this](std::uint8_t* buf) {
|
||||
std::uint8_t* expected = nullptr;
|
||||
|
||||
if (buf != nullptr &&
|
||||
!cachedBuffer_.compare_exchange_strong(
|
||||
expected, buf, std::memory_order::release))
|
||||
detail::deallocateBuffer(buf);
|
||||
};
|
||||
|
||||
for (auto slab = slabs_.load(std::memory_order::acquire); slab;
|
||||
slab = slab->next_)
|
||||
{
|
||||
if (slab->own(ptr))
|
||||
{
|
||||
slab->deallocate(ptr);
|
||||
if (slab->try_deallocate(ptr, slabSize_, release))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/** A collection of slab allocators of various sizes for a given type. */
|
||||
template <typename Type>
|
||||
class SlabAllocatorSet
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Configuration for a single slab allocator.
|
||||
|
||||
@tparam MinItems Minimum number of items per slab (must be > 0).
|
||||
@tparam Extra Extra bytes per item beyond sizeof(Type).
|
||||
*/
|
||||
template <std::size_t MinItems, std::size_t Extra = 0>
|
||||
requires(MinItems > 0)
|
||||
struct config
|
||||
{
|
||||
private:
|
||||
// The list of allocators that belong to this set
|
||||
boost::container::static_vector<SlabAllocator<Type>, 64> allocators_;
|
||||
static constexpr std::size_t minItems = MinItems;
|
||||
static constexpr std::size_t extra = Extra;
|
||||
};
|
||||
|
||||
std::size_t maxSize_ = 0;
|
||||
/** Concept for valid slab configuration types. */
|
||||
template <typename T>
|
||||
concept SlabConfig = requires {
|
||||
{ T::minItems } -> std::convertible_to<std::size_t>;
|
||||
{ T::extra } -> std::convertible_to<std::size_t>;
|
||||
requires T::minItems > 0;
|
||||
};
|
||||
|
||||
public:
|
||||
class SlabConfig
|
||||
{
|
||||
friend class SlabAllocatorSet;
|
||||
/** Validate slab configurations at compile time.
|
||||
|
||||
private:
|
||||
std::size_t extra;
|
||||
std::size_t alloc;
|
||||
std::size_t align;
|
||||
Checks that configurations produce strictly increasing sizes after
|
||||
alignment. This catches both unsorted configs and configs that
|
||||
collapse to the same size after alignment.
|
||||
|
||||
public:
|
||||
constexpr SlabConfig(
|
||||
std::size_t extra_,
|
||||
std::size_t alloc_ = 0,
|
||||
std::size_t align_ = alignof(Type))
|
||||
: extra(extra_), alloc(alloc_), align(align_)
|
||||
{
|
||||
}
|
||||
@tparam Type The type used to determine base size.
|
||||
@tparam Align Alignment for allocated blocks.
|
||||
@tparam Configs Configuration types to validate.
|
||||
@return true if configurations are valid, false otherwise.
|
||||
*/
|
||||
template <typename Type, std::size_t Align, SlabConfig... Configs>
|
||||
consteval bool
|
||||
validate_slab_config()
|
||||
{
|
||||
constexpr auto align_up = [](std::size_t n, std::size_t a) {
|
||||
return (n + a - 1) & ~(a - 1);
|
||||
};
|
||||
|
||||
constexpr SlabAllocatorSet(std::vector<SlabConfig> cfg)
|
||||
{
|
||||
// Ensure that the specified allocators are sorted from smallest to
|
||||
// largest by size:
|
||||
std::sort(
|
||||
std::begin(cfg),
|
||||
std::end(cfg),
|
||||
[](SlabConfig const& a, SlabConfig const& b) {
|
||||
return a.extra < b.extra;
|
||||
});
|
||||
constexpr std::array<std::size_t, sizeof...(Configs)> sizes{
|
||||
align_up(sizeof(Type) + Configs::extra, Align)...};
|
||||
|
||||
// We should never have two slabs of the same size
|
||||
if (std::adjacent_find(
|
||||
std::begin(cfg),
|
||||
std::end(cfg),
|
||||
[](SlabConfig const& a, SlabConfig const& b) {
|
||||
return a.extra == b.extra;
|
||||
}) != cfg.end())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"SlabAllocatorSet<" + beast::type_name<Type>() +
|
||||
">: duplicate slab size");
|
||||
}
|
||||
for (std::size_t i = 1; i < sizes.size(); ++i)
|
||||
if (sizes[i - 1] >= sizes[i])
|
||||
return false;
|
||||
|
||||
for (auto const& c : cfg)
|
||||
{
|
||||
auto& a = allocators_.emplace_back(c.extra, c.alloc, c.align);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a.size() > maxSize_)
|
||||
maxSize_ = a.size();
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
SlabAllocatorSet(SlabAllocatorSet const& other) = delete;
|
||||
SlabAllocatorSet&
|
||||
operator=(SlabAllocatorSet const& other) = delete;
|
||||
/** A collection of slab allocators for different sizes.
|
||||
|
||||
SlabAllocatorSet(SlabAllocatorSet&& other) = delete;
|
||||
SlabAllocatorSet&
|
||||
operator=(SlabAllocatorSet&& other) = delete;
|
||||
Manages multiple sized_allocator_t instances, each configured for a
|
||||
different block size. Allocations are routed to the smallest allocator
|
||||
that can satisfy the request.
|
||||
|
||||
~SlabAllocatorSet()
|
||||
@tparam Type The type used to determine minimum block size and alignment.
|
||||
@tparam Align Alignment for allocated blocks (must be >= alignof(Type)).
|
||||
@tparam Configs Configuration types specifying each size class.
|
||||
*/
|
||||
template <typename Type, std::size_t Align, SlabConfig... Configs>
|
||||
requires(
|
||||
sizeof(Type) >= sizeof(void*) && Align >= alignof(Type) &&
|
||||
std::has_single_bit(Align) && sizeof...(Configs) > 0 &&
|
||||
validate_slab_config<Type, Align, Configs...>())
|
||||
class aligned_allocator_t
|
||||
{
|
||||
std::array<sized_allocator_t<Type, Align>, sizeof...(Configs)> allocators_;
|
||||
|
||||
public:
|
||||
/** Construct an allocator set. */
|
||||
constexpr aligned_allocator_t()
|
||||
: allocators_{sized_allocator_t<Type, Align>(
|
||||
Configs::extra,
|
||||
Configs::minItems)...}
|
||||
{
|
||||
}
|
||||
|
||||
/** Returns a suitably aligned pointer, if one is available.
|
||||
~aligned_allocator_t() = default;
|
||||
|
||||
@param extra The number of extra bytes, above and beyond the size of
|
||||
the object, that should be returned by the allocator.
|
||||
aligned_allocator_t(aligned_allocator_t const&) = delete;
|
||||
aligned_allocator_t&
|
||||
operator=(aligned_allocator_t const&) = delete;
|
||||
aligned_allocator_t(aligned_allocator_t&&) = delete;
|
||||
aligned_allocator_t&
|
||||
operator=(aligned_allocator_t&&) = delete;
|
||||
|
||||
@return a pointer to a block of memory, or nullptr if the allocator
|
||||
can't satisfy this request.
|
||||
/** Allocate memory for an object with extra bytes.
|
||||
|
||||
@param extra Extra bytes needed beyond sizeof(Type).
|
||||
@return Pointer to memory, or nullptr if no suitable allocator
|
||||
or allocation failed.
|
||||
|
||||
@note The gnu::malloc attribute is an optimization hint that can
|
||||
be leveraged by GCC and Clang.
|
||||
*/
|
||||
std::uint8_t*
|
||||
[[nodiscard, gnu::malloc]] std::uint8_t*
|
||||
allocate(std::size_t extra) noexcept
|
||||
{
|
||||
if (auto const size = sizeof(Type) + extra; size <= maxSize_)
|
||||
auto const size = sizeof(Type) + extra;
|
||||
|
||||
for (auto& a : allocators_)
|
||||
{
|
||||
for (auto& a : allocators_)
|
||||
{
|
||||
if (a.size() >= size)
|
||||
return a.allocate();
|
||||
}
|
||||
if (a.size() >= size)
|
||||
return a.allocate();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
@@ -418,12 +638,10 @@ public:
|
||||
|
||||
/** Returns the memory block to the allocator.
|
||||
|
||||
@param ptr A pointer to a memory block.
|
||||
|
||||
@return true if this memory block belonged to one of the allocators
|
||||
in this set and has been released; false otherwise.
|
||||
@param ptr Pointer to memory block.
|
||||
@return true if memory belonged to this set and was freed.
|
||||
*/
|
||||
bool
|
||||
[[nodiscard]] bool
|
||||
deallocate(std::uint8_t* ptr) noexcept
|
||||
{
|
||||
for (auto& a : allocators_)
|
||||
@@ -431,11 +649,16 @@ public:
|
||||
if (a.deallocate(ptr))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/** Alias for aligned_allocator_t with default alignment. */
|
||||
template <typename Type, SlabConfig... Configs>
|
||||
using allocator_t = aligned_allocator_t<Type, alignof(Type), Configs...>;
|
||||
|
||||
} // namespace slab
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif // RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED
|
||||
@@ -77,7 +77,7 @@ strUnHex(std::size_t strSize, Iterator begin, Iterator end)
|
||||
|
||||
if (strSize & 1)
|
||||
{
|
||||
int c = unxtab[*iter++];
|
||||
int c = unxtab[static_cast<unsigned char>(*iter++)];
|
||||
|
||||
if (c < 0)
|
||||
return {};
|
||||
@@ -87,12 +87,12 @@ strUnHex(std::size_t strSize, Iterator begin, Iterator end)
|
||||
|
||||
while (iter != end)
|
||||
{
|
||||
int cHigh = unxtab[*iter++];
|
||||
int cHigh = unxtab[static_cast<unsigned char>(*iter++)];
|
||||
|
||||
if (cHigh < 0)
|
||||
return {};
|
||||
|
||||
int cLow = unxtab[*iter++];
|
||||
int cLow = unxtab[static_cast<unsigned char>(*iter++)];
|
||||
|
||||
if (cLow < 0)
|
||||
return {};
|
||||
|
||||
+420
-345
File diff suppressed because it is too large.
Load diff
@@ -23,6 +23,7 @@
|
||||
#include <xrpl/beast/type_name.h>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
|
||||
@@ -52,7 +53,7 @@ Rethrow()
|
||||
}
|
||||
|
||||
template <class E, class... Args>
|
||||
[[noreturn]] inline void
|
||||
[[noreturn]] constexpr void
|
||||
Throw(Args&&... args)
|
||||
{
|
||||
static_assert(
|
||||
@@ -60,10 +61,15 @@ Throw(Args&&... args)
|
||||
"Exception must derive from std::exception.");
|
||||
|
||||
E e(std::forward<Args>(args)...);
|
||||
LogThrow(
|
||||
std::string(
|
||||
"Throwing exception of type " + beast::type_name<E>() + ": ") +
|
||||
e.what());
|
||||
|
||||
// Skip logging when evaluated at compile time, so that `Throw` can be
|
||||
// called from constexpr/consteval functions.
|
||||
if (!std::is_constant_evaluated())
|
||||
LogThrow(
|
||||
std::string(
|
||||
"Throwing exception of type " + beast::type_name<E>() + ": ") +
|
||||
e.what());
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
+139
-96
@@ -20,6 +20,7 @@
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
@@ -30,6 +31,7 @@
|
||||
namespace ripple {
|
||||
|
||||
namespace detail {
|
||||
|
||||
/** Inform the processor that we are in a tight spin-wait loop.
|
||||
|
||||
Spinlocks caught in tight loops can result in the processor's pipeline
|
||||
@@ -52,11 +54,130 @@ spin_pause() noexcept
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/** @{ */
|
||||
/** Classes to handle arrays of spinlocks packed into a single atomic integer:
|
||||
/** Attempt to acquire a spinlock without blocking.
|
||||
|
||||
Packed spinlocks allow for tremendously space-efficient lock-sharding
|
||||
but they come at a cost.
|
||||
@tparam T An unsigned integral type.
|
||||
@param lock The atomic variable used as the lock.
|
||||
@return true if the lock was acquired, false if it was already held.
|
||||
*/
|
||||
template <typename T>
|
||||
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
|
||||
[[nodiscard]] bool
|
||||
spin_try_lock(std::atomic<T>& lock) noexcept
|
||||
{
|
||||
T expected = 0;
|
||||
|
||||
return lock.compare_exchange_strong(
|
||||
expected,
|
||||
std::numeric_limits<T>::max(),
|
||||
std::memory_order::acquire,
|
||||
std::memory_order::relaxed);
|
||||
}
|
||||
|
||||
/** Acquire a spinlock, blocking until available.
|
||||
|
||||
Uses a TTAS (test-and-test-and-set) pattern to reduce cache coherency
|
||||
traffic during contention.
|
||||
|
||||
@tparam T An unsigned integral type.
|
||||
@param lock The atomic variable used as the lock.
|
||||
*/
|
||||
template <typename T>
|
||||
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
|
||||
void
|
||||
spin_lock(std::atomic<T>& lock) noexcept
|
||||
{
|
||||
T expected = 0;
|
||||
|
||||
while (!lock.compare_exchange_weak(
|
||||
expected,
|
||||
std::numeric_limits<T>::max(),
|
||||
std::memory_order::acquire,
|
||||
std::memory_order::relaxed))
|
||||
{
|
||||
expected = 0;
|
||||
|
||||
while (lock.load(std::memory_order::relaxed) != 0)
|
||||
detail::spin_pause();
|
||||
}
|
||||
}
|
||||
|
||||
/** Release a spinlock.
|
||||
|
||||
@tparam T An unsigned integral type.
|
||||
@param lock The atomic variable used as the lock.
|
||||
*/
|
||||
template <typename T>
|
||||
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
|
||||
void
|
||||
spin_unlock(std::atomic<T>& lock) noexcept
|
||||
{
|
||||
lock.store(0, std::memory_order::release);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** A Lockable interface to a spinlock implemented on top of an atomic.
|
||||
|
||||
@tparam T An unsigned integral type.
|
||||
|
||||
@note Using `packed_spinlock` and `spinlock` against the same underlying
|
||||
atomic integer can result in `spinlock` not being able to actually
|
||||
acquire the lock during periods of high contention, because of how
|
||||
the two locks operate: `spinlock` will spin trying to grab all the
|
||||
bits at once, whereas any given `packed_spinlock` will only try to
|
||||
grab one bit at a time. Caveat emptor.
|
||||
|
||||
This class meets the requirements of Lockable:
|
||||
https://en.cppreference.com/w/cpp/named_req/Lockable
|
||||
*/
|
||||
template <typename T>
|
||||
requires(std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free)
|
||||
class spinlock
|
||||
{
|
||||
std::atomic<T>& lock_;
|
||||
|
||||
public:
|
||||
spinlock(spinlock const&) = delete;
|
||||
spinlock&
|
||||
operator=(spinlock const&) = delete;
|
||||
|
||||
/** Construct a spinlock handle.
|
||||
|
||||
@param lock The atomic integer to spin against.
|
||||
|
||||
@note For performance reasons, you should strive to have `lock` be
|
||||
on a cacheline by itself.
|
||||
*/
|
||||
explicit spinlock(std::atomic<T>& lock) noexcept : lock_(lock)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
try_lock() noexcept
|
||||
{
|
||||
return spin_try_lock(lock_);
|
||||
}
|
||||
|
||||
void
|
||||
lock() noexcept
|
||||
{
|
||||
spin_lock(lock_);
|
||||
}
|
||||
|
||||
void
|
||||
unlock() noexcept
|
||||
{
|
||||
spin_unlock(lock_);
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** A Lockable interface to a packed spinlock implemented on top of an atomic.
|
||||
|
||||
Packed spinlocks offer tremendous space-efficient lock-sharding but
|
||||
they come at a cost.
|
||||
|
||||
First, the implementation is necessarily low-level and uses advanced
|
||||
features like memory ordering and highly platform-specific tricks to
|
||||
@@ -78,26 +199,19 @@ spin_pause() noexcept
|
||||
that it can, usually, outperform spinlocks.
|
||||
|
||||
@tparam T An unsigned integral type (e.g. std::uint16_t)
|
||||
*/
|
||||
|
||||
/** A class that grabs a single packed spinlock from an atomic integer.
|
||||
|
||||
This class meets the requirements of Lockable:
|
||||
https://en.cppreference.com/w/cpp/named_req/Lockable
|
||||
*/
|
||||
template <class T>
|
||||
template <typename T>
|
||||
requires(
|
||||
std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free &&
|
||||
requires(std::atomic<T>& a, T v) {
|
||||
{ a.fetch_or(v) } -> std::same_as<T>;
|
||||
{ a.fetch_and(v) } -> std::same_as<T>;
|
||||
})
|
||||
class packed_spinlock
|
||||
{
|
||||
// clang-format off
|
||||
static_assert(std::is_unsigned_v<T>);
|
||||
static_assert(std::atomic<T>::is_always_lock_free);
|
||||
static_assert(
|
||||
std::is_same_v<decltype(std::declval<std::atomic<T>&>().fetch_or(0)), T> &&
|
||||
std::is_same_v<decltype(std::declval<std::atomic<T>&>().fetch_and(0)), T>,
|
||||
"std::atomic<T>::fetch_and(T) and std::atomic<T>::fetch_and(T) are required by packed_spinlock");
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
std::atomic<T>& bits_;
|
||||
T const mask_;
|
||||
|
||||
@@ -114,7 +228,7 @@ public:
|
||||
@note For performance reasons, you should strive to have `lock` be
|
||||
on a cacheline by itself.
|
||||
*/
|
||||
packed_spinlock(std::atomic<T>& lock, int index)
|
||||
packed_spinlock(std::atomic<T>& lock, int index) noexcept
|
||||
: bits_(lock), mask_(static_cast<T>(1) << index)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
@@ -123,13 +237,13 @@ public:
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
try_lock()
|
||||
try_lock() noexcept
|
||||
{
|
||||
return (bits_.fetch_or(mask_, std::memory_order_acquire) & mask_) == 0;
|
||||
return (bits_.fetch_or(mask_, std::memory_order::acquire) & mask_) == 0;
|
||||
}
|
||||
|
||||
void
|
||||
lock()
|
||||
lock() noexcept
|
||||
{
|
||||
while (!try_lock())
|
||||
{
|
||||
@@ -137,89 +251,18 @@ public:
|
||||
// serves to help reduce cache coherency traffic during times
|
||||
// of contention by avoiding writes that would definitely not
|
||||
// result in the lock being acquired.
|
||||
while ((bits_.load(std::memory_order_relaxed) & mask_) != 0)
|
||||
while ((bits_.load(std::memory_order::relaxed) & mask_) != 0)
|
||||
detail::spin_pause();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
unlock()
|
||||
unlock() noexcept
|
||||
{
|
||||
bits_.fetch_and(~mask_, std::memory_order_release);
|
||||
bits_.fetch_and(~mask_, std::memory_order::release);
|
||||
}
|
||||
};
|
||||
|
||||
/** A spinlock implemented on top of an atomic integer.
|
||||
|
||||
@note Using `packed_spinlock` and `spinlock` against the same underlying
|
||||
atomic integer can result in `spinlock` not being able to actually
|
||||
acquire the lock during periods of high contention, because of how
|
||||
the two locks operate: `spinlock` will spin trying to grab all the
|
||||
bits at once, whereas any given `packed_spinlock` will only try to
|
||||
grab one bit at a time. Caveat emptor.
|
||||
|
||||
This class meets the requirements of Lockable:
|
||||
https://en.cppreference.com/w/cpp/named_req/Lockable
|
||||
*/
|
||||
template <class T>
|
||||
class spinlock
|
||||
{
|
||||
static_assert(std::is_unsigned_v<T>);
|
||||
static_assert(std::atomic<T>::is_always_lock_free);
|
||||
|
||||
private:
|
||||
std::atomic<T>& lock_;
|
||||
|
||||
public:
|
||||
spinlock(spinlock const&) = delete;
|
||||
spinlock&
|
||||
operator=(spinlock const&) = delete;
|
||||
|
||||
/** Grabs the
|
||||
|
||||
@param lock The atomic integer to spin against.
|
||||
|
||||
@note For performance reasons, you should strive to have `lock` be
|
||||
on a cacheline by itself.
|
||||
*/
|
||||
spinlock(std::atomic<T>& lock) : lock_(lock)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
try_lock()
|
||||
{
|
||||
T expected = 0;
|
||||
|
||||
return lock_.compare_exchange_weak(
|
||||
expected,
|
||||
std::numeric_limits<T>::max(),
|
||||
std::memory_order_acquire,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void
|
||||
lock()
|
||||
{
|
||||
while (!try_lock())
|
||||
{
|
||||
// The use of relaxed memory ordering here is intentional and
|
||||
// serves to help reduce cache coherency traffic during times
|
||||
// of contention by avoiding writes that would definitely not
|
||||
// result in the lock being acquired.
|
||||
while (lock_.load(std::memory_order_relaxed) != 0)
|
||||
detail::spin_pause();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
unlock()
|
||||
{
|
||||
lock_.store(0, std::memory_order_release);
|
||||
}
|
||||
};
|
||||
/** @} */
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -22,7 +22,8 @@
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <boost/asio/basic_waitable_timer.hpp>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
@@ -31,7 +32,7 @@
|
||||
|
||||
namespace beast {
|
||||
|
||||
/** Measures handler latency on an io_service queue. */
|
||||
/** Measures handler latency on an io_context queue. */
|
||||
template <class Clock>
|
||||
class io_latency_probe
|
||||
{
|
||||
@@ -43,12 +44,12 @@ private:
|
||||
std::condition_variable_any m_cond;
|
||||
std::size_t m_count;
|
||||
duration const m_period;
|
||||
boost::asio::io_service& m_ios;
|
||||
boost::asio::io_context& m_ios;
|
||||
boost::asio::basic_waitable_timer<std::chrono::steady_clock> m_timer;
|
||||
bool m_cancel;
|
||||
|
||||
public:
|
||||
io_latency_probe(duration const& period, boost::asio::io_service& ios)
|
||||
io_latency_probe(duration const& period, boost::asio::io_context& ios)
|
||||
: m_count(1)
|
||||
, m_period(period)
|
||||
, m_ios(ios)
|
||||
@@ -63,16 +64,16 @@ public:
|
||||
cancel(lock, true);
|
||||
}
|
||||
|
||||
/** Return the io_service associated with the latency probe. */
|
||||
/** Return the io_context associated with the latency probe. */
|
||||
/** @{ */
|
||||
boost::asio::io_service&
|
||||
get_io_service()
|
||||
boost::asio::io_context&
|
||||
get_io_context()
|
||||
{
|
||||
return m_ios;
|
||||
}
|
||||
|
||||
boost::asio::io_service const&
|
||||
get_io_service() const
|
||||
boost::asio::io_context const&
|
||||
get_io_context() const
|
||||
{
|
||||
return m_ios;
|
||||
}
|
||||
@@ -108,8 +109,10 @@ public:
|
||||
std::lock_guard lock(m_mutex);
|
||||
if (m_cancel)
|
||||
throw std::logic_error("io_latency_probe is canceled");
|
||||
m_ios.post(sample_op<Handler>(
|
||||
std::forward<Handler>(handler), Clock::now(), false, this));
|
||||
boost::asio::post(
|
||||
m_ios,
|
||||
sample_op<Handler>(
|
||||
std::forward<Handler>(handler), Clock::now(), false, this));
|
||||
}
|
||||
|
||||
/** Initiate continuous i/o latency sampling.
|
||||
@@ -123,8 +126,10 @@ public:
|
||||
std::lock_guard lock(m_mutex);
|
||||
if (m_cancel)
|
||||
throw std::logic_error("io_latency_probe is canceled");
|
||||
m_ios.post(sample_op<Handler>(
|
||||
std::forward<Handler>(handler), Clock::now(), true, this));
|
||||
boost::asio::post(
|
||||
m_ios,
|
||||
sample_op<Handler>(
|
||||
std::forward<Handler>(handler), Clock::now(), true, this));
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -235,12 +240,13 @@ private:
|
||||
// The latency is too high to maintain the desired
|
||||
// period so don't bother with a timer.
|
||||
//
|
||||
m_probe->m_ios.post(
|
||||
boost::asio::post(
|
||||
m_probe->m_ios,
|
||||
sample_op<Handler>(m_handler, now, m_repeat, m_probe));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_probe->m_timer.expires_from_now(when - now);
|
||||
m_probe->m_timer.expires_after(when - now);
|
||||
m_probe->m_timer.async_wait(
|
||||
sample_op<Handler>(m_handler, now, m_repeat, m_probe));
|
||||
}
|
||||
@@ -253,7 +259,8 @@ private:
|
||||
if (!m_probe)
|
||||
return;
|
||||
typename Clock::time_point const now(Clock::now());
|
||||
m_probe->m_ios.post(
|
||||
boost::asio::post(
|
||||
m_probe->m_ios,
|
||||
sample_op<Handler>(m_handler, now, m_repeat, m_probe));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,307 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <[email protected]>
|
||||
|
||||
Permission to use, copy, modify, and/or 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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_INTRUSIVE_LOCKFREESTACK_H_INCLUDED
|
||||
#define BEAST_INTRUSIVE_LOCKFREESTACK_H_INCLUDED
|
||||
|
||||
#include <atomic>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
|
||||
namespace beast {
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Container, bool IsConst>
|
||||
class LockFreeStackIterator
|
||||
{
|
||||
protected:
|
||||
using Node = typename Container::Node;
|
||||
using NodePtr =
|
||||
typename std::conditional<IsConst, Node const*, Node*>::type;
|
||||
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = typename Container::value_type;
|
||||
using difference_type = typename Container::difference_type;
|
||||
using pointer = typename std::conditional<
|
||||
IsConst,
|
||||
typename Container::const_pointer,
|
||||
typename Container::pointer>::type;
|
||||
using reference = typename std::conditional<
|
||||
IsConst,
|
||||
typename Container::const_reference,
|
||||
typename Container::reference>::type;
|
||||
|
||||
LockFreeStackIterator() : m_node()
|
||||
{
|
||||
}
|
||||
|
||||
LockFreeStackIterator(NodePtr node) : m_node(node)
|
||||
{
|
||||
}
|
||||
|
||||
template <bool OtherIsConst>
|
||||
explicit LockFreeStackIterator(
|
||||
LockFreeStackIterator<Container, OtherIsConst> const& other)
|
||||
: m_node(other.m_node)
|
||||
{
|
||||
}
|
||||
|
||||
LockFreeStackIterator&
|
||||
operator=(NodePtr node)
|
||||
{
|
||||
m_node = node;
|
||||
return static_cast<LockFreeStackIterator&>(*this);
|
||||
}
|
||||
|
||||
LockFreeStackIterator&
|
||||
operator++()
|
||||
{
|
||||
m_node = m_node->m_next.load();
|
||||
return static_cast<LockFreeStackIterator&>(*this);
|
||||
}
|
||||
|
||||
LockFreeStackIterator
|
||||
operator++(int)
|
||||
{
|
||||
LockFreeStackIterator result(*this);
|
||||
m_node = m_node->m_next;
|
||||
return result;
|
||||
}
|
||||
|
||||
NodePtr
|
||||
node() const
|
||||
{
|
||||
return m_node;
|
||||
}
|
||||
|
||||
reference
|
||||
operator*() const
|
||||
{
|
||||
return *this->operator->();
|
||||
}
|
||||
|
||||
pointer
|
||||
operator->() const
|
||||
{
|
||||
return static_cast<pointer>(m_node);
|
||||
}
|
||||
|
||||
private:
|
||||
NodePtr m_node;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Container, bool LhsIsConst, bool RhsIsConst>
|
||||
bool
|
||||
operator==(
|
||||
LockFreeStackIterator<Container, LhsIsConst> const& lhs,
|
||||
LockFreeStackIterator<Container, RhsIsConst> const& rhs)
|
||||
{
|
||||
return lhs.node() == rhs.node();
|
||||
}
|
||||
|
||||
template <class Container, bool LhsIsConst, bool RhsIsConst>
|
||||
bool
|
||||
operator!=(
|
||||
LockFreeStackIterator<Container, LhsIsConst> const& lhs,
|
||||
LockFreeStackIterator<Container, RhsIsConst> const& rhs)
|
||||
{
|
||||
return lhs.node() != rhs.node();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Multiple Producer, Multiple Consumer (MPMC) intrusive stack.
|
||||
|
||||
This stack is implemented using the same intrusive interface as List.
|
||||
All mutations are lock-free.
|
||||
|
||||
The caller is responsible for preventing the "ABA" problem:
|
||||
http://en.wikipedia.org/wiki/ABA_problem
|
||||
|
||||
@param Tag A type name used to distinguish lists and nodes, for
|
||||
putting objects in multiple lists. If this parameter is
|
||||
omitted, the default tag is used.
|
||||
*/
|
||||
template <class Element, class Tag = void>
|
||||
class LockFreeStack
|
||||
{
|
||||
public:
|
||||
class Node
|
||||
{
|
||||
public:
|
||||
Node() : m_next(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
explicit Node(Node* next) : m_next(next)
|
||||
{
|
||||
}
|
||||
|
||||
Node(Node const&) = delete;
|
||||
Node&
|
||||
operator=(Node const&) = delete;
|
||||
|
||||
private:
|
||||
friend class LockFreeStack;
|
||||
|
||||
template <class Container, bool IsConst>
|
||||
friend class LockFreeStackIterator;
|
||||
|
||||
std::atomic<Node*> m_next;
|
||||
};
|
||||
|
||||
public:
|
||||
using value_type = Element;
|
||||
using pointer = Element*;
|
||||
using reference = Element&;
|
||||
using const_pointer = Element const*;
|
||||
using const_reference = Element const&;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using iterator = LockFreeStackIterator<LockFreeStack<Element, Tag>, false>;
|
||||
using const_iterator =
|
||||
LockFreeStackIterator<LockFreeStack<Element, Tag>, true>;
|
||||
|
||||
LockFreeStack() : m_end(nullptr), m_head(&m_end)
|
||||
{
|
||||
}
|
||||
|
||||
LockFreeStack(LockFreeStack const&) = delete;
|
||||
LockFreeStack&
|
||||
operator=(LockFreeStack const&) = delete;
|
||||
|
||||
/** Returns true if the stack is empty. */
|
||||
bool
|
||||
empty() const
|
||||
{
|
||||
return m_head.load() == &m_end;
|
||||
}
|
||||
|
||||
/** Push a node onto the stack.
|
||||
The caller is responsible for preventing the ABA problem.
|
||||
This operation is lock-free.
|
||||
Thread safety:
|
||||
Safe to call from any thread.
|
||||
|
||||
@param node The node to push.
|
||||
|
||||
@return `true` if the stack was previously empty. If multiple threads
|
||||
are attempting to push, only one will receive `true`.
|
||||
*/
|
||||
// VFALCO NOTE Fix this, shouldn't it be a reference like intrusive list?
|
||||
bool
|
||||
push_front(Node* node)
|
||||
{
|
||||
bool first;
|
||||
Node* old_head = m_head.load(std::memory_order_relaxed);
|
||||
do
|
||||
{
|
||||
first = (old_head == &m_end);
|
||||
node->m_next = old_head;
|
||||
} while (!m_head.compare_exchange_strong(
|
||||
old_head,
|
||||
node,
|
||||
std::memory_order_release,
|
||||
std::memory_order_relaxed));
|
||||
return first;
|
||||
}
|
||||
|
||||
/** Pop an element off the stack.
|
||||
The caller is responsible for preventing the ABA problem.
|
||||
This operation is lock-free.
|
||||
Thread safety:
|
||||
Safe to call from any thread.
|
||||
|
||||
@return The element that was popped, or `nullptr` if the stack
|
||||
was empty.
|
||||
*/
|
||||
Element*
|
||||
pop_front()
|
||||
{
|
||||
Node* node = m_head.load();
|
||||
Node* new_head;
|
||||
do
|
||||
{
|
||||
if (node == &m_end)
|
||||
return nullptr;
|
||||
new_head = node->m_next.load();
|
||||
} while (!m_head.compare_exchange_strong(
|
||||
node,
|
||||
new_head,
|
||||
std::memory_order_release,
|
||||
std::memory_order_relaxed));
|
||||
return static_cast<Element*>(node);
|
||||
}
|
||||
|
||||
/** Return a forward iterator to the beginning or end of the stack.
|
||||
Undefined behavior results if push_front or pop_front is called
|
||||
while an iteration is in progress.
|
||||
Thread safety:
|
||||
Caller is responsible for synchronization.
|
||||
*/
|
||||
/** @{ */
|
||||
iterator
|
||||
begin()
|
||||
{
|
||||
return iterator(m_head.load());
|
||||
}
|
||||
|
||||
iterator
|
||||
end()
|
||||
{
|
||||
return iterator(&m_end);
|
||||
}
|
||||
|
||||
const_iterator
|
||||
begin() const
|
||||
{
|
||||
return const_iterator(m_head.load());
|
||||
}
|
||||
|
||||
const_iterator
|
||||
end() const
|
||||
{
|
||||
return const_iterator(&m_end);
|
||||
}
|
||||
|
||||
const_iterator
|
||||
cbegin() const
|
||||
{
|
||||
return const_iterator(m_head.load());
|
||||
}
|
||||
|
||||
const_iterator
|
||||
cend() const
|
||||
{
|
||||
return const_iterator(&m_end);
|
||||
}
|
||||
/** @} */
|
||||
|
||||
private:
|
||||
Node m_end;
|
||||
std::atomic<Node*> m_head;
|
||||
};
|
||||
|
||||
} // namespace beast
|
||||
|
||||
#endif
|
||||
@@ -8,9 +8,12 @@
|
||||
#ifndef BEAST_TEST_YIELD_TO_HPP
|
||||
#define BEAST_TEST_YIELD_TO_HPP
|
||||
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#include <boost/asio/executor_work_guard.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/thread/csbl/memory/allocator_arg.hpp>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
@@ -29,10 +32,12 @@ namespace test {
|
||||
class enable_yield_to
|
||||
{
|
||||
protected:
|
||||
boost::asio::io_service ios_;
|
||||
boost::asio::io_context ios_;
|
||||
|
||||
private:
|
||||
boost::optional<boost::asio::io_service::work> work_;
|
||||
boost::optional<boost::asio::executor_work_guard<
|
||||
boost::asio::io_context::executor_type>>
|
||||
work_;
|
||||
std::vector<std::thread> threads_;
|
||||
std::mutex m_;
|
||||
std::condition_variable cv_;
|
||||
@@ -42,7 +47,8 @@ public:
|
||||
/// The type of yield context passed to functions.
|
||||
using yield_context = boost::asio::yield_context;
|
||||
|
||||
explicit enable_yield_to(std::size_t concurrency = 1) : work_(ios_)
|
||||
explicit enable_yield_to(std::size_t concurrency = 1)
|
||||
: work_(boost::asio::make_work_guard(ios_))
|
||||
{
|
||||
threads_.reserve(concurrency);
|
||||
while (concurrency--)
|
||||
@@ -56,9 +62,9 @@ public:
|
||||
t.join();
|
||||
}
|
||||
|
||||
/// Return the `io_service` associated with the object
|
||||
boost::asio::io_service&
|
||||
get_io_service()
|
||||
/// Return the `io_context` associated with the object
|
||||
boost::asio::io_context&
|
||||
get_io_context()
|
||||
{
|
||||
return ios_;
|
||||
}
|
||||
@@ -111,13 +117,18 @@ enable_yield_to::spawn(F0&& f, FN&&... fn)
|
||||
{
|
||||
boost::asio::spawn(
|
||||
ios_,
|
||||
boost::allocator_arg,
|
||||
boost::context::fixedsize_stack(2 * 1024 * 1024),
|
||||
[&](yield_context yield) {
|
||||
f(yield);
|
||||
std::lock_guard lock{m_};
|
||||
if (--running_ == 0)
|
||||
cv_.notify_all();
|
||||
},
|
||||
boost::coroutines::attributes(2 * 1024 * 1024));
|
||||
[](std::exception_ptr e) {
|
||||
if (e)
|
||||
std::rethrow_exception(e);
|
||||
});
|
||||
spawn(fn...);
|
||||
}
|
||||
|
||||
|
||||
+19
-13
@@ -15,6 +15,7 @@
|
||||
#define uint256 std::string
|
||||
#define featureHooksUpdate1 "1"
|
||||
#define featureHooksUpdate2 "1"
|
||||
#define featureRNG "1"
|
||||
#define fix20250131 "1"
|
||||
#define fixGuardDepth32 "1"
|
||||
namespace hook_api {
|
||||
@@ -385,7 +386,9 @@ enum class hook_return_code : int64_t {
|
||||
MEM_OVERLAP = -43, // one or more specified buffers are the same memory
|
||||
TOO_MANY_STATE_MODIFICATIONS = -44, // more than 256 modified state
|
||||
// entires in the combined hook chains
|
||||
TOO_MANY_NAMESPACES = -45
|
||||
TOO_MANY_NAMESPACES = -45,
|
||||
TOO_LITTLE_ENTROPY = -46 // featureRNG: no seed for this ledger, or too few
|
||||
// validators contributed to it
|
||||
};
|
||||
|
||||
enum class ExitType : uint8_t {
|
||||
@@ -398,12 +401,25 @@ enum class ExitType : uint8_t {
|
||||
const uint16_t max_state_modifications = 256;
|
||||
const uint8_t max_slots = 255;
|
||||
const uint8_t max_nonce = 255;
|
||||
const uint16_t max_rng_draws = 256; // featureRNG, per Hook execution
|
||||
const uint8_t max_emit = 255;
|
||||
const uint8_t max_params = 16;
|
||||
const double fee_base_multiplier = 1.1f;
|
||||
|
||||
using APIWhitelist = std::map<std::string, std::vector<uint8_t>>;
|
||||
|
||||
namespace detail {
|
||||
template <class T>
|
||||
constexpr uint8_t wasmTypeCode = sizeof(T) == 8 ? 0x7EU : 0x7FU;
|
||||
|
||||
template <class R, class... Args>
|
||||
std::vector<uint8_t>
|
||||
wasmSignature(R (*)(Args...))
|
||||
{
|
||||
return {wasmTypeCode<R>, wasmTypeCode<Args>...};
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
// RH NOTE: Find descriptions of api functions in ./impl/applyHook.cpp and
|
||||
// hookapi.h (include for hooks) this is a map of the api name to its return
|
||||
// code (vec[0] and its parameters vec[>0]) as wasm type codes
|
||||
@@ -415,25 +431,15 @@ getImportWhitelist(Rules const& rules)
|
||||
#pragma push_macro("HOOK_API_DEFINITION")
|
||||
#undef HOOK_API_DEFINITION
|
||||
|
||||
#define int64_t 0x7EU
|
||||
#define int32_t 0x7FU
|
||||
#define uint32_t 0x7FU
|
||||
|
||||
#define HOOK_WRAP_PARAMS(...) __VA_ARGS__
|
||||
|
||||
#define HOOK_API_DEFINITION( \
|
||||
RETURN_TYPE, FUNCTION_NAME, PARAMS_TUPLE, AMENDMENT) \
|
||||
if (AMENDMENT == uint256{} || rules.enabled(AMENDMENT)) \
|
||||
whitelist[#FUNCTION_NAME] = { \
|
||||
RETURN_TYPE, HOOK_WRAP_PARAMS PARAMS_TUPLE};
|
||||
whitelist[#FUNCTION_NAME] = \
|
||||
detail::wasmSignature((RETURN_TYPE(*) PARAMS_TUPLE) nullptr);
|
||||
|
||||
#include "hook_api.macro"
|
||||
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef HOOK_WRAP_PARAMS
|
||||
#undef int64_t
|
||||
#undef int32_t
|
||||
#undef uint32_t
|
||||
#pragma pop_macro("HOOK_API_DEFINITION")
|
||||
|
||||
return whitelist;
|
||||
|
||||
@@ -87,7 +87,7 @@ parseSignedLeb128(
|
||||
}
|
||||
*end_offset = i;
|
||||
if (shift < 64 && (b & 0x40U))
|
||||
val |= (~0 << shift);
|
||||
val |= (~int64_t{0} << shift);
|
||||
|
||||
return val;
|
||||
}
|
||||
@@ -1096,7 +1096,11 @@ validateGuards(
|
||||
{
|
||||
int name_len = parseLeb128(wasm, i, &i);
|
||||
CHECK_SHORT_HOOK();
|
||||
if (name_len == 4)
|
||||
// Only compare the name if all four bytes are in bounds. A
|
||||
// name running past the end is rejected as a short hook below
|
||||
// either way, so this does not change which hooks are valid.
|
||||
if (name_len == 4 &&
|
||||
static_cast<std::size_t>(i) + 4 <= wasm.size())
|
||||
{
|
||||
if (wasm[i] == 'h' && wasm[i + 1] == 'o' &&
|
||||
wasm[i + 2] == 'o' && wasm[i + 3] == 'k')
|
||||
|
||||
@@ -244,9 +244,9 @@
|
||||
<< "Tried to accept/rollback but specified " \
|
||||
"memory outside of the wasm instance " \
|
||||
<< "limit when specifying a reason string"; \
|
||||
if (!view.rules().enabled(fix20261001)) \
|
||||
if (!view.rules().enabled(fix20260929)) \
|
||||
return OUT_OF_BOUNDS; \
|
||||
/* fix20261001: terminate as a rollback so the \
|
||||
/* fix20260929: terminate as a rollback so the \
|
||||
failure is visible in HookExecution metadata */ \
|
||||
hookCtx.result.exitType = hook_api::ExitType::ROLLBACK; \
|
||||
hookCtx.result.exitCode = (int64_t)OUT_OF_BOUNDS; \
|
||||
|
||||
+26
-518
@@ -1,523 +1,31 @@
|
||||
|
||||
#ifndef HOOKMISC_INCLUDED
|
||||
#define HOOKMISC_INCLUDED 1
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
namespace ripple {
|
||||
// RH TODO: there's definitely a mucher nicer way to do this, but it involves
|
||||
// modifying the base_uint class and we don't want to do that yet.
|
||||
|
||||
static const std::array<ripple::uint256, 256> UINT256_BIT = {
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000000001"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000000002"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000000004"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000000008"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000000010"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000000020"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000000040"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000000080"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000000100"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000000200"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000000400"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000000800"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000001000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000002000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000004000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000008000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000010000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000020000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000040000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000080000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000100000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000200000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000400000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000000800000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000001000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000002000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000004000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000008000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000010000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000020000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000040000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000080000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000100000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000200000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000400000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000000800000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000001000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000002000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000004000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000008000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000010000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000020000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000040000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000080000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000100000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000200000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000400000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000000800000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000001000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000002000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000004000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000008000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000010000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000020000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000040000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000080000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000100000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000200000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000400000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000000800000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000001000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000002000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000004000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000008000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000010000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000020000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000040000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000080000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000100000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000200000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000400000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000000800000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000001000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000002000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000004000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000008000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000010000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000020000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000040000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000080000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000100000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000200000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000400000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000000800000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000001000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000002000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000004000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000008000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000010000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000020000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000040000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000080000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000100000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000200000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000400000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000000800000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000001000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000002000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000004000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000008000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000010000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000020000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000040000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000080000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000100000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000200000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000400000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000000800000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000001000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000002000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000004000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000008000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000010000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000020000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000040000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000080000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000100000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000200000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000400000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000000800000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000001000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000002000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000004000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000008000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000010000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000020000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000040000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000080000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000100000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000200000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000400000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000000800000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000001000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000002000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000004000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000008000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000010000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000020000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000040000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000080000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000100000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000200000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000400000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000000800000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000001000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000002000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000004000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000008000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000010000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000020000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000040000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000080000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000100000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000200000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000400000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000000800000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000001000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000002000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000004000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000008000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000010000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000020000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000040000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000080000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000100000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000200000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000400000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000000800000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000001000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000002000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000004000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000008000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000010000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000020000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000040000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000080000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000100000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000200000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000400000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000000800000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000001000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000002000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000004000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000008000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000010000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000020000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000040000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000080000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000100000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000200000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000400000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000000800000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000001000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000002000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000004000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000008000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000010000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000020000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000040000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000080000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000100000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000200000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000400000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000000800000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000001000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000002000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000004000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000008000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000010000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000020000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000040000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000080000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000100000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000200000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000400000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000000800000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000001000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000002000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000004000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000008000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000010000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000020000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000040000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000080000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000100000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000200000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000400000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000000800000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000001000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000002000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000004000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000008000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000010000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000020000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000040000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000080000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000100000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000200000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000400000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0000800000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0001000000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0002000000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0004000000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0008000000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0010000000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0020000000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0040000000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0080000000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0200000000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0400000000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"0800000000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"1000000000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"2000000000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"4000000000000000000000000000000000000000000000000000000000000000"),
|
||||
ripple::uint256(
|
||||
"8000000000000000000000000000000000000000000000000000000000000000")};
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** UINT256_BIT[i] is the 256-bit value with only bit i set (LSB is bit 0).
|
||||
|
||||
Used to encode and test the per-transaction-type bits of the HookOn and
|
||||
HookCanEmit fields. Built at compile time.
|
||||
*/
|
||||
inline constexpr auto UINT256_BIT = []() consteval {
|
||||
std::array<ripple::uint256, 256> table{};
|
||||
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
std::array<std::uint8_t, 32> bytes{};
|
||||
bytes[31 - (i / 8)] = 1 << (i % 8);
|
||||
table[i] = ripple::uint256(bytes);
|
||||
}
|
||||
|
||||
return table;
|
||||
}();
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -1,374 +1,309 @@
|
||||
// int32_t _g(uint32_t guard_id, uint32_t maxiter);
|
||||
// clang-format off
|
||||
HOOK_API_DEFINITION(
|
||||
int32_t, _g, (uint32_t, uint32_t),
|
||||
int32_t, _g, (uint32_t guard_id, uint32_t maxiter),
|
||||
uint256{})
|
||||
|
||||
// int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, accept, (uint32_t, uint32_t, int64_t),
|
||||
int64_t, accept, (uint32_t read_ptr, uint32_t read_len, int64_t error_code),
|
||||
uint256{})
|
||||
|
||||
// int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, rollback, (uint32_t, uint32_t, int64_t),
|
||||
int64_t, rollback, (uint32_t read_ptr, uint32_t read_len, int64_t error_code),
|
||||
uint256{})
|
||||
|
||||
// int64_t util_raddr(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_raddr, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, util_raddr, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t util_accid(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_accid, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, util_accid, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t util_verify(uint32_t dread_ptr, uint32_t dread_len, uint32_t sread_ptr, uint32_t sread_len, uint32_t kread_ptr, uint32_t kread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_verify, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, util_verify, (uint32_t dread_ptr, uint32_t dread_len, uint32_t sread_ptr, uint32_t sread_len, uint32_t kread_ptr, uint32_t kread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t util_sha512h(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_sha512h, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, util_sha512h, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t util_keylet(uint32_t write_ptr, uint32_t write_len, uint32_t keylet_type, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_keylet, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, util_keylet, (uint32_t write_ptr, uint32_t write_len, uint32_t keylet_type, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f),
|
||||
uint256{})
|
||||
|
||||
// int64_t sto_validate(uint32_t tread_ptr, uint32_t tread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, sto_validate, (uint32_t, uint32_t),
|
||||
int64_t, sto_validate, (uint32_t tread_ptr, uint32_t tread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t sto_subfield(uint32_t read_ptr, uint32_t read_len, uint32_t field_id);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, sto_subfield, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, sto_subfield, (uint32_t read_ptr, uint32_t read_len, uint32_t field_id),
|
||||
uint256{})
|
||||
|
||||
// int64_t sto_subarray(uint32_t read_ptr, uint32_t read_len, uint32_t array_id);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, sto_subarray, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, sto_subarray, (uint32_t read_ptr, uint32_t read_len, uint32_t array_id),
|
||||
uint256{})
|
||||
|
||||
// int64_t sto_emplace(uint32_t write_ptr, uint32_t write_len, uint32_t sread_ptr, uint32_t sread_len, uint32_t fread_ptr, uint32_t fread_len, uint32_t field_id);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, sto_emplace, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, sto_emplace, (uint32_t write_ptr, uint32_t write_len, uint32_t sread_ptr, uint32_t sread_len, uint32_t fread_ptr, uint32_t fread_len, uint32_t field_id),
|
||||
uint256{})
|
||||
|
||||
// int64_t sto_erase(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len, uint32_t field_id);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, sto_erase, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, sto_erase, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len, uint32_t field_id),
|
||||
uint256{})
|
||||
|
||||
// int64_t etxn_burden();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_burden, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t etxn_details(uint32_t write_ptr, uint32_t write_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_details, (uint32_t, uint32_t),
|
||||
int64_t, etxn_details, (uint32_t write_ptr, uint32_t write_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t etxn_fee_base(uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_fee_base, (uint32_t, uint32_t),
|
||||
int64_t, etxn_fee_base, (uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t etxn_reserve(uint32_t count);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_reserve, (uint32_t),
|
||||
int64_t, etxn_reserve, (uint32_t count),
|
||||
uint256{})
|
||||
|
||||
// int64_t etxn_generation();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_generation, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t etxn_nonce(uint32_t write_ptr, uint32_t write_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_nonce, (uint32_t, uint32_t),
|
||||
int64_t, etxn_nonce, (uint32_t write_ptr, uint32_t write_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t emit(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, emit, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, emit, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_set(int32_t exponent, int64_t mantissa);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_set, (int32_t, int64_t),
|
||||
int64_t, float_set, (int32_t exponent, int64_t mantissa),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_multiply(int64_t float1, int64_t float2);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_multiply, (int64_t, int64_t),
|
||||
int64_t, float_multiply, (int64_t float1, int64_t float2),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_mulratio(int64_t float1, uint32_t round_up, uint32_t numerator, uint32_t denominator);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_mulratio, (int64_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, float_mulratio, (int64_t float1, uint32_t round_up, uint32_t numerator, uint32_t denominator),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_negate(int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_negate, (int64_t),
|
||||
int64_t, float_negate, (int64_t float1),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_compare(int64_t float1, int64_t float2, uint32_t mode);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_compare, (int64_t, int64_t, uint32_t),
|
||||
int64_t, float_compare, (int64_t float1, int64_t float2, uint32_t mode),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_sum(int64_t float1, int64_t float2);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_sum, (int64_t, int64_t),
|
||||
int64_t, float_sum, (int64_t float1, int64_t float2),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_sto(uint32_t write_ptr, uint32_t write_len, uint32_t cread_ptr, uint32_t cread_len, uint32_t iread_ptr, uint32_t iread_len, int64_t float1, uint32_t field_code);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_sto, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, int64_t, uint32_t),
|
||||
int64_t, float_sto, (uint32_t write_ptr, uint32_t write_len, uint32_t cread_ptr, uint32_t cread_len, uint32_t iread_ptr, uint32_t iread_len, int64_t float1, uint32_t field_code),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_sto_set(uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_sto_set, (uint32_t, uint32_t),
|
||||
int64_t, float_sto_set, (uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_invert(int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_invert, (int64_t),
|
||||
int64_t, float_invert, (int64_t float1),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_divide(int64_t float1, int64_t float2);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_divide, (int64_t, int64_t),
|
||||
int64_t, float_divide, (int64_t float1, int64_t float2),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_one();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_one, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_mantissa(int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_mantissa, (int64_t),
|
||||
int64_t, float_mantissa, (int64_t float1),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_sign(int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_sign, (int64_t),
|
||||
int64_t, float_sign, (int64_t float1),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_int(int64_t float1, uint32_t decimal_places, uint32_t abs);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_int, (int64_t, uint32_t, uint32_t),
|
||||
int64_t, float_int, (int64_t float1, uint32_t decimal_places, uint32_t abs),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_log(int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_log, (int64_t),
|
||||
int64_t, float_log, (int64_t float1),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_root(int64_t float1, uint32_t n);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_root, (int64_t, uint32_t),
|
||||
int64_t, float_root, (int64_t float1, uint32_t n),
|
||||
uint256{})
|
||||
|
||||
// int64_t fee_base();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, fee_base, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t ledger_seq();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, ledger_seq, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t ledger_last_time();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, ledger_last_time, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t ledger_last_hash(uint32_t write_ptr, uint32_t write_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, ledger_last_hash, (uint32_t, uint32_t),
|
||||
int64_t, ledger_last_hash, (uint32_t write_ptr, uint32_t write_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t ledger_nonce(uint32_t write_ptr, uint32_t write_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, ledger_nonce, (uint32_t, uint32_t),
|
||||
int64_t, ledger_nonce, (uint32_t write_ptr, uint32_t write_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t ledger_keylet(uint32_t write_ptr, uint32_t write_len, uint32_t lread_ptr, uint32_t lread_len, uint32_t hread_ptr, uint32_t hread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, ledger_keylet, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, ledger_keylet, (uint32_t write_ptr, uint32_t write_len, uint32_t lread_ptr, uint32_t lread_len, uint32_t hread_ptr, uint32_t hread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t hook_account(uint32_t write_ptr, uint32_t write_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_account, (uint32_t, uint32_t),
|
||||
int64_t, hook_account, (uint32_t write_ptr, uint32_t write_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t hook_hash(uint32_t write_ptr, uint32_t write_len, int32_t hook_no);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_hash, (uint32_t, uint32_t, int32_t),
|
||||
int64_t, hook_hash, (uint32_t write_ptr, uint32_t write_len, int32_t hook_no),
|
||||
uint256{})
|
||||
|
||||
// int64_t hook_param_set(uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t hread_ptr, uint32_t hread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_param_set, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, hook_param_set, (uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t hread_ptr, uint32_t hread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t hook_param(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_param, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, hook_param, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t hook_again();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_again, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t hook_skip(uint32_t read_ptr, uint32_t read_len, uint32_t flags);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_skip, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, hook_skip, (uint32_t read_ptr, uint32_t read_len, uint32_t flags),
|
||||
uint256{})
|
||||
|
||||
// int64_t hook_pos();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_pos, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot(uint32_t write_ptr, uint32_t write_len, uint32_t slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, slot, (uint32_t write_ptr, uint32_t write_len, uint32_t slot),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_clear(uint32_t slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_clear, (uint32_t),
|
||||
int64_t, slot_clear, (uint32_t slot),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_count(uint32_t slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_count, (uint32_t),
|
||||
int64_t, slot_count, (uint32_t slot),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_set(uint32_t read_ptr, uint32_t read_len, uint32_t slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_set, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, slot_set, (uint32_t read_ptr, uint32_t read_len, uint32_t slot),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_size(uint32_t slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_size, (uint32_t),
|
||||
int64_t, slot_size, (uint32_t slot),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_subarray(uint32_t parent_slot, uint32_t array_id, uint32_t new_slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_subarray, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, slot_subarray, (uint32_t parent_slot, uint32_t array_id, uint32_t new_slot),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_subfield(uint32_t parent_slot, uint32_t field_id, uint32_t new_slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_subfield, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, slot_subfield, (uint32_t parent_slot, uint32_t field_id, uint32_t new_slot),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_type(uint32_t slot_no, uint32_t flags);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_type, (uint32_t, uint32_t),
|
||||
int64_t, slot_type, (uint32_t slot_no, uint32_t flags),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_float(uint32_t slot_no);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_float, (uint32_t),
|
||||
int64_t, slot_float, (uint32_t slot_no),
|
||||
uint256{})
|
||||
|
||||
// int64_t state_set(uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, state_set, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, state_set, (uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t state_foreign_set(uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t nread_ptr, uint32_t nread_len, uint32_t aread_ptr, uint32_t aread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, state_foreign_set, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, state_foreign_set, (uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t nread_ptr, uint32_t nread_len, uint32_t aread_ptr, uint32_t aread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t state(uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, state, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, state, (uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t state_foreign(uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t nread_ptr, uint32_t nread_len, uint32_t aread_ptr, uint32_t aread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, state_foreign, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, state_foreign, (uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t nread_ptr, uint32_t nread_len, uint32_t aread_ptr, uint32_t aread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t trace(uint32_t mread_ptr, uint32_t mread_len, uint32_t dread_ptr, uint32_t dread_len, uint32_t as_hex);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, trace, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, trace, (uint32_t mread_ptr, uint32_t mread_len, uint32_t dread_ptr, uint32_t dread_len, uint32_t as_hex),
|
||||
uint256{})
|
||||
|
||||
// int64_t trace_num(uint32_t read_ptr, uint32_t read_len, int64_t number);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, trace_num, (uint32_t, uint32_t, int64_t),
|
||||
int64_t, trace_num, (uint32_t read_ptr, uint32_t read_len, int64_t number),
|
||||
uint256{})
|
||||
|
||||
// int64_t trace_float(uint32_t read_ptr, uint32_t read_len, int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, trace_float, (uint32_t, uint32_t, int64_t),
|
||||
int64_t, trace_float, (uint32_t read_ptr, uint32_t read_len, int64_t float1),
|
||||
uint256{})
|
||||
|
||||
// int64_t otxn_burden();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_burden, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t otxn_field(uint32_t write_ptr, uint32_t write_len, uint32_t field_id);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_field, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, otxn_field, (uint32_t write_ptr, uint32_t write_len, uint32_t field_id),
|
||||
uint256{})
|
||||
|
||||
// int64_t otxn_generation();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_generation, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t otxn_id(uint32_t write_ptr, uint32_t write_len, uint32_t flags);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_id, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, otxn_id, (uint32_t write_ptr, uint32_t write_len, uint32_t flags),
|
||||
uint256{})
|
||||
|
||||
// int64_t otxn_type();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_type, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t otxn_slot(uint32_t slot_no);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_slot, (uint32_t),
|
||||
int64_t, otxn_slot, (uint32_t slot_no),
|
||||
uint256{})
|
||||
|
||||
// int64_t otxn_param(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_param, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, otxn_param, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t meta_slot(uint32_t slot_no);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, meta_slot, (uint32_t),
|
||||
int64_t, meta_slot, (uint32_t slot_no),
|
||||
uint256{})
|
||||
|
||||
// int64_t xpop_slot(uint32_t slot_no_tx, uint32_t slot_no_meta);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, xpop_slot, (uint32_t, uint32_t),
|
||||
int64_t, xpop_slot, (uint32_t slot_no_tx, uint32_t slot_no_meta),
|
||||
featureHooksUpdate1)
|
||||
|
||||
// int64_t prepare(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, prepare, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, prepare, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len),
|
||||
featureHooksUpdate2)
|
||||
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, dice, (uint32_t sides),
|
||||
featureRNG)
|
||||
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_random, (uint32_t write_ptr, uint32_t write_len),
|
||||
featureRNG)
|
||||
// clang-format on
|
||||
@@ -215,7 +215,7 @@ Reader::parse(Value& root, BufferSequence const& bs)
|
||||
std::string s;
|
||||
s.reserve(buffer_size(bs));
|
||||
for (auto const& b : bs)
|
||||
s.append(buffer_cast<char const*>(b), buffer_size(b));
|
||||
s.append(static_cast<char const*>(b.data()), buffer_size(b));
|
||||
return parse(s, root);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or 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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_PROTOCOL_APPLOADER_H_INCLUDED
|
||||
#define RIPPLE_PROTOCOL_APPLOADER_H_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** Validation of an AppLoader document (sfAppLoader), the bootstrap page of
|
||||
a Progressive Web App served from an account.
|
||||
|
||||
Consensus cannot afford a real HTML parser, so this is a shallow sanity
|
||||
gate and nothing more. A document must be non-empty and at most
|
||||
maxAppLoaderLength bytes; well-formed UTF-8; free of C0 control
|
||||
characters other than TAB, LF, FF and CR, and of DEL; begin, after an
|
||||
optional BOM and whitespace, with an HTML doctype or an <html start tag;
|
||||
and contain an <html start tag followed later by a </html> end tag with
|
||||
nothing but whitespace after the last one. Nothing parses comments,
|
||||
scripts or attributes, and content is not policed.
|
||||
|
||||
Consensus critical: a change to what it accepts needs an amendment.
|
||||
*/
|
||||
namespace appLoader {
|
||||
|
||||
enum class Result : std::uint8_t {
|
||||
ok = 0,
|
||||
empty,
|
||||
tooLarge,
|
||||
badUTF8,
|
||||
badControlChar,
|
||||
noDoctype, // does not open with a doctype or <html tag
|
||||
noHtmlElement, // no <html start tag
|
||||
unclosed, // no </html> end tag after it
|
||||
trailingGarbage, // non-whitespace after the last </html>
|
||||
};
|
||||
|
||||
/** Total: every input yields a result. `data` may be null iff size is 0. */
|
||||
Result
|
||||
validate(std::uint8_t const* data, std::size_t size);
|
||||
|
||||
char const*
|
||||
to_string(Result r);
|
||||
|
||||
} // namespace appLoader
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -157,7 +157,12 @@ enum error_code_i {
|
||||
// Simulate
|
||||
rpcTX_SIGNED = 97,
|
||||
|
||||
rpcLAST = rpcTX_SIGNED // rpcLAST should always equal the last code.
|
||||
// ledger_entry
|
||||
rpcENTRY_NOT_FOUND = 98,
|
||||
rpcUNEXPECTED_LEDGER_TYPE = 99,
|
||||
|
||||
rpcLAST =
|
||||
rpcUNEXPECTED_LEDGER_TYPE // rpcLAST should always equal the last code.
|
||||
};
|
||||
|
||||
/** Codes returned in the `warnings` array of certain RPC commands.
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/base64.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/json/json_reader.h>
|
||||
#include <charconv>
|
||||
|
||||
@@ -118,8 +119,9 @@ syntaxCheckProof(
|
||||
{
|
||||
if (!proof["hash"].isString() ||
|
||||
proof["hash"].asString().size() != 64 ||
|
||||
!proof["key"].isString() ||
|
||||
!isHex(proof["hash"].asString()) || !proof["key"].isString() ||
|
||||
proof["key"].asString().size() != 64 ||
|
||||
!isHex(proof["key"].asString()) ||
|
||||
!proof["children"].isObject())
|
||||
{
|
||||
JLOG(j.warn()) << "XPOP.transaction.proof tree node has wrong "
|
||||
@@ -132,7 +134,11 @@ syntaxCheckProof(
|
||||
|
||||
for (const auto& branch : proof.getMemberNames())
|
||||
{
|
||||
if (branch.size() != 1 || !isHex(branch))
|
||||
// Upper case only: the txroot computation only looks up "0"-"F",
|
||||
// so any other key would be silently ignored.
|
||||
if (branch.size() != 1 ||
|
||||
branch.find_first_not_of("0123456789ABCDEF") !=
|
||||
std::string::npos)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "XPOP.transaction.proof child node was not 0-F "
|
||||
@@ -143,9 +149,9 @@ syntaxCheckProof(
|
||||
const auto& node = proof[branch];
|
||||
if (!node.isObject() || !node["hash"].isString() ||
|
||||
node["hash"].asString().size() != 64 ||
|
||||
!node["key"].isString() ||
|
||||
!isHex(node["hash"].asString()) || !node["key"].isString() ||
|
||||
node["key"].asString().size() != 64 ||
|
||||
!node["children"].isObject())
|
||||
!isHex(node["key"].asString()) || !node["children"].isObject())
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "XPOP.transaction.proof tree node has wrong format";
|
||||
@@ -465,6 +471,45 @@ getVLInfo(Json::Value const& xpop, beast::Journal const& j)
|
||||
return {{sequence, m->masterKey}};
|
||||
}
|
||||
|
||||
// <effective, expiration> of the unl blob, effective defaulting to the epoch
|
||||
inline std::optional<std::pair<NetClock::time_point, NetClock::time_point>>
|
||||
getVLWindow(Json::Value const& xpop, beast::Journal const& j)
|
||||
{
|
||||
auto const data =
|
||||
base64_decode(xpop[jss::validation][jss::unl][jss::blob].asString());
|
||||
Json::Reader r;
|
||||
Json::Value parsed;
|
||||
if (!r.parse(data, parsed) || !parsed.isObject())
|
||||
{
|
||||
JLOG(j.warn()) << "Import: unl blob was not a json object (after "
|
||||
"base64 decoding)";
|
||||
return {};
|
||||
}
|
||||
|
||||
// const, so that operator[] does not insert missing members
|
||||
Json::Value const& list = parsed;
|
||||
auto const time = [&list](Json::StaticString const& field)
|
||||
-> std::optional<NetClock::time_point> {
|
||||
auto const& v = list[field];
|
||||
if (!v.isInt() || v.asInt() < 0)
|
||||
return {};
|
||||
return NetClock::time_point{NetClock::duration{v.asUInt()}};
|
||||
};
|
||||
|
||||
auto const validUntil = time(jss::expiration);
|
||||
auto const validFrom = list.isMember(jss::effective)
|
||||
? time(jss::effective)
|
||||
: std::optional<NetClock::time_point>{NetClock::time_point{}};
|
||||
if (!validUntil || !validFrom)
|
||||
{
|
||||
JLOG(j.warn()) << "Import: unl blob json (after base64 decoding) "
|
||||
"had missing or invalid effective/expiration";
|
||||
return {};
|
||||
}
|
||||
|
||||
return {{*validFrom, *validUntil}};
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -118,6 +118,10 @@ negativeUNL() noexcept;
|
||||
Keylet const&
|
||||
UNLReport() noexcept;
|
||||
|
||||
/** The (fixed) index of the featureRNG random number generator object. */
|
||||
Keylet const&
|
||||
random() noexcept;
|
||||
|
||||
/** The beginning of an order book */
|
||||
struct book_t
|
||||
{
|
||||
@@ -328,6 +332,14 @@ xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq);
|
||||
Keylet
|
||||
did(AccountID const& account) noexcept;
|
||||
|
||||
/** An AppLoader belonging to an account.
|
||||
|
||||
There is at most one per account, so the key is derived from the owner's
|
||||
AccountID alone.
|
||||
*/
|
||||
Keylet
|
||||
appLoader(AccountID const& account) noexcept;
|
||||
|
||||
Keylet
|
||||
oracle(AccountID const& account, std::uint32_t const& documentID) noexcept;
|
||||
|
||||
@@ -376,6 +388,16 @@ permissionedDomain(uint256 const& domainID) noexcept;
|
||||
Keylet
|
||||
manifest(PublicKey const& pk) noexcept;
|
||||
|
||||
/** The directory listing every master key's ltMANIFEST object.
|
||||
|
||||
One entry per master key, made when its first manifest is published and
|
||||
kept for good: a newer manifest, a revocation included, replaces the object
|
||||
at the same key and keeps its place. The copy at the ephemeral key is not
|
||||
listed; it is named by the master object.
|
||||
*/
|
||||
Keylet const&
|
||||
manifestDir() noexcept;
|
||||
|
||||
} // namespace keylet
|
||||
|
||||
// Everything below is deprecated and should be removed in favor of keylets:
|
||||
@@ -406,7 +428,7 @@ struct keyletDesc
|
||||
|
||||
// This list should include all of the keylet functions that take a single
|
||||
// AccountID parameter.
|
||||
std::array<keyletDesc<AccountID const&>, 6> const directAccountKeylets{
|
||||
std::array<keyletDesc<AccountID const&>, 7> const directAccountKeylets{
|
||||
{{&keylet::account, jss::AccountRoot, false},
|
||||
{&keylet::ownerDir, jss::DirectoryNode, true},
|
||||
{&keylet::signers, jss::SignerList, true},
|
||||
@@ -414,7 +436,8 @@ std::array<keyletDesc<AccountID const&>, 6> const directAccountKeylets{
|
||||
// test it anyway, since the invariant checks for it.
|
||||
{&keylet::nftpage_min, jss::NFTokenPage, true},
|
||||
{&keylet::nftpage_max, jss::NFTokenPage, true},
|
||||
{&keylet::did, jss::DID, true}}};
|
||||
{&keylet::did, jss::DID, true},
|
||||
{&keylet::appLoader, jss::AppLoader, true}}};
|
||||
|
||||
MPTID
|
||||
makeMptID(std::uint32_t sequence, AccountID const& account);
|
||||
|
||||
@@ -84,6 +84,9 @@ std::uint16_t constexpr maxTransferFee = 50000;
|
||||
/** The maximum length of a URI inside an NFT */
|
||||
std::size_t constexpr maxTokenURILength = 256;
|
||||
|
||||
/** The maximum length of a URI inside an URIToken */
|
||||
std::size_t constexpr maxURITokenURILength = 256;
|
||||
|
||||
/** The maximum length of a Data element inside a DID */
|
||||
std::size_t constexpr maxDIDDocumentLength = 256;
|
||||
|
||||
@@ -96,6 +99,25 @@ std::size_t constexpr maxDIDAttestationLength = 256;
|
||||
/** The maximum length of a domain */
|
||||
std::size_t constexpr maxDomainLength = 256;
|
||||
|
||||
/** The maximum length of an AppLoader document.
|
||||
|
||||
The AppLoader holds the bootstrap document of a Progressive Web App
|
||||
served from an account. It is deliberately capped: the blob lives in a
|
||||
ledger object that carries no owner reserve at all, so the only things
|
||||
standing between it and unbounded ledger growth are this limit and the
|
||||
per-byte transaction fee below.
|
||||
*/
|
||||
std::size_t constexpr maxAppLoaderLength = 4096;
|
||||
|
||||
/** Drops of transaction fee charged per byte of AppLoader document.
|
||||
|
||||
Storage is paid for through the fee rather than through an owner
|
||||
reserve, so that a loader can be published by an account sitting at its
|
||||
reserve floor. The fee is charged on every AccountSet that carries the
|
||||
field, including overwrites.
|
||||
*/
|
||||
std::uint64_t constexpr appLoaderFeeDropsPerByte = 1;
|
||||
|
||||
/** The maximum length of a URI inside a Credential */
|
||||
std::size_t constexpr maxCredentialURILength = 256;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/STExchange.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/detail/KeyBase.h>
|
||||
#include <xrpl/protocol/json_get_or_throw.h>
|
||||
#include <xrpl/protocol/tokens.h>
|
||||
|
||||
@@ -36,6 +37,26 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** Returns the type of public key.
|
||||
|
||||
@return std::nullopt If the public key does not
|
||||
represent a known type.
|
||||
*/
|
||||
[[nodiscard]] inline std::optional<KeyType>
|
||||
publicKeyType(Slice const& slice)
|
||||
{
|
||||
if (slice.size() == 33)
|
||||
{
|
||||
if (slice[0] == 0xED)
|
||||
return KeyType::ed25519;
|
||||
|
||||
if (slice[0] == 0x02 || slice[0] == 0x03)
|
||||
return KeyType::secp256k1;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/** A public key.
|
||||
|
||||
Public keys are used in the public-key cryptography
|
||||
@@ -58,77 +79,47 @@ namespace ripple {
|
||||
prefix constant 0xED, followed by 32 bytes of
|
||||
public key data.
|
||||
*/
|
||||
class PublicKey
|
||||
class PublicKey : public detail::KeyBase<PublicKey, 33>
|
||||
{
|
||||
protected:
|
||||
// All the constructed public keys are valid, non-empty and contain 33
|
||||
// bytes of data.
|
||||
static constexpr std::size_t size_ = 33;
|
||||
std::uint8_t buf_[size_]; // should be large enough
|
||||
|
||||
public:
|
||||
using const_iterator = std::uint8_t const*;
|
||||
|
||||
public:
|
||||
PublicKey() = delete;
|
||||
|
||||
PublicKey(PublicKey const& other);
|
||||
PublicKey(PublicKey const&) = default;
|
||||
PublicKey&
|
||||
operator=(PublicKey const& other);
|
||||
operator=(PublicKey const&) = default;
|
||||
|
||||
/** Create a public key.
|
||||
|
||||
Preconditions:
|
||||
publicKeyType(slice) != std::nullopt
|
||||
*/
|
||||
explicit PublicKey(Slice const& slice);
|
||||
|
||||
std::uint8_t const*
|
||||
data() const noexcept
|
||||
explicit PublicKey(Slice const& slice) noexcept
|
||||
{
|
||||
return buf_;
|
||||
if (slice.size() < buf_.size())
|
||||
LogicError("PublicKey::PublicKey: undersized buffer");
|
||||
|
||||
if (!publicKeyType(slice))
|
||||
LogicError("PublicKey::PublicKey: invalid type");
|
||||
|
||||
std::copy_n(slice.data(), buf_.size(), buf_.data());
|
||||
}
|
||||
|
||||
std::size_t
|
||||
size() const noexcept
|
||||
explicit PublicKey(value_t const& data) noexcept
|
||||
: PublicKey(makeSlice(data))
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
const_iterator
|
||||
begin() const noexcept
|
||||
{
|
||||
return buf_;
|
||||
}
|
||||
|
||||
const_iterator
|
||||
cbegin() const noexcept
|
||||
{
|
||||
return buf_;
|
||||
}
|
||||
|
||||
const_iterator
|
||||
end() const noexcept
|
||||
{
|
||||
return buf_ + size_;
|
||||
}
|
||||
|
||||
const_iterator
|
||||
cend() const noexcept
|
||||
{
|
||||
return buf_ + size_;
|
||||
}
|
||||
|
||||
Slice
|
||||
[[nodiscard]] Slice
|
||||
slice() const noexcept
|
||||
{
|
||||
return {buf_, size_};
|
||||
return {buf_.data(), buf_.size()};
|
||||
}
|
||||
|
||||
operator Slice() const noexcept
|
||||
{
|
||||
return slice();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto
|
||||
operator<=>(PublicKey const& rhs) const
|
||||
{
|
||||
return buf_ <=> rhs.buf_;
|
||||
}
|
||||
};
|
||||
|
||||
/** Print the public key to a stream.
|
||||
@@ -136,22 +127,6 @@ public:
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, PublicKey const& pk);
|
||||
|
||||
inline bool
|
||||
operator==(PublicKey const& lhs, PublicKey const& rhs)
|
||||
{
|
||||
return std::memcmp(lhs.data(), rhs.data(), rhs.size()) == 0;
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator<(PublicKey const& lhs, PublicKey const& rhs)
|
||||
{
|
||||
return std::lexicographical_compare(
|
||||
lhs.data(),
|
||||
lhs.data() + lhs.size(),
|
||||
rhs.data(),
|
||||
rhs.data() + rhs.size());
|
||||
}
|
||||
|
||||
template <class Hasher>
|
||||
void
|
||||
hash_append(Hasher& h, PublicKey const& pk)
|
||||
@@ -221,22 +196,6 @@ enum class ECDSACanonicality { canonical, fullyCanonical };
|
||||
std::optional<ECDSACanonicality>
|
||||
ecdsaCanonicality(Slice const& sig);
|
||||
|
||||
/** Returns the type of public key.
|
||||
|
||||
@return std::nullopt If the public key does not
|
||||
represent a known type.
|
||||
*/
|
||||
/** @{ */
|
||||
[[nodiscard]] std::optional<KeyType>
|
||||
publicKeyType(Slice const& slice);
|
||||
|
||||
[[nodiscard]] inline std::optional<KeyType>
|
||||
publicKeyType(PublicKey const& publicKey)
|
||||
{
|
||||
return publicKeyType(publicKey.slice());
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/** Verify a secp256k1 signature on the digest of a message. */
|
||||
[[nodiscard]] bool
|
||||
verifyDigest(
|
||||
|
||||
@@ -25,90 +25,39 @@
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/detail/KeyBase.h>
|
||||
#include <xrpl/protocol/tokens.h>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** A secret key. */
|
||||
class SecretKey
|
||||
class SecretKey : public detail::KeyBase<SecretKey, 32>
|
||||
{
|
||||
private:
|
||||
std::uint8_t buf_[32];
|
||||
|
||||
public:
|
||||
using const_iterator = std::uint8_t const*;
|
||||
|
||||
SecretKey() = delete;
|
||||
|
||||
SecretKey(SecretKey const&) = default;
|
||||
SecretKey&
|
||||
operator=(SecretKey const&) = default;
|
||||
|
||||
/** Destroy the secret key. This will try to securely erase the buffer. */
|
||||
~SecretKey();
|
||||
|
||||
SecretKey(std::array<std::uint8_t, 32> const& data);
|
||||
SecretKey(Slice const& slice);
|
||||
|
||||
std::uint8_t const*
|
||||
data() const
|
||||
SecretKey(Slice const& slice)
|
||||
{
|
||||
return buf_;
|
||||
if (slice.size() != buf_.size())
|
||||
LogicError("SecretKey::SecretKey: invalid size");
|
||||
|
||||
std::copy_n(slice.data(), buf_.size(), buf_.data());
|
||||
}
|
||||
|
||||
std::size_t
|
||||
size() const
|
||||
explicit SecretKey(span_t const& data) noexcept : KeyBase(data)
|
||||
{
|
||||
return sizeof(buf_);
|
||||
}
|
||||
|
||||
/** Convert the secret key to a hexadecimal string.
|
||||
|
||||
@note The operator<< function is deliberately omitted
|
||||
to avoid accidental exposure of secret key material.
|
||||
*/
|
||||
std::string
|
||||
to_string() const;
|
||||
|
||||
const_iterator
|
||||
begin() const noexcept
|
||||
{
|
||||
return buf_;
|
||||
}
|
||||
|
||||
const_iterator
|
||||
cbegin() const noexcept
|
||||
{
|
||||
return buf_;
|
||||
}
|
||||
|
||||
const_iterator
|
||||
end() const noexcept
|
||||
{
|
||||
return buf_ + sizeof(buf_);
|
||||
}
|
||||
|
||||
const_iterator
|
||||
cend() const noexcept
|
||||
{
|
||||
return buf_ + sizeof(buf_);
|
||||
}
|
||||
};
|
||||
|
||||
inline bool
|
||||
operator==(SecretKey const& lhs, SecretKey const& rhs)
|
||||
{
|
||||
return lhs.size() == rhs.size() &&
|
||||
std::memcmp(lhs.data(), rhs.data(), rhs.size()) == 0;
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator!=(SecretKey const& lhs, SecretKey const& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Parse a secret key */
|
||||
|
||||
@@ -22,72 +22,37 @@
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/protocol/detail/KeyBase.h>
|
||||
#include <xrpl/protocol/tokens.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** Seeds are used to generate deterministic secret keys. */
|
||||
class Seed
|
||||
class Seed : public detail::KeyBase<Seed, 16>
|
||||
{
|
||||
private:
|
||||
std::array<uint8_t, 16> buf_;
|
||||
|
||||
public:
|
||||
using const_iterator = std::array<uint8_t, 16>::const_iterator;
|
||||
|
||||
Seed() = delete;
|
||||
|
||||
Seed(Seed const&) = default;
|
||||
Seed&
|
||||
operator=(Seed const&) = default;
|
||||
|
||||
/** Destroy the seed.
|
||||
The buffer will first be securely erased.
|
||||
*/
|
||||
/** Destroy the seed. This will attempt to securely erase the buffer. */
|
||||
~Seed();
|
||||
|
||||
/** Construct a seed */
|
||||
/** @{ */
|
||||
explicit Seed(Slice const& slice);
|
||||
explicit Seed(uint128 const& seed);
|
||||
/** @} */
|
||||
|
||||
std::uint8_t const*
|
||||
data() const
|
||||
explicit Seed(Slice const& slice)
|
||||
{
|
||||
return buf_.data();
|
||||
if (slice.size() != buf_.size())
|
||||
LogicError("Seed::Seed: invalid size");
|
||||
|
||||
std::copy_n(slice.data(), buf_.size(), buf_.data());
|
||||
}
|
||||
|
||||
std::size_t
|
||||
size() const
|
||||
explicit Seed(span_t seed) noexcept : KeyBase(seed)
|
||||
{
|
||||
return buf_.size();
|
||||
}
|
||||
|
||||
const_iterator
|
||||
begin() const noexcept
|
||||
{
|
||||
return buf_.begin();
|
||||
}
|
||||
|
||||
const_iterator
|
||||
cbegin() const noexcept
|
||||
{
|
||||
return buf_.cbegin();
|
||||
}
|
||||
|
||||
const_iterator
|
||||
end() const noexcept
|
||||
{
|
||||
return buf_.end();
|
||||
}
|
||||
|
||||
const_iterator
|
||||
cend() const noexcept
|
||||
{
|
||||
return buf_.cend();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -24,18 +24,14 @@
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
// Various protocol and system specific constant globals.
|
||||
|
||||
/* The name of the system. */
|
||||
static inline std::string const&
|
||||
systemName()
|
||||
{
|
||||
static std::string const name = "xahau";
|
||||
return name;
|
||||
}
|
||||
inline constexpr std::string_view systemName = "xahau";
|
||||
|
||||
/** Configure the native currency. */
|
||||
|
||||
@@ -58,12 +54,7 @@ isLegalAmountSigned(XRPAmount const& amount)
|
||||
}
|
||||
|
||||
/* The currency code for the native currency. */
|
||||
static inline std::string const&
|
||||
systemCurrencyCode()
|
||||
{
|
||||
static std::string const code = "XAH";
|
||||
return code;
|
||||
}
|
||||
inline constexpr std::string_view systemCurrencyCode = "XAH";
|
||||
|
||||
/** The XRP ledger network's earliest allowed sequence */
|
||||
static constexpr std::uint32_t XRP_LEDGER_EARLIEST_SEQ{1U};
|
||||
|
||||
@@ -69,6 +69,7 @@ enum TELcodes : TERUnderlyingType {
|
||||
telIMPORT_VL_KEY_NOT_RECOGNISED,
|
||||
telCAN_NOT_QUEUE_IMPORT,
|
||||
telENV_RPC_FAILED,
|
||||
telMANIFEST_FEE_MISMATCH,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -196,6 +197,7 @@ enum TEFcodes : TERUnderlyingType {
|
||||
tefINVALID_LEDGER_FIX_TYPE,
|
||||
tefPAST_MANIFEST_SEQ,
|
||||
tefREVOKED_MANIFEST,
|
||||
tefMANIFEST_KEY_IN_USE,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -152,6 +152,26 @@ public:
|
||||
return static_cast<bool>(mHookEmissions);
|
||||
}
|
||||
|
||||
/** featureRNG: the RNG state this transaction's first Hook draw saw.
|
||||
Every value the transaction's Hooks drew is derived from it. */
|
||||
void
|
||||
setRandomData(uint256 const& randomData)
|
||||
{
|
||||
mRandomData = randomData;
|
||||
}
|
||||
|
||||
bool
|
||||
hasRandomData() const
|
||||
{
|
||||
return static_cast<bool>(mRandomData);
|
||||
}
|
||||
|
||||
uint256 const&
|
||||
getRandomData() const
|
||||
{
|
||||
return *mRandomData;
|
||||
}
|
||||
|
||||
STAmount
|
||||
getDeliveredAmount() const
|
||||
{
|
||||
@@ -176,6 +196,7 @@ private:
|
||||
std::optional<STAmount> mDelivered;
|
||||
std::optional<STArray> mHookExecutions;
|
||||
std::optional<STArray> mHookEmissions;
|
||||
std::optional<uint256> mRandomData;
|
||||
|
||||
STArray mNodes;
|
||||
};
|
||||
|
||||
@@ -24,84 +24,166 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/SystemParameters.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace detail {
|
||||
|
||||
class CurrencyTag
|
||||
{
|
||||
public:
|
||||
explicit CurrencyTag() = default;
|
||||
};
|
||||
|
||||
class DirectoryTag
|
||||
{
|
||||
public:
|
||||
explicit DirectoryTag() = default;
|
||||
};
|
||||
|
||||
class NodeIDTag
|
||||
{
|
||||
public:
|
||||
explicit NodeIDTag() = default;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
using LedgerHash = uint256;
|
||||
|
||||
/** Directory is an index into the directory of offer books.
|
||||
The last 64 bits of this are the quality. */
|
||||
using Directory = base_uint<256, detail::DirectoryTag>;
|
||||
using Directory = base_uint<256, struct DirectoryTag>;
|
||||
|
||||
/** Currency is a hash representing a specific currency. */
|
||||
using Currency = base_uint<160, detail::CurrencyTag>;
|
||||
using Currency = base_uint<160, struct CurrencyTag>;
|
||||
|
||||
/** NodeID is a 160-bit hash representing one node. */
|
||||
using NodeID = base_uint<160, detail::NodeIDTag>;
|
||||
using NodeID = base_uint<160, struct NodeIDTag>;
|
||||
|
||||
/** MPTID is a 192-bit value representing MPT Issuance ID,
|
||||
* which is a concatenation of a 32-bit sequence (big endian)
|
||||
* and a 160-bit account */
|
||||
/** A Multi-Purpose Token Issuance ID,
|
||||
|
||||
The ID is the a concatenation of a 32-bit sequence number,
|
||||
in big endian, and a 160-bit account.
|
||||
|
||||
@note This type is, unfortunately, untagged because the authors
|
||||
of the original code used a deserialization APIs that did
|
||||
not support tags. It should be fixed.
|
||||
*/
|
||||
using MPTID = base_uint<192>;
|
||||
|
||||
namespace detail {
|
||||
|
||||
// A table mapping which characters we are willing to allow in the ASCII
|
||||
// representation of a three-letter currency code.
|
||||
inline constexpr auto validIsoChars = []() consteval {
|
||||
std::string_view isoCharSet =
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"0123456789"
|
||||
"<>(){}[]|?!@#$%^&*";
|
||||
|
||||
std::array<bool, 256> table{};
|
||||
|
||||
for (unsigned char c : isoCharSet)
|
||||
table[c] = true;
|
||||
|
||||
return table;
|
||||
}();
|
||||
|
||||
// Determines if the given code is composed entirely of letters valid in
|
||||
// ISO 4217 codes.
|
||||
[[nodiscard]] inline constexpr bool
|
||||
isIsoCode(std::string_view code) noexcept
|
||||
{
|
||||
return std::all_of(code.begin(), code.end(), [](char c) {
|
||||
return validIsoChars[static_cast<unsigned char>(c)];
|
||||
});
|
||||
}
|
||||
|
||||
// The location (in bytes) of the 3 digit currency inside a 160-bit value
|
||||
inline constexpr std::size_t isoCodeOffset = 12;
|
||||
|
||||
// The length of an ISO-4217 like code
|
||||
inline constexpr std::size_t isoCodeLength = 3;
|
||||
|
||||
inline constexpr Currency isoMaskBits = ~Currency(0xFFFFFF0000000000);
|
||||
|
||||
// The special currency identifier for the system currency: all-zero
|
||||
inline constexpr Currency xrpCurrency{0x0000000000000000};
|
||||
|
||||
// The special currency identifier meaning "no currency"
|
||||
inline constexpr Currency noCurrency{0x0000000000000001};
|
||||
|
||||
// The system currency identifier in "ISO4217 format" which is reserved.
|
||||
// We derive this value dynamically from the system currency code.
|
||||
inline constexpr Currency badCurrency = []() consteval {
|
||||
if (systemCurrencyCode.size() != isoCodeLength)
|
||||
throw "Incorrect systemCurrencyCode size (must be 3 digits)";
|
||||
|
||||
std::array<std::uint8_t, Currency::bytes> bytes{};
|
||||
|
||||
for (std::size_t i = 0; i < isoCodeLength; ++i)
|
||||
bytes[isoCodeOffset + i] =
|
||||
static_cast<std::uint8_t>(systemCurrencyCode[i]);
|
||||
|
||||
return Currency(bytes);
|
||||
}();
|
||||
|
||||
// We take advantage of the fact that an ASCII value in the [a-z] range
|
||||
// transforms into the equivalent character in the [A-Z] range when you
|
||||
// AND it with 0xDF, so we can compare against all possible variants of
|
||||
// "XAH" in one go. Every byte outside the ISO code position is kept as-is
|
||||
// by the mask, so a currency is only "bad" when those bytes are all zero
|
||||
// (i.e. the value is exactly one of the case variants of the code).
|
||||
inline constexpr Currency badCurrencyCodeMask =
|
||||
isoMaskBits | Currency{0xDFDFDF0000000000};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/** XRP currency. */
|
||||
Currency const&
|
||||
xrpCurrency();
|
||||
[[nodiscard]] constexpr Currency const&
|
||||
xrpCurrency() noexcept
|
||||
{
|
||||
return detail::xrpCurrency;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool
|
||||
isXRP(Currency const& c) noexcept
|
||||
{
|
||||
return c == detail::xrpCurrency;
|
||||
}
|
||||
|
||||
/** A placeholder for empty currencies. */
|
||||
Currency const&
|
||||
noCurrency();
|
||||
[[nodiscard]] constexpr Currency const&
|
||||
noCurrency() noexcept
|
||||
{
|
||||
return detail::noCurrency;
|
||||
}
|
||||
|
||||
/** We deliberately disallow the currency that looks like "XAH" because too
|
||||
many people were using it instead of the correct XAH currency. */
|
||||
Currency const&
|
||||
badCurrency();
|
||||
|
||||
inline bool
|
||||
isXRP(Currency const& c)
|
||||
[[nodiscard]] constexpr Currency const&
|
||||
badCurrency() noexcept
|
||||
{
|
||||
return c == beast::zero;
|
||||
return detail::badCurrency;
|
||||
}
|
||||
|
||||
inline bool
|
||||
isBadCurrency(Currency const& c)
|
||||
[[nodiscard]] constexpr bool
|
||||
isBadCurrency(Currency const& c) noexcept
|
||||
{
|
||||
static const std::set<Currency> badCurrencies{
|
||||
Currency(0x7861680000000000), // xah
|
||||
Currency(0x7861480000000000), // xaH
|
||||
Currency(0x7841680000000000), // xAh
|
||||
Currency(0x7841480000000000), // xAH
|
||||
Currency(0x5861680000000000), // Xah
|
||||
Currency(0x5861480000000000), // XaH
|
||||
Currency(0x5841680000000000), // XAh
|
||||
Currency(0x5841480000000000) // XAH
|
||||
};
|
||||
|
||||
return badCurrencies.find(c) != badCurrencies.end();
|
||||
// We take advantage of the fact that an ASCII value in the [a-z] range
|
||||
// transforms into the equivalent character in the [A-Z] range when you
|
||||
// AND it with 0xDF, so we can compare against all possible variants of
|
||||
// the bad currency code in one go:
|
||||
return (c & detail::badCurrencyCodeMask) == badCurrency();
|
||||
}
|
||||
|
||||
/** Returns "", "XAH", or three letter ISO code. */
|
||||
std::string
|
||||
to_string(Currency const& c);
|
||||
/** Returns "", "XAH", three letter ISO code or the hex representation. */
|
||||
[[nodiscard]] inline std::string
|
||||
to_string(Currency const& currency)
|
||||
{
|
||||
if (currency == xrpCurrency())
|
||||
return std::string{systemCurrencyCode};
|
||||
|
||||
if (currency == noCurrency())
|
||||
return "1";
|
||||
|
||||
if ((currency & detail::isoMaskBits) == beast::zero)
|
||||
{
|
||||
std::string_view const iso(
|
||||
reinterpret_cast<char const*>(currency.data()) +
|
||||
detail::isoCodeOffset,
|
||||
detail::isoCodeLength);
|
||||
|
||||
// Specifying the system currency code using ISO-style representation
|
||||
// is not allowed. Note that the check is case-sensitive; yet another
|
||||
// instance of legacy code smell.
|
||||
if (detail::isIsoCode(iso) && iso != systemCurrencyCode)
|
||||
return std::string{iso};
|
||||
}
|
||||
|
||||
return strHex(currency);
|
||||
}
|
||||
|
||||
/** Tries to convert a string to a Currency, returns true on success.
|
||||
|
||||
@@ -110,8 +192,29 @@ to_string(Currency const& c);
|
||||
will require very careful checking everywhere and may mean having
|
||||
to rewrite some unit test code.
|
||||
*/
|
||||
bool
|
||||
to_currency(Currency&, std::string const&);
|
||||
[[nodiscard]] constexpr bool
|
||||
to_currency(Currency& currency, std::string_view code) noexcept
|
||||
{
|
||||
if (code.empty() || code == systemCurrencyCode)
|
||||
{
|
||||
currency = xrpCurrency();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle ISO-4217-like 3-digit character codes.
|
||||
if (code.size() != detail::isoCodeLength)
|
||||
return currency.parseHex(code);
|
||||
|
||||
if (!detail::isIsoCode(code))
|
||||
return false;
|
||||
|
||||
currency = beast::zero;
|
||||
|
||||
std::copy_n(
|
||||
code.data(), code.size(), currency.begin() + detail::isoCodeOffset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Tries to convert a string to a Currency, returns noCurrency() on failure.
|
||||
|
||||
@@ -119,8 +222,14 @@ to_currency(Currency&, std::string const&);
|
||||
unfortunate; changing this will require very careful checking
|
||||
everywhere and may mean having to rewrite some unit test code.
|
||||
*/
|
||||
Currency
|
||||
to_currency(std::string const&);
|
||||
[[nodiscard]] constexpr Currency
|
||||
to_currency(std::string_view code) noexcept
|
||||
{
|
||||
if (Currency currency; to_currency(currency, code))
|
||||
return currency;
|
||||
|
||||
return noCurrency();
|
||||
}
|
||||
|
||||
inline std::ostream&
|
||||
operator<<(std::ostream& os, Currency const& x)
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of xahaud: https://github.com/xahau/xahaud
|
||||
Copyright (c) 2026, the Xahaud developers.
|
||||
|
||||
Permission to use, copy, modify, and/or 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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef XAHAU_PROTOCOL_KEYBASE_H_INCLUDED
|
||||
#define XAHAU_PROTOCOL_KEYBASE_H_INCLUDED
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
namespace ripple {
|
||||
namespace detail {
|
||||
|
||||
/** Common base for fixed-size key and seed types.
|
||||
|
||||
This CRTP base provides storage, accessors, and equality comparison
|
||||
for types like seeds and secret/public keys, which wrap a fixed-size
|
||||
byte array. The Derived parameter ensures that unrelated types with
|
||||
the same size do not implicitly interoperate with one another.
|
||||
|
||||
Equality comparison is provided automatically. Derived classes that
|
||||
want to can opt in to ordering by declaring operator<=> as a
|
||||
hidden friend.
|
||||
|
||||
@tparam Derived The concrete type inheriting from this base (CRTP).
|
||||
@tparam N The size of the underlying byte array.
|
||||
*/
|
||||
template <class Derived, std::size_t N>
|
||||
class KeyBase
|
||||
{
|
||||
public:
|
||||
/** The type we use to store the data. */
|
||||
using value_t = std::array<std::uint8_t, N>;
|
||||
|
||||
/** The span type corresponding to the type we use to store the data. */
|
||||
using span_t = std::span<std::uint8_t, N>;
|
||||
|
||||
protected:
|
||||
value_t buf_;
|
||||
|
||||
KeyBase() noexcept = default;
|
||||
|
||||
explicit KeyBase(span_t data) noexcept
|
||||
{
|
||||
std::copy_n(data.data(), N, buf_.data());
|
||||
}
|
||||
|
||||
public:
|
||||
[[nodiscard]] std::uint8_t const*
|
||||
data() const
|
||||
{
|
||||
return buf_.data();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t
|
||||
size() const
|
||||
{
|
||||
return buf_.size();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto
|
||||
begin() const noexcept
|
||||
{
|
||||
return buf_.begin();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto
|
||||
cbegin() const noexcept
|
||||
{
|
||||
return buf_.cbegin();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto
|
||||
end() const noexcept
|
||||
{
|
||||
return buf_.end();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto
|
||||
cend() const noexcept
|
||||
{
|
||||
return buf_.cend();
|
||||
}
|
||||
|
||||
[[nodiscard]] friend bool
|
||||
operator==(Derived const& lhs, Derived const& rhs)
|
||||
{
|
||||
// This is not a constant time comparison. This is probably OK
|
||||
// for the xahaud codebase.
|
||||
return lhs.buf_ == rhs.buf_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -34,9 +34,13 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FIX (20261001, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(RNG, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PWALoader, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (20261005, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(EscrowDestinationCancel, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (20260929, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(HookOnV2_1, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(OnChainManifests, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(OnChainManifests, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (HookMap, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (GuardDepth32, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(NamedHooks, Supported::yes, VoteBehavior::DefaultNo)
|
||||
@@ -86,7 +90,6 @@ XRPL_FIX (ReduceImport, Supported::yes, VoteBehavior::DefaultYe
|
||||
XRPL_FEATURE(Touch, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Remarks, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (FloatDivide, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (240911, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (PageCap, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (240819, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (NSDelete, Supported::yes, VoteBehavior::DefaultNo)
|
||||
@@ -162,6 +165,7 @@ XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete)
|
||||
// pre-amendment code has been removed and the identifiers are deprecated.
|
||||
// All known amendments and amendments that may appear in a validated
|
||||
// ledger must be registered either here or above with the "active" amendments
|
||||
XRPL_RETIRE(fix240911)
|
||||
XRPL_RETIRE(fixXahauV2)
|
||||
XRPL_RETIRE(fixXahauV1)
|
||||
XRPL_RETIRE(MultiSign)
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
#endif
|
||||
|
||||
#ifndef LEDGER_ENTRY_DUPLICATE
|
||||
// The EXPAND macro is needed for Windows
|
||||
// The LEDGER_ENTRY_EXPAND macro is needed for Windows
|
||||
// https://stackoverflow.com/questions/5134523/msvc-doesnt-expand-va-args-correctly
|
||||
#define EXPAND(x) x
|
||||
#define LEDGER_ENTRY_EXPAND(x) x
|
||||
|
||||
// The `LEDGER_ENTRY_DUPLICATE macro is needed to avoid JSS conflicts
|
||||
// Since some transactions and ledger entries have the same name (like `DepositPreauth`)
|
||||
// The compiler won't accept two instances of `JSS(DepositPreauth)`
|
||||
#define LEDGER_ENTRY_DUPLICATE(...) EXPAND(LEDGER_ENTRY(__VA_ARGS__))
|
||||
#define LEDGER_ENTRY_DUPLICATE(...) LEDGER_ENTRY_EXPAND(LEDGER_ENTRY(__VA_ARGS__))
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -142,6 +142,22 @@ LEDGER_ENTRY(ltIMPORT_VLSEQ, 0x0049, ImportVLSequence, import_vlseq, ({
|
||||
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** The bootstrap document of a Progressive Web App (featurePWALoader).
|
||||
|
||||
At most one per account, keyed on the owner alone (keylet::appLoader).
|
||||
It is in no owner directory and takes no reserve: its storage is paid
|
||||
for by a per-byte fee on the AccountSet that writes it, and
|
||||
DeleteAccount erases it explicitly. AppLoaderSequence equals the owner's
|
||||
AccountRoot AppLoaderSequence as of the AccountSet that wrote it.
|
||||
*/
|
||||
LEDGER_ENTRY(ltAPP_LOADER, 0x004c, AppLoader, app_loader, ({
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfAppLoader, soeREQUIRED},
|
||||
{sfAppLoaderSequence, soeREQUIRED},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** The ledger object which tracks the current negative UNL state.
|
||||
|
||||
\note This is a singleton: only one such object exists in the ledger.
|
||||
@@ -180,6 +196,26 @@ LEDGER_ENTRY_DUPLICATE(ltUNL_REPORT, 0x0052, UNLReport, unl_report, ({
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** featureRNG: the consensus random number generator.
|
||||
|
||||
A singleton. sfRandomData is seeded from validator reveals by the
|
||||
ttENTROPY transactions applied at the start of each ledger, and then
|
||||
iterated by every Hook API draw for the rest of that ledger.
|
||||
sfRandomData and sfRandomDigests are never written to metadata, and
|
||||
Hooks cannot load this object.
|
||||
|
||||
\sa keylet::random
|
||||
*/
|
||||
LEDGER_ENTRY(ltRANDOM, 0x526E, Random, random, ({
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
{sfRandomData, soeREQUIRED},
|
||||
{sfEntropyCount, soeREQUIRED},
|
||||
{sfEntropyDenominator, soeREQUIRED},
|
||||
{sfRandomDigests, soeREQUIRED},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** A ledger object which contains a signer list for an account.
|
||||
|
||||
\sa keylet::signers
|
||||
@@ -262,6 +298,7 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
|
||||
{sfHookStateScale, soeOPTIONAL},
|
||||
{sfCron, soeOPTIONAL},
|
||||
{sfAMMID, soeOPTIONAL},
|
||||
{sfAppLoaderSequence, soeOPTIONAL},
|
||||
{sfManifestID, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
@@ -609,10 +646,11 @@ LEDGER_ENTRY(ltMANIFEST, 0x004D, Manifest, manifest_entry, ({
|
||||
{sfMasterSignature, soeREQUIRED},
|
||||
{sfSignature, soeOPTIONAL}, /* absent if the master is revoked */
|
||||
{sfManifestID, soeOPTIONAL}, /* pointer to the identical other copy on the other key */
|
||||
{sfOwnerNode, soeOPTIONAL}, /* master copy only: its page in keylet::manifestDir() */
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
#undef EXPAND
|
||||
#undef LEDGER_ENTRY_EXPAND
|
||||
#undef LEDGER_ENTRY_DUPLICATE
|
||||
|
||||
@@ -59,6 +59,8 @@ TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19)
|
||||
TYPED_SFIELD(sfHookApiVersion, UINT16, 20)
|
||||
TYPED_SFIELD(sfHookStateScale, UINT16, 21)
|
||||
TYPED_SFIELD(sfLedgerFixType, UINT16, 22)
|
||||
TYPED_SFIELD(sfEntropyCount, UINT16, 99)
|
||||
TYPED_SFIELD(sfEntropyDenominator, UINT16, 100)
|
||||
|
||||
// 32-bit integers (common)
|
||||
TYPED_SFIELD(sfNetworkID, UINT32, 1)
|
||||
@@ -123,6 +125,7 @@ TYPED_SFIELD(sfImportSequence, UINT32, 97)
|
||||
TYPED_SFIELD(sfRewardTime, UINT32, 98)
|
||||
TYPED_SFIELD(sfRewardLgrFirst, UINT32, 99)
|
||||
TYPED_SFIELD(sfRewardLgrLast, UINT32, 100)
|
||||
TYPED_SFIELD(sfAppLoaderSequence, UINT32, 101)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -210,6 +213,9 @@ TYPED_SFIELD(sfOfferID, UINT256, 34)
|
||||
TYPED_SFIELD(sfEscrowID, UINT256, 35)
|
||||
TYPED_SFIELD(sfURITokenID, UINT256, 36)
|
||||
TYPED_SFIELD(sfDomainID, UINT256, 37)
|
||||
TYPED_SFIELD(sfNextRandomDigest, UINT256, 89)
|
||||
// RNG running state: never written to metadata (see featureRNG)
|
||||
TYPED_SFIELD(sfRandomData, UINT256, 90, SField::sMD_Never)
|
||||
TYPED_SFIELD(sfManifestID, UINT256, 91)
|
||||
TYPED_SFIELD(sfHookOnOutgoing, UINT256, 93)
|
||||
TYPED_SFIELD(sfHookOnIncoming, UINT256, 94)
|
||||
@@ -294,6 +300,7 @@ TYPED_SFIELD(sfAssetClass, VL, 29)
|
||||
TYPED_SFIELD(sfProvider, VL, 30)
|
||||
TYPED_SFIELD(sfMPTokenMetadata, VL, 31)
|
||||
TYPED_SFIELD(sfCredentialType, VL, 32)
|
||||
TYPED_SFIELD(sfAppLoader, VL, 96)
|
||||
TYPED_SFIELD(sfHookName, VL, 97)
|
||||
TYPED_SFIELD(sfRemarkValue, VL, 98)
|
||||
TYPED_SFIELD(sfRemarkName, VL, 99)
|
||||
@@ -383,6 +390,7 @@ UNTYPED_SFIELD(sfXChainClaimAttestationCollectionElement, OBJECT, 30)
|
||||
UNTYPED_SFIELD(sfXChainCreateAccountAttestationCollectionElement, OBJECT, 31)
|
||||
UNTYPED_SFIELD(sfPriceData, OBJECT, 32)
|
||||
UNTYPED_SFIELD(sfCredential, OBJECT, 33)
|
||||
UNTYPED_SFIELD(sfRandomDigestEntry, OBJECT, 88)
|
||||
UNTYPED_SFIELD(sfManifest, OBJECT, 90)
|
||||
UNTYPED_SFIELD(sfAmountEntry, OBJECT, 91)
|
||||
UNTYPED_SFIELD(sfMintURIToken, OBJECT, 92)
|
||||
@@ -423,6 +431,8 @@ UNTYPED_SFIELD(sfAuthAccounts, ARRAY, 25)
|
||||
UNTYPED_SFIELD(sfAuthorizeCredentials, ARRAY, 26)
|
||||
UNTYPED_SFIELD(sfUnauthorizeCredentials, ARRAY, 27)
|
||||
UNTYPED_SFIELD(sfAcceptedCredentials, ARRAY, 28)
|
||||
// RNG validator commitments: never written to metadata (see featureRNG)
|
||||
UNTYPED_SFIELD(sfRandomDigests, ARRAY, 91, SField::sMD_Never)
|
||||
UNTYPED_SFIELD(sfAmounts, ARRAY, 92)
|
||||
UNTYPED_SFIELD(sfHookEmissions, ARRAY, 93)
|
||||
UNTYPED_SFIELD(sfImportVLKeys, ARRAY, 94)
|
||||
|
||||
@@ -74,6 +74,7 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet, ({
|
||||
{sfTickSize, soeOPTIONAL},
|
||||
{sfNFTokenMinter, soeOPTIONAL},
|
||||
{sfHookStateScale, soeOPTIONAL},
|
||||
{sfAppLoader, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type cancels an existing escrow. */
|
||||
@@ -500,6 +501,22 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({
|
||||
{sfDomainID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** featureRNG: a validator's entropy contribution for one ledger.
|
||||
|
||||
Each proposing UNLReport validator places exactly one of these in its own
|
||||
initial position when it closes the ledger. It reveals the secret behind
|
||||
the validator's commitment currently stored in the Random object (if any)
|
||||
and commits to the secret it will reveal next time. It never enters the
|
||||
open ledger, so a reveal only becomes public after the revealing
|
||||
validator's transaction set is locked.
|
||||
*/
|
||||
TRANSACTION(ttENTROPY, 89, Entropy, ({
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
{sfPublicKey, soeREQUIRED},
|
||||
{sfNextRandomDigest, soeREQUIRED},
|
||||
{sfRandomData, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/* sfAccount is supplied by TxFormats::commonFields; listing it here as well
|
||||
makes the SOTemplate reject the format at construction. */
|
||||
TRANSACTION(ttMANIFEST_SET, 91, SetManifest, ({
|
||||
|
||||
@@ -47,6 +47,7 @@ JSS(AL_hit_rate); // out: GetCounts
|
||||
JSS(AcceptedCredentials); // out: AccountObjects
|
||||
JSS(Account); // in: TransactionSign; field.
|
||||
JSS(AMMID); // field
|
||||
JSS(API); // out: RPC server_definitions (HOOK)
|
||||
JSS(Amount); // in: TransactionSign; field.
|
||||
JSS(Authorize); // field
|
||||
JSS(Amount2); // in/out: AMM IOU/XRP pool, deposit, withdraw amount
|
||||
@@ -68,6 +69,9 @@ JSS(Destination); // in: TransactionSign; field.
|
||||
JSS(EPrice); // in: AMM Deposit option
|
||||
JSS(Fee); // in/out: TransactionSign; field.
|
||||
JSS(FIELDS); // out: RPC server_definitions
|
||||
JSS(HOOK); // out: RPC server_definitions
|
||||
JSS(KEYLET_TYPES); // out: RPC server_definitions (HOOK)
|
||||
JSS(RETURN_CODES); // out: RPC server_definitions (HOOK)
|
||||
JSS(Flags); // in/out: TransactionSign; field.
|
||||
JSS(GenesisMints);
|
||||
JSS(GovernanceMarks);
|
||||
@@ -96,11 +100,15 @@ JSS(isVLEncoded); // out: RPC server_definitions
|
||||
// matches definitions.json format
|
||||
JSS(Invalid); //
|
||||
JSS(Issuer); // in: Credential transactions
|
||||
JSS(IssuingChainDoor); // field.
|
||||
JSS(IssuingChainIssue); // field.
|
||||
JSS(InvoiceID); // field
|
||||
JSS(LastLedgerSequence); // in: TransactionSign; field
|
||||
JSS(FirstLedgerSequence); // in: TransactionSign; field
|
||||
JSS(LastUpdateTime); // field.
|
||||
JSS(LimitAmount); // field.
|
||||
JSS(LockingChainDoor); // field.
|
||||
JSS(LockingChainIssue); // field.
|
||||
JSS(NetworkID); // field.
|
||||
JSS(LPTokenOut); // in: AMM Liquidity Provider deposit tokens
|
||||
JSS(LPTokenIn); // in: AMM Liquidity Provider withdraw tokens
|
||||
@@ -167,6 +175,7 @@ JSS(amendment_blocked); // out: NetworkOPs
|
||||
JSS(amm_account); // in: amm_info
|
||||
JSS(amount); // out: AccountChannels, amm_info
|
||||
JSS(amount2); // out: amm_info
|
||||
JSS(anchored_through); // out: CatalogueLoad
|
||||
JSS(api_version); // in: many, out: Version
|
||||
JSS(api_version_low); // out: Version
|
||||
JSS(applied); // out: SubmitTransaction
|
||||
@@ -234,7 +243,7 @@ JSS(closed); // out: NetworkOPs, LedgerToJson,
|
||||
// handlers/Ledger
|
||||
JSS(closed_ledger); // out: NetworkOPs
|
||||
JSS(cluster); // out: PeerImp
|
||||
JSS(code); // out: errors
|
||||
JSS(code); // in: HookValidate; out: errors
|
||||
JSS(command); // in: RPCHandler
|
||||
JSS(complete); // out: NetworkOPs, InboundLedger
|
||||
JSS(complete_ledgers); // out: NetworkOPs, PeerImp
|
||||
@@ -374,6 +383,8 @@ JSS(index); // in: LedgerEntry,
|
||||
// LedgerEntry, TxHistory, LedgerData
|
||||
JSS(info); // out: ServerInfo, ConsensusInfo, FetchInfo
|
||||
JSS(initial_sync_duration_us);
|
||||
JSS(instruction_count_cbak); // out: HookValidate
|
||||
JSS(instruction_count_hook); // out: HookValidate
|
||||
JSS(internal_command); // in: Internal
|
||||
JSS(invalid_API_version); // out: Many, when a request has an invalid
|
||||
// version
|
||||
@@ -447,6 +458,7 @@ JSS(local); // out: resource/Logic.h
|
||||
JSS(local_txs); // out: GetCounts
|
||||
JSS(local_static_keys); // out: ValidatorList
|
||||
JSS(locked_balance); // out: AccountLines
|
||||
JSS(log); // out: HookValidate
|
||||
JSS(low); // out: BookChanges
|
||||
JSS(lock_count); // out: AccountLines
|
||||
JSS(lowest_sequence); // out: AccountInfo
|
||||
@@ -585,7 +597,6 @@ JSS(queue_data); // out: AccountInfo
|
||||
JSS(queued); // out: SubmitTransaction
|
||||
JSS(queued_duration_us);
|
||||
JSS(quote_asset); // in: get_aggregate_price
|
||||
JSS(random); // out: Random
|
||||
JSS(raw_meta); // out: AcceptedLedgerTx
|
||||
JSS(receive_currencies); // out: AccountCurrencies
|
||||
JSS(reference_level); // out: TxQ
|
||||
@@ -603,6 +614,7 @@ JSS(reserve_base_native); // out: NetworkOPs
|
||||
JSS(reserve_inc); // out: NetworkOPs
|
||||
JSS(reserve_inc_xrp); // out: NetworkOPs
|
||||
JSS(reserve_inc_native); // out: NetworkOPs
|
||||
JSS(reset); // in/out: PWAInfo
|
||||
JSS(response); // websocket
|
||||
JSS(result); // RPC
|
||||
JSS(ripple_lines); // out: NetworkOPs
|
||||
@@ -737,6 +749,7 @@ JSS(TRANSACTION_FLAGS_INDICES); // out: RPC server_definitions
|
||||
JSS(type_hex); // out: STPathSet
|
||||
JSS(unl); // out: UnlList
|
||||
JSS(unlimited); // out: Connection.h
|
||||
JSS(unpinned); // out: CatalogueUnpin
|
||||
JSS(uptime); // out: GetCounts
|
||||
JSS(uri); // out: ValidatorSites
|
||||
JSS(url); // in/out: Subscribe, Unsubscribe
|
||||
@@ -744,6 +757,7 @@ JSS(url_password); // in: Subscribe
|
||||
JSS(url_username); // in: Subscribe
|
||||
JSS(urlgravatar); //
|
||||
JSS(username); // in: Subscribe
|
||||
JSS(valid); // out: HookValidate
|
||||
JSS(validated); // out: NetworkOPs, RPCHelpers, AccountTx*
|
||||
// Tx
|
||||
JSS(validator_list_expires); // out: NetworkOps, ValidatorList
|
||||
@@ -765,6 +779,7 @@ JSS(validator_sites); // out: ValidatorSites
|
||||
JSS(value); // out: STAmount
|
||||
JSS(version); // out: RPCVersion
|
||||
JSS(vetoed); // out: AmendmentTableImpl
|
||||
JSS(vm_error); // out: HookValidate
|
||||
JSS(volume_a); // out: BookChanges
|
||||
JSS(volume_b); // out: BookChanges
|
||||
JSS(vote); // in: Feature
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <xrpl/basics/BasicConfig.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/network_v4.hpp>
|
||||
#include <boost/asio/ip/network_v6.hpp>
|
||||
@@ -57,6 +58,12 @@ struct Port
|
||||
std::vector<boost::asio::ip::network_v6> admin_nets_v6;
|
||||
std::vector<boost::asio::ip::network_v4> secure_gateway_nets_v4;
|
||||
std::vector<boost::asio::ip::network_v6> secure_gateway_nets_v6;
|
||||
// "pwa" ports only; see the "pwa ports" section of xahaud-example.cfg.
|
||||
std::set<std::string> pwa_domains; // zones, lower case, no trailing dot
|
||||
std::set<AccountID> pwa_allowlist; // if non-empty, only these are served
|
||||
std::set<AccountID> pwa_blocklist; // never served; wins over allowlist
|
||||
bool pwa_custom_domains = false; // serve hosts named by TXT records
|
||||
std::optional<beast::IP::Endpoint> pwa_resolver; // for those; else system
|
||||
std::string user;
|
||||
std::string password;
|
||||
std::string admin_user;
|
||||
@@ -93,8 +100,18 @@ struct Port
|
||||
return protocol.count("udp") > 0;
|
||||
}
|
||||
|
||||
bool
|
||||
has_peer() const
|
||||
{
|
||||
return protocol.count("peer") > 0;
|
||||
}
|
||||
|
||||
// Maximum UDP packet size (default 64KB)
|
||||
std::size_t udp_packet_size = 65536;
|
||||
|
||||
// If this is a peer port, also listen for UDP Superhighway (XUSH)
|
||||
// datagrams on the same port number.
|
||||
bool udp_highway = false;
|
||||
};
|
||||
|
||||
std::ostream&
|
||||
@@ -126,6 +143,9 @@ struct ParsedPort
|
||||
std::vector<boost::asio::ip::network_v6> admin_nets_v6;
|
||||
std::vector<boost::asio::ip::network_v4> secure_gateway_nets_v4;
|
||||
std::vector<boost::asio::ip::network_v6> secure_gateway_nets_v6;
|
||||
std::set<std::string> pwa_domains;
|
||||
bool pwa_custom_domains = false;
|
||||
std::optional<beast::IP::Endpoint> pwa_resolver;
|
||||
};
|
||||
|
||||
void
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
#include <xrpl/beast/utility/PropertyStream.h>
|
||||
#include <xrpl/server/Port.h>
|
||||
#include <xrpl/server/detail/ServerImpl.h>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -33,10 +34,10 @@ template <class Handler>
|
||||
std::unique_ptr<Server>
|
||||
make_Server(
|
||||
Handler& handler,
|
||||
boost::asio::io_service& io_service,
|
||||
boost::asio::io_context& io_context,
|
||||
beast::Journal journal)
|
||||
{
|
||||
return std::make_unique<ServerImpl<Handler>>(handler, io_service, journal);
|
||||
return std::make_unique<ServerImpl<Handler>>(handler, io_context, journal);
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -86,9 +86,7 @@ public:
|
||||
++iter)
|
||||
{
|
||||
typename BufferSequence::value_type const& buffer(*iter);
|
||||
write(
|
||||
boost::asio::buffer_cast<void const*>(buffer),
|
||||
boost::asio::buffer_size(buffer));
|
||||
write(buffer.data(), boost::asio::buffer_size(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +100,7 @@ public:
|
||||
|
||||
/** Detach the session.
|
||||
This holds the session open so that the response can be sent
|
||||
asynchronously. Calls to io_service::run made by the server
|
||||
asynchronously. Calls to io_context::run made by the server
|
||||
will not return until all detached sessions are closed.
|
||||
*/
|
||||
virtual std::shared_ptr<Session>
|
||||
|
||||
@@ -24,10 +24,12 @@
|
||||
#include <xrpl/beast/net/IPAddressConversion.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/server/Session.h>
|
||||
#include <xrpl/server/detail/Spawn.h>
|
||||
#include <xrpl/server/detail/io_list.h>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/ssl/stream.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/asio/streambuf.hpp>
|
||||
#include <boost/beast/core/stream_traits.hpp>
|
||||
#include <boost/beast/http/dynamic_body.hpp>
|
||||
@@ -214,8 +216,8 @@ BaseHTTPPeer<Handler, Impl>::BaseHTTPPeer(
|
||||
ConstBufferSequence const& buffers)
|
||||
: port_(port)
|
||||
, handler_(handler)
|
||||
, work_(executor)
|
||||
, strand_(executor)
|
||||
, work_(boost::asio::make_work_guard(executor))
|
||||
, strand_(boost::asio::make_strand(executor))
|
||||
, remote_address_(remote_address)
|
||||
, journal_(journal)
|
||||
{
|
||||
@@ -361,7 +363,7 @@ BaseHTTPPeer<Handler, Impl>::on_write(
|
||||
return;
|
||||
if (graceful_)
|
||||
return do_close();
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
strand_,
|
||||
std::bind(
|
||||
&BaseHTTPPeer<Handler, Impl>::do_read,
|
||||
@@ -380,7 +382,7 @@ BaseHTTPPeer<Handler, Impl>::do_writer(
|
||||
{
|
||||
auto const p = impl().shared_from_this();
|
||||
resume = std::function<void(void)>([this, p, writer, keep_alive]() {
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
strand_,
|
||||
std::bind(
|
||||
&BaseHTTPPeer<Handler, Impl>::do_writer,
|
||||
@@ -411,7 +413,7 @@ BaseHTTPPeer<Handler, Impl>::do_writer(
|
||||
if (!keep_alive)
|
||||
return do_close();
|
||||
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
strand_,
|
||||
std::bind(
|
||||
&BaseHTTPPeer<Handler, Impl>::do_read,
|
||||
@@ -453,14 +455,14 @@ BaseHTTPPeer<Handler, Impl>::write(
|
||||
std::shared_ptr<Writer> const& writer,
|
||||
bool keep_alive)
|
||||
{
|
||||
boost::asio::spawn(bind_executor(
|
||||
util::spawn(
|
||||
strand_,
|
||||
std::bind(
|
||||
&BaseHTTPPeer<Handler, Impl>::do_writer,
|
||||
impl().shared_from_this(),
|
||||
writer,
|
||||
keep_alive,
|
||||
std::placeholders::_1)));
|
||||
std::placeholders::_1));
|
||||
}
|
||||
|
||||
// DEPRECATED
|
||||
@@ -495,12 +497,12 @@ BaseHTTPPeer<Handler, Impl>::complete()
|
||||
}
|
||||
|
||||
// keep-alive
|
||||
boost::asio::spawn(bind_executor(
|
||||
util::spawn(
|
||||
strand_,
|
||||
std::bind(
|
||||
&BaseHTTPPeer<Handler, Impl>::do_read,
|
||||
impl().shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
std::placeholders::_1));
|
||||
}
|
||||
|
||||
// DEPRECATED
|
||||
|
||||
@@ -89,8 +89,8 @@ BasePeer<Handler, Impl>::BasePeer(
|
||||
return "##" + std::to_string(++id) + " ";
|
||||
}())
|
||||
, j_(sink_)
|
||||
, work_(executor)
|
||||
, strand_(executor)
|
||||
, work_(boost::asio::make_work_guard(executor))
|
||||
, strand_(boost::asio::make_strand(executor))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <xrpl/server/detail/BasePeer.h>
|
||||
#include <xrpl/server/detail/LowestLayer.h>
|
||||
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/beast/core/multi_buffer.hpp>
|
||||
#include <boost/beast/http/message.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
@@ -420,11 +421,17 @@ BaseWSPeer<Handler, Impl>::start_timer()
|
||||
// Max seconds without completing a message
|
||||
static constexpr std::chrono::seconds timeout{30};
|
||||
static constexpr std::chrono::seconds timeoutLocal{3};
|
||||
error_code ec;
|
||||
timer_.expires_from_now(
|
||||
remote_endpoint().address().is_loopback() ? timeoutLocal : timeout, ec);
|
||||
if (ec)
|
||||
return fail(ec, "start_timer");
|
||||
|
||||
try
|
||||
{
|
||||
timer_.expires_after(
|
||||
remote_endpoint().address().is_loopback() ? timeoutLocal : timeout);
|
||||
}
|
||||
catch (boost::system::system_error const& e)
|
||||
{
|
||||
return fail(e.code(), "start_timer");
|
||||
}
|
||||
|
||||
timer_.async_wait(bind_executor(
|
||||
strand_,
|
||||
std::bind(
|
||||
@@ -438,8 +445,14 @@ template <class Handler, class Impl>
|
||||
void
|
||||
BaseWSPeer<Handler, Impl>::cancel_timer()
|
||||
{
|
||||
error_code ec;
|
||||
timer_.cancel(ec);
|
||||
try
|
||||
{
|
||||
timer_.cancel();
|
||||
}
|
||||
catch (boost::system::system_error const&)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
template <class Handler, class Impl>
|
||||
|
||||
@@ -69,7 +69,7 @@ private:
|
||||
stream_type stream_;
|
||||
socket_type& socket_;
|
||||
endpoint_type remote_address_;
|
||||
boost::asio::io_context::strand strand_;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
|
||||
beast::Journal const j_;
|
||||
|
||||
public:
|
||||
@@ -95,7 +95,7 @@ private:
|
||||
Handler& handler_;
|
||||
boost::asio::io_context& ioc_;
|
||||
acceptor_type acceptor_;
|
||||
boost::asio::io_context::strand strand_;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
|
||||
bool ssl_;
|
||||
bool plain_;
|
||||
|
||||
@@ -155,7 +155,7 @@ Door<Handler>::Detector::Detector(
|
||||
, stream_(std::move(stream))
|
||||
, socket_(stream_.socket())
|
||||
, remote_address_(remote_address)
|
||||
, strand_(ioc_)
|
||||
, strand_(boost::asio::make_strand(ioc_))
|
||||
, j_(j)
|
||||
{
|
||||
}
|
||||
@@ -164,7 +164,7 @@ template <class Handler>
|
||||
void
|
||||
Door<Handler>::Detector::run()
|
||||
{
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
strand_,
|
||||
std::bind(
|
||||
&Detector::do_detect,
|
||||
@@ -269,7 +269,7 @@ Door<Handler>::reOpen()
|
||||
Throw<std::exception>();
|
||||
}
|
||||
|
||||
acceptor_.listen(boost::asio::socket_base::max_connections, ec);
|
||||
acceptor_.listen(boost::asio::socket_base::max_listen_connections, ec);
|
||||
if (ec)
|
||||
{
|
||||
JLOG(j_.error()) << "Listen on port '" << port_.name
|
||||
@@ -291,14 +291,14 @@ Door<Handler>::Door(
|
||||
, handler_(handler)
|
||||
, ioc_(io_context)
|
||||
, acceptor_(io_context)
|
||||
, strand_(io_context)
|
||||
, strand_(boost::asio::make_strand(io_context))
|
||||
, ssl_(
|
||||
port_.protocol.count("https") > 0 ||
|
||||
port_.protocol.count("wss") > 0 || port_.protocol.count("wss2") > 0 ||
|
||||
port_.protocol.count("peer") > 0)
|
||||
, plain_(
|
||||
port_.protocol.count("http") > 0 || port_.protocol.count("ws") > 0 ||
|
||||
port_.protocol.count("ws2"))
|
||||
port_.protocol.count("ws2") || port_.protocol.count("pwa"))
|
||||
{
|
||||
reOpen();
|
||||
}
|
||||
@@ -307,7 +307,7 @@ template <class Handler>
|
||||
void
|
||||
Door<Handler>::run()
|
||||
{
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
strand_,
|
||||
std::bind(
|
||||
&Door<Handler>::do_accept,
|
||||
@@ -320,7 +320,8 @@ void
|
||||
Door<Handler>::close()
|
||||
{
|
||||
if (!strand_.running_in_this_thread())
|
||||
return strand_.post(
|
||||
return boost::asio::post(
|
||||
strand_,
|
||||
std::bind(&Door<Handler>::close, this->shared_from_this()));
|
||||
error_code ec;
|
||||
acceptor_.close(ec);
|
||||
@@ -400,7 +401,7 @@ Door<Handler>::do_accept(boost::asio::yield_context do_yield)
|
||||
{
|
||||
create(
|
||||
ssl_,
|
||||
boost::asio::null_buffers{},
|
||||
boost::asio::const_buffer{},
|
||||
std::move(stream),
|
||||
remote_address);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ PlainHTTPPeer<Handler>::run()
|
||||
{
|
||||
if (!this->handler_.onAccept(this->session(), this->remote_address_))
|
||||
{
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
this->strand_,
|
||||
std::bind(&PlainHTTPPeer::do_close, this->shared_from_this()));
|
||||
return;
|
||||
@@ -112,7 +112,7 @@ PlainHTTPPeer<Handler>::run()
|
||||
if (!socket_.is_open())
|
||||
return;
|
||||
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
this->strand_,
|
||||
std::bind(
|
||||
&PlainHTTPPeer::do_read,
|
||||
|
||||
@@ -113,14 +113,14 @@ SSLHTTPPeer<Handler>::run()
|
||||
{
|
||||
if (!this->handler_.onAccept(this->session(), this->remote_address_))
|
||||
{
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
this->strand_,
|
||||
std::bind(&SSLHTTPPeer::do_close, this->shared_from_this()));
|
||||
return;
|
||||
}
|
||||
if (!socket_.is_open())
|
||||
return;
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
this->strand_,
|
||||
std::bind(
|
||||
&SSLHTTPPeer::do_handshake,
|
||||
@@ -162,7 +162,7 @@ SSLHTTPPeer<Handler>::do_handshake(yield_context do_yield)
|
||||
this->port().protocol.count("https") > 0;
|
||||
if (http)
|
||||
{
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
this->strand_,
|
||||
std::bind(
|
||||
&SSLHTTPPeer::do_read,
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include <xrpl/server/detail/io_list.h>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/executor_work_guard.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
@@ -86,9 +88,11 @@ private:
|
||||
|
||||
Handler& handler_;
|
||||
beast::Journal const j_;
|
||||
boost::asio::io_service& io_service_;
|
||||
boost::asio::io_service::strand strand_;
|
||||
std::optional<boost::asio::io_service::work> work_;
|
||||
boost::asio::io_context& io_context_;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
|
||||
std::optional<boost::asio::executor_work_guard<
|
||||
boost::asio::io_context::executor_type>>
|
||||
work_;
|
||||
|
||||
std::mutex m_;
|
||||
std::vector<Port> ports_;
|
||||
@@ -101,7 +105,7 @@ private:
|
||||
public:
|
||||
ServerImpl(
|
||||
Handler& handler,
|
||||
boost::asio::io_service& io_service,
|
||||
boost::asio::io_context& io_context,
|
||||
beast::Journal journal);
|
||||
|
||||
~ServerImpl();
|
||||
@@ -124,10 +128,10 @@ public:
|
||||
return ios_;
|
||||
}
|
||||
|
||||
boost::asio::io_service&
|
||||
get_io_service()
|
||||
boost::asio::io_context&
|
||||
get_io_context()
|
||||
{
|
||||
return io_service_;
|
||||
return io_context_;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -141,13 +145,13 @@ private:
|
||||
template <class Handler>
|
||||
ServerImpl<Handler>::ServerImpl(
|
||||
Handler& handler,
|
||||
boost::asio::io_service& io_service,
|
||||
boost::asio::io_context& io_context,
|
||||
beast::Journal journal)
|
||||
: handler_(handler)
|
||||
, j_(journal)
|
||||
, io_service_(io_service)
|
||||
, strand_(io_service_)
|
||||
, work_(io_service_)
|
||||
, io_context_(io_context)
|
||||
, strand_(boost::asio::make_strand(io_context_))
|
||||
, work_(std::in_place, boost::asio::make_work_guard(io_context_))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -179,7 +183,7 @@ ServerImpl<Handler>::ports(std::vector<Port> const& ports)
|
||||
{
|
||||
// UDP-RPC door
|
||||
if (auto sp = ios_.emplace<UDPDoor<Handler>>(
|
||||
handler_, io_service_, internalPort, j_))
|
||||
handler_, io_context_, internalPort, j_))
|
||||
{
|
||||
auto ep = sp->get_endpoint();
|
||||
if (!internalPort.port)
|
||||
@@ -193,7 +197,7 @@ ServerImpl<Handler>::ports(std::vector<Port> const& ports)
|
||||
{
|
||||
// Standard TCP door
|
||||
if (auto sp = ios_.emplace<Door<Handler>>(
|
||||
handler_, io_service_, internalPort, j_))
|
||||
handler_, io_context_, internalPort, j_))
|
||||
{
|
||||
list_.push_back(sp);
|
||||
|
||||
@@ -204,6 +208,16 @@ ServerImpl<Handler>::ports(std::vector<Port> const& ports)
|
||||
|
||||
sp->run();
|
||||
}
|
||||
|
||||
// UDP Superhighway (XUSH) datagrams arrive on the peer port
|
||||
// number. TCP and UDP port numbers are independent, so this
|
||||
// needs no socket sharing with the TCP door.
|
||||
if (internalPort.has_peer() && internalPort.udp_highway)
|
||||
{
|
||||
if (auto sp = ios_.emplace<UDPDoor<Handler>>(
|
||||
handler_, io_context_, internalPort, j_))
|
||||
sp->run();
|
||||
}
|
||||
}
|
||||
}
|
||||
return eps;
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright(c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or 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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_SERVER_SPAWN_H_INCLUDED
|
||||
#define RIPPLE_SERVER_SPAWN_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
namespace ripple::util {
|
||||
namespace impl {
|
||||
|
||||
template <typename T>
|
||||
concept IsStrand = std::same_as<
|
||||
std::decay_t<T>,
|
||||
boost::asio::strand<typename std::decay_t<T>::inner_executor_type>>;
|
||||
|
||||
/**
|
||||
* @brief A completion handler that restores `boost::asio::spawn`'s behaviour
|
||||
* from Boost 1.83
|
||||
*
|
||||
* This is intended to be passed as the third argument to `boost::asio::spawn`
|
||||
* so that exceptions are not ignored but propagated to `io_context.run()` call
|
||||
* site.
|
||||
*
|
||||
* @param ePtr The exception that was caught on the coroutine
|
||||
*/
|
||||
inline constexpr auto kPROPAGATE_EXCEPTIONS = [](std::exception_ptr ePtr) {
|
||||
if (ePtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::rethrow_exception(ePtr);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(debugLog().warn()) << "Spawn exception: " << e.what();
|
||||
throw;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
JLOG(debugLog().warn()) << "Spawn exception: Unknown";
|
||||
throw;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
||||
/**
|
||||
* @brief Spawns a coroutine using `boost::asio::spawn`
|
||||
*
|
||||
* @note This uses kPROPAGATE_EXCEPTIONS to force asio to propagate exceptions
|
||||
* through `io_context`
|
||||
* @note Since implicit strand was removed from boost::asio::spawn this helper
|
||||
* function adds the strand back
|
||||
*
|
||||
* @tparam Ctx The type of the context/strand
|
||||
* @tparam F The type of the function to execute
|
||||
* @param ctx The execution context
|
||||
* @param func The function to execute. Must return `void`
|
||||
*/
|
||||
template <typename Ctx, typename F>
|
||||
requires std::is_invocable_r_v<void, F, boost::asio::yield_context>
|
||||
void
|
||||
spawn(Ctx&& ctx, F&& func)
|
||||
{
|
||||
if constexpr (impl::IsStrand<Ctx>)
|
||||
{
|
||||
boost::asio::spawn(
|
||||
std::forward<Ctx>(ctx),
|
||||
std::forward<F>(func),
|
||||
impl::kPROPAGATE_EXCEPTIONS);
|
||||
}
|
||||
else
|
||||
{
|
||||
// make_strand accepts an executor or an execution context directly.
|
||||
// Going through get_associated_executor here resolved a plain
|
||||
// executor to asio's inline executor, which cannot be stranded and
|
||||
// did not compile; nothing in the tree had instantiated this branch.
|
||||
boost::asio::spawn(
|
||||
boost::asio::make_strand(std::forward<Ctx>(ctx)),
|
||||
std::forward<F>(func),
|
||||
impl::kPROPAGATE_EXCEPTIONS);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ripple::util
|
||||
|
||||
#endif
|
||||
@@ -78,10 +78,9 @@ public:
|
||||
error_code ec;
|
||||
|
||||
// Create UDP endpoint from port configuration
|
||||
auto const addr = port_.ip.to_v4();
|
||||
boost::asio::ip::udp::endpoint udp_endpoint(addr, port_.port);
|
||||
boost::asio::ip::udp::endpoint udp_endpoint(port_.ip, port_.port);
|
||||
|
||||
socket_.open(boost::asio::ip::udp::v4(), ec);
|
||||
socket_.open(udp_endpoint.protocol(), ec);
|
||||
if (ec)
|
||||
{
|
||||
JLOG(j_.error()) << "UDP socket open failed: " << ec.message();
|
||||
@@ -94,17 +93,22 @@ public:
|
||||
{
|
||||
JLOG(j_.error())
|
||||
<< "UDP set reuse_address failed: " << ec.message();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
socket_.bind(udp_endpoint, ec);
|
||||
if (ec)
|
||||
{
|
||||
JLOG(j_.error()) << "UDP socket bind failed: " << ec.message();
|
||||
// Close the socket so that run() does not start receiving on an
|
||||
// unbound socket, which would silently bind an ephemeral port.
|
||||
JLOG(j_.error()) << "UDP socket bind to " << udp_endpoint
|
||||
<< " failed: " << ec.message();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
JLOG(j_.info()) << "UDP-RPC listening on " << udp_endpoint;
|
||||
JLOG(j_.info()) << "UDP listening on " << udp_endpoint;
|
||||
}
|
||||
|
||||
endpoint_type
|
||||
@@ -169,6 +173,7 @@ private:
|
||||
handler_.onUDPMessage(
|
||||
std::string(recv_buffer_.data(), bytes_transferred),
|
||||
tcp_endpoint,
|
||||
port_,
|
||||
[this, tcp_endpoint](std::string const& response) {
|
||||
do_send(response, tcp_endpoint);
|
||||
});
|
||||
|
||||
@@ -166,7 +166,7 @@ public:
|
||||
May be called concurrently.
|
||||
|
||||
Preconditions:
|
||||
No call to io_service::run on any io_service
|
||||
No call to io_context::run on any io_context
|
||||
used by work objects associated with this io_list
|
||||
exists in the caller's call stack.
|
||||
*/
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or 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 <xrpl/basics/CountedObject.h>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
CountedObjects&
|
||||
CountedObjects::getInstance() noexcept
|
||||
{
|
||||
static CountedObjects instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
CountedObjects::CountedObjects() noexcept : m_count(0), m_head(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
CountedObjects::List
|
||||
CountedObjects::getCounts(int minimumThreshold) const
|
||||
{
|
||||
List counts;
|
||||
|
||||
// When other operations are concurrent, the count
|
||||
// might be temporarily less than the actual count.
|
||||
counts.reserve(m_count.load());
|
||||
|
||||
for (auto* ctr = m_head.load(); ctr != nullptr; ctr = ctr->getNext())
|
||||
{
|
||||
if (ctr->getCount() >= minimumThreshold)
|
||||
counts.emplace_back(ctr->getName(), ctr->getCount());
|
||||
}
|
||||
|
||||
std::sort(counts.begin(), counts.end());
|
||||
|
||||
return counts;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -22,7 +22,14 @@
|
||||
#include <xrpl/beast/net/IPAddressConversion.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/system/detail/error_code.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
@@ -110,8 +117,8 @@ public:
|
||||
|
||||
beast::Journal m_journal;
|
||||
|
||||
boost::asio::io_service& m_io_service;
|
||||
boost::asio::io_service::strand m_strand;
|
||||
boost::asio::io_context& m_io_context;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> m_strand;
|
||||
boost::asio::ip::tcp::resolver m_resolver;
|
||||
|
||||
std::condition_variable m_cv;
|
||||
@@ -141,12 +148,12 @@ public:
|
||||
std::deque<Work> m_work;
|
||||
|
||||
ResolverAsioImpl(
|
||||
boost::asio::io_service& io_service,
|
||||
boost::asio::io_context& io_context,
|
||||
beast::Journal journal)
|
||||
: m_journal(journal)
|
||||
, m_io_service(io_service)
|
||||
, m_strand(io_service)
|
||||
, m_resolver(io_service)
|
||||
, m_io_context(io_context)
|
||||
, m_strand(boost::asio::make_strand(io_context))
|
||||
, m_resolver(io_context)
|
||||
, m_asyncHandlersCompleted(true)
|
||||
, m_stop_called(false)
|
||||
, m_stopped(true)
|
||||
@@ -202,8 +209,14 @@ public:
|
||||
{
|
||||
if (m_stop_called.exchange(true) == false)
|
||||
{
|
||||
m_io_service.dispatch(m_strand.wrap(std::bind(
|
||||
&ResolverAsioImpl::do_stop, this, CompletionCounter(this))));
|
||||
boost::asio::dispatch(
|
||||
m_io_context,
|
||||
boost::asio::bind_executor(
|
||||
m_strand,
|
||||
std::bind(
|
||||
&ResolverAsioImpl::do_stop,
|
||||
this,
|
||||
CompletionCounter(this))));
|
||||
|
||||
JLOG(m_journal.debug()) << "Queued a stop request";
|
||||
}
|
||||
@@ -234,12 +247,16 @@ public:
|
||||
|
||||
// TODO NIKB use rvalue references to construct and move
|
||||
// reducing cost.
|
||||
m_io_service.dispatch(m_strand.wrap(std::bind(
|
||||
&ResolverAsioImpl::do_resolve,
|
||||
this,
|
||||
names,
|
||||
handler,
|
||||
CompletionCounter(this))));
|
||||
boost::asio::dispatch(
|
||||
m_io_context,
|
||||
boost::asio::bind_executor(
|
||||
m_strand,
|
||||
std::bind(
|
||||
&ResolverAsioImpl::do_resolve,
|
||||
this,
|
||||
names,
|
||||
handler,
|
||||
CompletionCounter(this))));
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
@@ -265,19 +282,20 @@ public:
|
||||
std::string name,
|
||||
boost::system::error_code const& ec,
|
||||
HandlerType handler,
|
||||
boost::asio::ip::tcp::resolver::iterator iter,
|
||||
boost::asio::ip::tcp::resolver::results_type results,
|
||||
CompletionCounter)
|
||||
{
|
||||
if (ec == boost::asio::error::operation_aborted)
|
||||
return;
|
||||
|
||||
std::vector<beast::IP::Endpoint> addresses;
|
||||
auto iter = results.begin();
|
||||
|
||||
// If we get an error message back, we don't return any
|
||||
// results that we may have gotten.
|
||||
if (!ec)
|
||||
{
|
||||
while (iter != boost::asio::ip::tcp::resolver::iterator())
|
||||
while (iter != results.end())
|
||||
{
|
||||
addresses.push_back(
|
||||
beast::IPAddressConversion::from_asio(*iter));
|
||||
@@ -287,8 +305,14 @@ public:
|
||||
|
||||
handler(name, addresses);
|
||||
|
||||
m_io_service.post(m_strand.wrap(std::bind(
|
||||
&ResolverAsioImpl::do_work, this, CompletionCounter(this))));
|
||||
boost::asio::post(
|
||||
m_io_context,
|
||||
boost::asio::bind_executor(
|
||||
m_strand,
|
||||
std::bind(
|
||||
&ResolverAsioImpl::do_work,
|
||||
this,
|
||||
CompletionCounter(this))));
|
||||
}
|
||||
|
||||
HostAndPort
|
||||
@@ -369,16 +393,21 @@ public:
|
||||
{
|
||||
JLOG(m_journal.error()) << "Unable to parse '" << name << "'";
|
||||
|
||||
m_io_service.post(m_strand.wrap(std::bind(
|
||||
&ResolverAsioImpl::do_work, this, CompletionCounter(this))));
|
||||
boost::asio::post(
|
||||
m_io_context,
|
||||
boost::asio::bind_executor(
|
||||
m_strand,
|
||||
std::bind(
|
||||
&ResolverAsioImpl::do_work,
|
||||
this,
|
||||
CompletionCounter(this))));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
boost::asio::ip::tcp::resolver::query query(host, port);
|
||||
|
||||
m_resolver.async_resolve(
|
||||
query,
|
||||
host,
|
||||
port,
|
||||
std::bind(
|
||||
&ResolverAsioImpl::do_finish,
|
||||
this,
|
||||
@@ -409,10 +438,14 @@ public:
|
||||
|
||||
if (m_work.size() > 0)
|
||||
{
|
||||
m_io_service.post(m_strand.wrap(std::bind(
|
||||
&ResolverAsioImpl::do_work,
|
||||
this,
|
||||
CompletionCounter(this))));
|
||||
boost::asio::post(
|
||||
m_io_context,
|
||||
boost::asio::bind_executor(
|
||||
m_strand,
|
||||
std::bind(
|
||||
&ResolverAsioImpl::do_work,
|
||||
this,
|
||||
CompletionCounter(this))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -421,9 +454,9 @@ public:
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
std::unique_ptr<ResolverAsio>
|
||||
ResolverAsio::New(boost::asio::io_service& io_service, beast::Journal journal)
|
||||
ResolverAsio::New(boost::asio::io_context& io_context, beast::Journal journal)
|
||||
{
|
||||
return std::make_unique<ResolverAsioImpl>(io_service, journal);
|
||||
return std::make_unique<ResolverAsioImpl>(io_context, journal);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
@@ -25,19 +25,6 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace detail {
|
||||
|
||||
[[noreturn]] void
|
||||
accessViolation() noexcept
|
||||
{
|
||||
// dereference memory location zero
|
||||
int volatile* j = 0;
|
||||
(void)*j;
|
||||
std::abort();
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
void
|
||||
LogThrow(std::string const& title)
|
||||
{
|
||||
@@ -55,7 +42,7 @@ LogicError(std::string const& s) noexcept
|
||||
// here.
|
||||
// For the above reasons, we want this contract to stand out.
|
||||
UNREACHABLE("LogicError", {{"message", s}});
|
||||
detail::accessViolation();
|
||||
std::abort();
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -33,7 +33,7 @@
|
||||
namespace beast::detail {
|
||||
|
||||
inline void
|
||||
setCurrentThreadNameImpl(std::string_view name)
|
||||
setCurrentThreadNameImpl(char const* name)
|
||||
{
|
||||
#if DEBUG && BOOST_COMP_MSVC
|
||||
// This technique is documented by Microsoft and works for all versions
|
||||
@@ -54,7 +54,7 @@ setCurrentThreadNameImpl(std::string_view name)
|
||||
THREADNAME_INFO ni;
|
||||
|
||||
ni.dwType = 0x1000;
|
||||
ni.szName = name.data();
|
||||
ni.szName = name;
|
||||
ni.dwThreadID = GetCurrentThreadId();
|
||||
ni.dwFlags = 0;
|
||||
|
||||
@@ -81,9 +81,9 @@ setCurrentThreadNameImpl(std::string_view name)
|
||||
namespace beast::detail {
|
||||
|
||||
inline void
|
||||
setCurrentThreadNameImpl(std::string_view name)
|
||||
setCurrentThreadNameImpl(char const* name)
|
||||
{
|
||||
pthread_setname_np(name.data());
|
||||
pthread_setname_np(name);
|
||||
}
|
||||
|
||||
} // namespace beast::detail
|
||||
@@ -95,9 +95,9 @@ setCurrentThreadNameImpl(std::string_view name)
|
||||
namespace beast::detail {
|
||||
|
||||
inline void
|
||||
setCurrentThreadNameImpl(std::string_view name)
|
||||
setCurrentThreadNameImpl(char const* name)
|
||||
{
|
||||
pthread_setname_np(pthread_self(), name.data());
|
||||
pthread_setname_np(pthread_self(), name);
|
||||
}
|
||||
|
||||
} // namespace beast::detail
|
||||
@@ -119,7 +119,7 @@ void
|
||||
setCurrentThreadName(std::string_view name)
|
||||
{
|
||||
detail::threadName = name;
|
||||
detail::setCurrentThreadNameImpl(name);
|
||||
detail::setCurrentThreadNameImpl(detail::threadName.c_str());
|
||||
}
|
||||
|
||||
} // namespace beast
|
||||
@@ -26,10 +26,24 @@
|
||||
#include <xrpl/beast/insight/StatsDCollector.h>
|
||||
#include <xrpl/beast/net/IPAddressConversion.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <boost/asio/basic_waitable_timer.hpp>
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/executor_work_guard.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/udp.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/system/detail/error_code.hpp>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
@@ -223,9 +237,11 @@ private:
|
||||
Journal m_journal;
|
||||
IP::Endpoint m_address;
|
||||
std::string m_prefix;
|
||||
boost::asio::io_service m_io_service;
|
||||
std::optional<boost::asio::io_service::work> m_work;
|
||||
boost::asio::io_service::strand m_strand;
|
||||
boost::asio::io_context m_io_context;
|
||||
std::optional<boost::asio::executor_work_guard<
|
||||
boost::asio::io_context::executor_type>>
|
||||
m_work;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> m_strand;
|
||||
boost::asio::basic_waitable_timer<std::chrono::steady_clock> m_timer;
|
||||
boost::asio::ip::udp::socket m_socket;
|
||||
std::deque<std::string> m_data;
|
||||
@@ -249,18 +265,24 @@ public:
|
||||
: m_journal(journal)
|
||||
, m_address(address)
|
||||
, m_prefix(prefix)
|
||||
, m_work(std::ref(m_io_service))
|
||||
, m_strand(m_io_service)
|
||||
, m_timer(m_io_service)
|
||||
, m_socket(m_io_service)
|
||||
, m_work(boost::asio::make_work_guard(m_io_context))
|
||||
, m_strand(boost::asio::make_strand(m_io_context))
|
||||
, m_timer(m_io_context)
|
||||
, m_socket(m_io_context)
|
||||
, m_thread(&StatsDCollectorImp::run, this)
|
||||
{
|
||||
}
|
||||
|
||||
~StatsDCollectorImp() override
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
m_timer.cancel(ec);
|
||||
try
|
||||
{
|
||||
m_timer.cancel();
|
||||
}
|
||||
catch (boost::system::system_error const&)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
m_work.reset();
|
||||
m_thread.join();
|
||||
@@ -319,10 +341,10 @@ public:
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
boost::asio::io_service&
|
||||
get_io_service()
|
||||
boost::asio::io_context&
|
||||
get_io_context()
|
||||
{
|
||||
return m_io_service;
|
||||
return m_io_context;
|
||||
}
|
||||
|
||||
std::string const&
|
||||
@@ -340,8 +362,14 @@ public:
|
||||
void
|
||||
post_buffer(std::string&& buffer)
|
||||
{
|
||||
m_io_service.dispatch(m_strand.wrap(std::bind(
|
||||
&StatsDCollectorImp::do_post_buffer, this, std::move(buffer))));
|
||||
boost::asio::dispatch(
|
||||
m_io_context,
|
||||
boost::asio::bind_executor(
|
||||
m_strand,
|
||||
std::bind(
|
||||
&StatsDCollectorImp::do_post_buffer,
|
||||
this,
|
||||
std::move(buffer))));
|
||||
}
|
||||
|
||||
// The keepAlive parameter makes sure the buffers sent to
|
||||
@@ -371,7 +399,7 @@ public:
|
||||
for (auto const& buffer : buffers)
|
||||
{
|
||||
std::string const s(
|
||||
boost::asio::buffer_cast<char const*>(buffer),
|
||||
static_cast<char const*>(buffer.data()),
|
||||
boost::asio::buffer_size(buffer));
|
||||
std::cerr << s;
|
||||
}
|
||||
@@ -441,7 +469,7 @@ public:
|
||||
set_timer()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
m_timer.expires_from_now(1s);
|
||||
m_timer.expires_after(1s);
|
||||
m_timer.async_wait(std::bind(
|
||||
&StatsDCollectorImp::on_timer, this, std::placeholders::_1));
|
||||
}
|
||||
@@ -483,13 +511,13 @@ public:
|
||||
|
||||
set_timer();
|
||||
|
||||
m_io_service.run();
|
||||
m_io_context.run();
|
||||
|
||||
m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_send, ec);
|
||||
|
||||
m_socket.close();
|
||||
|
||||
m_io_service.poll();
|
||||
m_io_context.poll();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -532,10 +560,12 @@ StatsDCounterImpl::~StatsDCounterImpl()
|
||||
void
|
||||
StatsDCounterImpl::increment(CounterImpl::value_type amount)
|
||||
{
|
||||
m_impl->get_io_service().dispatch(std::bind(
|
||||
&StatsDCounterImpl::do_increment,
|
||||
std::static_pointer_cast<StatsDCounterImpl>(shared_from_this()),
|
||||
amount));
|
||||
boost::asio::dispatch(
|
||||
m_impl->get_io_context(),
|
||||
std::bind(
|
||||
&StatsDCounterImpl::do_increment,
|
||||
std::static_pointer_cast<StatsDCounterImpl>(shared_from_this()),
|
||||
amount));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -577,10 +607,12 @@ StatsDEventImpl::StatsDEventImpl(
|
||||
void
|
||||
StatsDEventImpl::notify(EventImpl::value_type const& value)
|
||||
{
|
||||
m_impl->get_io_service().dispatch(std::bind(
|
||||
&StatsDEventImpl::do_notify,
|
||||
std::static_pointer_cast<StatsDEventImpl>(shared_from_this()),
|
||||
value));
|
||||
boost::asio::dispatch(
|
||||
m_impl->get_io_context(),
|
||||
std::bind(
|
||||
&StatsDEventImpl::do_notify,
|
||||
std::static_pointer_cast<StatsDEventImpl>(shared_from_this()),
|
||||
value));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -610,19 +642,23 @@ StatsDGaugeImpl::~StatsDGaugeImpl()
|
||||
void
|
||||
StatsDGaugeImpl::set(GaugeImpl::value_type value)
|
||||
{
|
||||
m_impl->get_io_service().dispatch(std::bind(
|
||||
&StatsDGaugeImpl::do_set,
|
||||
std::static_pointer_cast<StatsDGaugeImpl>(shared_from_this()),
|
||||
value));
|
||||
boost::asio::dispatch(
|
||||
m_impl->get_io_context(),
|
||||
std::bind(
|
||||
&StatsDGaugeImpl::do_set,
|
||||
std::static_pointer_cast<StatsDGaugeImpl>(shared_from_this()),
|
||||
value));
|
||||
}
|
||||
|
||||
void
|
||||
StatsDGaugeImpl::increment(GaugeImpl::difference_type amount)
|
||||
{
|
||||
m_impl->get_io_service().dispatch(std::bind(
|
||||
&StatsDGaugeImpl::do_increment,
|
||||
std::static_pointer_cast<StatsDGaugeImpl>(shared_from_this()),
|
||||
amount));
|
||||
boost::asio::dispatch(
|
||||
m_impl->get_io_context(),
|
||||
std::bind(
|
||||
&StatsDGaugeImpl::do_increment,
|
||||
std::static_pointer_cast<StatsDGaugeImpl>(shared_from_this()),
|
||||
amount));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -698,10 +734,12 @@ StatsDMeterImpl::~StatsDMeterImpl()
|
||||
void
|
||||
StatsDMeterImpl::increment(MeterImpl::value_type amount)
|
||||
{
|
||||
m_impl->get_io_service().dispatch(std::bind(
|
||||
&StatsDMeterImpl::do_increment,
|
||||
std::static_pointer_cast<StatsDMeterImpl>(shared_from_this()),
|
||||
amount));
|
||||
boost::asio::dispatch(
|
||||
m_impl->get_io_context(),
|
||||
std::bind(
|
||||
&StatsDMeterImpl::do_increment,
|
||||
std::static_pointer_cast<StatsDMeterImpl>(shared_from_this()),
|
||||
amount));
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -28,11 +28,11 @@ namespace IP {
|
||||
bool
|
||||
is_private(AddressV4 const& addr)
|
||||
{
|
||||
return ((addr.to_ulong() & 0xff000000) ==
|
||||
return ((addr.to_uint() & 0xff000000) ==
|
||||
0x0a000000) || // Prefix /8, 10. #.#.#
|
||||
((addr.to_ulong() & 0xfff00000) ==
|
||||
((addr.to_uint() & 0xfff00000) ==
|
||||
0xac100000) || // Prefix /12 172. 16.#.# - 172.31.#.#
|
||||
((addr.to_ulong() & 0xffff0000) ==
|
||||
((addr.to_uint() & 0xffff0000) ==
|
||||
0xc0a80000) || // Prefix /16 192.168.#.#
|
||||
addr.is_loopback();
|
||||
}
|
||||
@@ -47,7 +47,7 @@ char
|
||||
get_class(AddressV4 const& addr)
|
||||
{
|
||||
static char const* table = "AAAABBCD";
|
||||
return table[(addr.to_ulong() & 0xE0000000) >> 29];
|
||||
return table[(addr.to_uint() & 0xE0000000) >> 29];
|
||||
}
|
||||
|
||||
} // namespace IP
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include <xrpl/beast/net/IPAddressV4.h>
|
||||
#include <xrpl/beast/net/IPAddressV6.h>
|
||||
|
||||
#include <boost/asio/ip/address_v4.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace IP {
|
||||
|
||||
@@ -27,15 +29,18 @@ bool
|
||||
is_private(AddressV6 const& addr)
|
||||
{
|
||||
return (
|
||||
(addr.to_bytes()[0] & 0xfd) || // TODO fc00::/8 too ?
|
||||
(addr.is_v4_mapped() && is_private(addr.to_v4())));
|
||||
addr.is_link_local() || // fe80::/10
|
||||
addr.is_loopback() || // ::1
|
||||
((addr.to_bytes()[0] & 0xfe) == 0xfc) || // fc00::/7 (all ULA)
|
||||
(addr.is_v4_mapped() &&
|
||||
is_private(boost::asio::ip::make_address_v4(
|
||||
boost::asio::ip::v4_mapped, addr))));
|
||||
}
|
||||
|
||||
bool
|
||||
is_public(AddressV6 const& addr)
|
||||
{
|
||||
// TODO is this correct?
|
||||
return !is_private(addr) && !addr.is_multicast();
|
||||
return !is_private(addr) && !addr.is_multicast() && !addr.is_unspecified();
|
||||
}
|
||||
|
||||
} // namespace IP
|
||||
|
||||
@@ -18,7 +18,19 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/address_v4.hpp>
|
||||
#include <boost/system/detail/error_code.hpp>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <cctype>
|
||||
#include <ios>
|
||||
#include <istream>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace beast {
|
||||
namespace IP {
|
||||
@@ -157,7 +169,7 @@ operator>>(std::istream& is, Endpoint& endpoint)
|
||||
}
|
||||
|
||||
boost::system::error_code ec;
|
||||
auto addr = Address::from_string(addrStr, ec);
|
||||
auto addr = boost::asio::ip::make_address(addrStr, ec);
|
||||
if (ec)
|
||||
{
|
||||
is.setstate(std::ios_base::failbit);
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <xrpl/json/json_writer.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace Json {
|
||||
|
||||
const Value Value::null;
|
||||
@@ -672,7 +674,9 @@ Value::isConvertibleTo(ValueType other) const
|
||||
(other == intValue && value_.real_ >= minInt &&
|
||||
value_.real_ <= maxInt) ||
|
||||
(other == uintValue && value_.real_ >= 0 &&
|
||||
value_.real_ <= maxUInt) ||
|
||||
value_.real_ <= maxUInt &&
|
||||
std::fabs(round(value_.real_) - value_.real_) <
|
||||
std::numeric_limits<double>::epsilon()) ||
|
||||
other == realValue || other == stringValue ||
|
||||
other == booleanValue;
|
||||
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or 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 <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/UTF8.h>
|
||||
#include <xrpl/protocol/AppLoader.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace ripple {
|
||||
namespace appLoader {
|
||||
|
||||
namespace {
|
||||
|
||||
// HTML "ASCII whitespace": TAB, LF, FF, CR, SPACE.
|
||||
inline bool
|
||||
isSpace(std::uint8_t c) noexcept
|
||||
{
|
||||
return c == 0x09 || c == 0x0A || c == 0x0C || c == 0x0D || c == 0x20;
|
||||
}
|
||||
|
||||
inline std::uint8_t
|
||||
lower(std::uint8_t c) noexcept
|
||||
{
|
||||
return (c >= 'A' && c <= 'Z') ? static_cast<std::uint8_t>(c + 32) : c;
|
||||
}
|
||||
|
||||
// Case-insensitive match of lower-case `lit` at `pos`; never reads past
|
||||
// the end.
|
||||
bool
|
||||
matchAt(
|
||||
std::uint8_t const* data,
|
||||
std::size_t size,
|
||||
std::size_t pos,
|
||||
std::string_view lit) noexcept
|
||||
{
|
||||
if (pos > size || size - pos < lit.size())
|
||||
return false;
|
||||
for (std::size_t i = 0; i < lit.size(); ++i)
|
||||
if (lower(data[pos + i]) != static_cast<std::uint8_t>(lit[i]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
findFrom(
|
||||
std::uint8_t const* data,
|
||||
std::size_t size,
|
||||
std::size_t from,
|
||||
std::string_view lit) noexcept
|
||||
{
|
||||
for (std::size_t i = from; i + lit.size() <= size; ++i)
|
||||
if (matchAt(data, size, i, lit))
|
||||
return i;
|
||||
return size;
|
||||
}
|
||||
|
||||
// So that "<htmlish>" is not mistaken for "<html>".
|
||||
inline bool
|
||||
isTagNameEnd(std::uint8_t c) noexcept
|
||||
{
|
||||
return isSpace(c) || c == '>' || c == '/';
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result
|
||||
validate(std::uint8_t const* data, std::size_t size)
|
||||
{
|
||||
if (size == 0 || data == nullptr)
|
||||
return Result::empty;
|
||||
if (size > maxAppLoaderLength)
|
||||
return Result::tooLarge;
|
||||
if (!isValidUTF8(Slice(data, size)))
|
||||
return Result::badUTF8;
|
||||
for (std::size_t i = 0; i < size; ++i)
|
||||
if (!isSpace(data[i]) && (data[i] < 0x20 || data[i] == 0x7F))
|
||||
return Result::badControlChar;
|
||||
|
||||
std::size_t begin = 0;
|
||||
if (size >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF)
|
||||
begin = 3;
|
||||
while (begin < size && isSpace(data[begin]))
|
||||
++begin;
|
||||
if (begin == size)
|
||||
return Result::empty;
|
||||
|
||||
// "<!doctype" followed by whitespace is the only doctype form any
|
||||
// browser treats as standards-mode HTML.
|
||||
bool const doctype = matchAt(data, size, begin, "<!doctype") &&
|
||||
begin + 9 < size && isSpace(data[begin + 9]);
|
||||
bool const html = matchAt(data, size, begin, "<html") && begin + 5 < size &&
|
||||
isTagNameEnd(data[begin + 5]);
|
||||
if (!doctype && !html)
|
||||
return Result::noDoctype;
|
||||
|
||||
std::size_t open = begin;
|
||||
for (;;)
|
||||
{
|
||||
open = findFrom(data, size, open, "<html");
|
||||
if (open == size)
|
||||
return Result::noHtmlElement;
|
||||
if (open + 5 < size && isTagNameEnd(data[open + 5]))
|
||||
break;
|
||||
++open;
|
||||
}
|
||||
|
||||
// The LAST well-formed </html>, so that one quoted inside a script
|
||||
// does not shadow the real one. Linear: each probe resumes one past
|
||||
// the previous match.
|
||||
// closeEnd == 0 means none found: a well-formed </html> always ends
|
||||
// past position 0.
|
||||
std::size_t closeEnd = 0;
|
||||
for (std::size_t probe = open;;)
|
||||
{
|
||||
std::size_t const found = findFrom(data, size, probe, "</html");
|
||||
if (found == size)
|
||||
break;
|
||||
std::size_t j = found + 6;
|
||||
while (j < size && isSpace(data[j]))
|
||||
++j;
|
||||
if (j < size && data[j] == '>')
|
||||
closeEnd = j + 1;
|
||||
probe = found + 1;
|
||||
}
|
||||
if (closeEnd == 0)
|
||||
return Result::unclosed;
|
||||
|
||||
for (std::size_t i = closeEnd; i < size; ++i)
|
||||
if (!isSpace(data[i]))
|
||||
return Result::trailingGarbage;
|
||||
return Result::ok;
|
||||
}
|
||||
|
||||
char const*
|
||||
to_string(Result r)
|
||||
{
|
||||
switch (r)
|
||||
{
|
||||
case Result::ok:
|
||||
return "ok";
|
||||
case Result::empty:
|
||||
return "empty document";
|
||||
case Result::tooLarge:
|
||||
return "document exceeds maxAppLoaderLength";
|
||||
case Result::badUTF8:
|
||||
return "malformed UTF-8";
|
||||
case Result::badControlChar:
|
||||
return "forbidden control character";
|
||||
case Result::noDoctype:
|
||||
return "does not open with a doctype or <html> tag";
|
||||
case Result::noHtmlElement:
|
||||
return "missing <html> start tag";
|
||||
case Result::unclosed:
|
||||
return "missing or misplaced </html> end tag";
|
||||
case Result::trailingGarbage:
|
||||
return "content after </html>";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
} // namespace appLoader
|
||||
} // namespace ripple
|
||||
@@ -56,9 +56,9 @@ getVersionString()
|
||||
(versionString == std::string("0.") + std::string("0.0") ||
|
||||
versionString == std::string("0.0.0+DEBUG")))
|
||||
{
|
||||
std::string y = std::string(__DATE__ + 7);
|
||||
std::string y = std::string(&__DATE__[7]);
|
||||
std::string d = std::string(
|
||||
__DATE__ + 4 + (__DATE__[4] == ' ' ? 1 : 0),
|
||||
&__DATE__[4 + (__DATE__[4] == ' ' ? 1 : 0)],
|
||||
__DATE__[4] == ' ' ? 1 : 2);
|
||||
std::string m;
|
||||
switch (__DATE__[0])
|
||||
|
||||
@@ -113,7 +113,10 @@ constexpr static ErrorInfo unorderedErrorInfos[]{
|
||||
{rpcLEDGER_MISSING, "ledgerMissing", "One or more ledgers in the specified range is missing", 406},
|
||||
{rpcORACLE_MALFORMED, "oracleMalformed", "Oracle request is malformed.", 400},
|
||||
{rpcBAD_CREDENTIALS, "badCredentials", "Credentials do not exist, are not accepted, or have expired.", 400},
|
||||
{rpcTX_SIGNED, "transactionSigned", "Transaction should not be signed.", 400}};
|
||||
{rpcTX_SIGNED, "transactionSigned", "Transaction should not be signed.", 400},
|
||||
{rpcENTRY_NOT_FOUND, "entryNotFound", "Entry not found.", 400},
|
||||
{rpcUNEXPECTED_LEDGER_TYPE, "unexpectedLedgerType", "Unexpected ledger type.", 400},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// Sort and validate unorderedErrorInfos at compile time. Should be
|
||||
|
||||
@@ -81,6 +81,7 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
IMPORT_VLSEQ = 'I',
|
||||
UNL_REPORT = 'R',
|
||||
CRON = 'L',
|
||||
APP_LOADER = 'W',
|
||||
AMM = 'A',
|
||||
BRIDGE = LEDGER_NAMESPACE2(0x01, 'H'),
|
||||
XCHAIN_CLAIM_ID = 'Q',
|
||||
@@ -90,8 +91,10 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
MPTOKEN_ISSUANCE = '~',
|
||||
MPTOKEN = 't',
|
||||
MANIFEST = 'M',
|
||||
MANIFEST_DIR = LEDGER_NAMESPACE2(0x01, 'M'),
|
||||
CREDENTIAL = LEDGER_NAMESPACE2(0x01, 'D'),
|
||||
PERMISSIONED_DOMAIN = 'm',
|
||||
RANDOM = 0x526E, // Rn
|
||||
|
||||
// No longer used or supported. Left here to reserve the space
|
||||
// to avoid accidental reuse.
|
||||
@@ -276,6 +279,13 @@ UNLReport() noexcept
|
||||
return ret;
|
||||
}
|
||||
|
||||
Keylet const&
|
||||
random() noexcept
|
||||
{
|
||||
static Keylet const ret{ltRANDOM, indexHash(LedgerNameSpace::RANDOM)};
|
||||
return ret;
|
||||
}
|
||||
|
||||
Keylet
|
||||
book_t::operator()(Book const& b) const
|
||||
{
|
||||
@@ -613,6 +623,12 @@ did(AccountID const& account) noexcept
|
||||
return {ltDID, indexHash(LedgerNameSpace::DID, account)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
appLoader(AccountID const& account) noexcept
|
||||
{
|
||||
return {ltAPP_LOADER, indexHash(LedgerNameSpace::APP_LOADER, account)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
oracle(AccountID const& account, std::uint32_t const& documentID) noexcept
|
||||
{
|
||||
@@ -677,6 +693,14 @@ manifest(PublicKey const& pk) noexcept
|
||||
return {ltMANIFEST, indexHash(LedgerNameSpace::MANIFEST, pk.slice())};
|
||||
}
|
||||
|
||||
Keylet const&
|
||||
manifestDir() noexcept
|
||||
{
|
||||
static Keylet const ret{
|
||||
ltDIR_NODE, indexHash(LedgerNameSpace::MANIFEST_DIR)};
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace keylet
|
||||
|
||||
} // namespace ripple
|
||||
@@ -143,6 +143,14 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfAccount, soeOPTIONAL},
|
||||
});
|
||||
|
||||
add(sfRandomDigestEntry.jsonName,
|
||||
sfRandomDigestEntry.getCode(),
|
||||
{
|
||||
{sfPublicKey, soeREQUIRED},
|
||||
{sfNextRandomDigest, soeREQUIRED},
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
});
|
||||
|
||||
add(sfAmountEntry.jsonName,
|
||||
sfAmountEntry.getCode(),
|
||||
{
|
||||
|
||||
@@ -171,53 +171,6 @@ ed25519Canonical(Slice const& sig)
|
||||
return std::lexicographical_compare(S, S + 32, Order, Order + 32);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
PublicKey::PublicKey(Slice const& slice)
|
||||
{
|
||||
if (slice.size() < size_)
|
||||
LogicError(
|
||||
"PublicKey::PublicKey - Input slice cannot be an undersized "
|
||||
"buffer");
|
||||
|
||||
if (!publicKeyType(slice))
|
||||
LogicError("PublicKey::PublicKey invalid type");
|
||||
std::memcpy(buf_, slice.data(), size_);
|
||||
}
|
||||
|
||||
PublicKey::PublicKey(PublicKey const& other)
|
||||
{
|
||||
std::memcpy(buf_, other.buf_, size_);
|
||||
}
|
||||
|
||||
PublicKey&
|
||||
PublicKey::operator=(PublicKey const& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
std::memcpy(buf_, other.buf_, size_);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::optional<KeyType>
|
||||
publicKeyType(Slice const& slice)
|
||||
{
|
||||
if (slice.size() == 33)
|
||||
{
|
||||
if (slice[0] == 0xED)
|
||||
return KeyType::ed25519;
|
||||
|
||||
if (slice[0] == 0x02 || slice[0] == 0x03)
|
||||
return KeyType::secp256k1;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool
|
||||
verifyDigest(
|
||||
PublicKey const& publicKey,
|
||||
|
||||
@@ -961,7 +961,7 @@ amountFromJson(SField const& name, Json::Value const& v)
|
||||
|
||||
bool const native = !currencyOrMPTID.isString() ||
|
||||
currencyOrMPTID.asString().empty() ||
|
||||
(currencyOrMPTID.asString() == systemCurrencyCode());
|
||||
(currencyOrMPTID.asString() == systemCurrencyCode);
|
||||
|
||||
if (native)
|
||||
{
|
||||
|
||||
@@ -684,7 +684,8 @@ isPseudoTx(STObject const& tx)
|
||||
|
||||
auto tt = safe_cast<TxType>(*t);
|
||||
return tt == ttAMENDMENT || tt == ttFEE || tt == ttUNL_MODIFY ||
|
||||
tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON;
|
||||
tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON ||
|
||||
tt == ttENTROPY;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -95,12 +95,10 @@ STXChainBridge::STXChainBridge(SField const& name, Json::Value const& v)
|
||||
};
|
||||
checkExtra(v);
|
||||
|
||||
Json::Value const& lockingChainDoorStr =
|
||||
v[sfLockingChainDoor.getJsonName()];
|
||||
Json::Value const& lockingChainIssue = v[sfLockingChainIssue.getJsonName()];
|
||||
Json::Value const& issuingChainDoorStr =
|
||||
v[sfIssuingChainDoor.getJsonName()];
|
||||
Json::Value const& issuingChainIssue = v[sfIssuingChainIssue.getJsonName()];
|
||||
Json::Value const& lockingChainDoorStr = v[jss::LockingChainDoor];
|
||||
Json::Value const& lockingChainIssue = v[jss::LockingChainIssue];
|
||||
Json::Value const& issuingChainDoorStr = v[jss::IssuingChainDoor];
|
||||
Json::Value const& issuingChainIssue = v[jss::IssuingChainIssue];
|
||||
|
||||
if (!lockingChainDoorStr.isString())
|
||||
{
|
||||
@@ -158,10 +156,10 @@ Json::Value
|
||||
STXChainBridge::getJson(JsonOptions jo) const
|
||||
{
|
||||
Json::Value v;
|
||||
v[sfLockingChainDoor.getJsonName()] = lockingChainDoor_.getJson(jo);
|
||||
v[sfLockingChainIssue.getJsonName()] = lockingChainIssue_.getJson(jo);
|
||||
v[sfIssuingChainDoor.getJsonName()] = issuingChainDoor_.getJson(jo);
|
||||
v[sfIssuingChainIssue.getJsonName()] = issuingChainIssue_.getJson(jo);
|
||||
v[jss::LockingChainDoor] = lockingChainDoor_.getJson(jo);
|
||||
v[jss::LockingChainIssue] = lockingChainIssue_.getJson(jo);
|
||||
v[jss::IssuingChainDoor] = issuingChainDoor_.getJson(jo);
|
||||
v[jss::IssuingChainIssue] = issuingChainIssue_.getJson(jo);
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/detail/secp256k1.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <ed25519.h>
|
||||
|
||||
@@ -32,25 +33,7 @@ namespace ripple {
|
||||
|
||||
SecretKey::~SecretKey()
|
||||
{
|
||||
secure_erase(buf_, sizeof(buf_));
|
||||
}
|
||||
|
||||
SecretKey::SecretKey(std::array<std::uint8_t, 32> const& key)
|
||||
{
|
||||
std::memcpy(buf_, key.data(), key.size());
|
||||
}
|
||||
|
||||
SecretKey::SecretKey(Slice const& slice)
|
||||
{
|
||||
if (slice.size() != sizeof(buf_))
|
||||
LogicError("SecretKey::SecretKey: invalid size");
|
||||
std::memcpy(buf_, slice.data(), sizeof(buf_));
|
||||
}
|
||||
|
||||
std::string
|
||||
SecretKey::to_string() const
|
||||
{
|
||||
return strHex(*this);
|
||||
secure_erase(buf_.data(), buf_.size());
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
@@ -214,7 +197,6 @@ signDigest(PublicKey const& pk, SecretKey const& sk, uint256 const& digest)
|
||||
if (publicKeyType(pk.slice()) != KeyType::secp256k1)
|
||||
LogicError("sign: secp256k1 required for digest signing");
|
||||
|
||||
BOOST_ASSERT(sk.size() == 32);
|
||||
secp256k1_ecdsa_signature sig_imp;
|
||||
if (secp256k1_ecdsa_sign(
|
||||
secp256k1Context(),
|
||||
@@ -226,7 +208,7 @@ signDigest(PublicKey const& pk, SecretKey const& sk, uint256 const& digest)
|
||||
LogicError("sign: secp256k1_ecdsa_sign failed");
|
||||
|
||||
unsigned char sig[72];
|
||||
size_t len = sizeof(sig);
|
||||
auto len = sizeof(sig);
|
||||
if (secp256k1_ecdsa_signature_serialize_der(
|
||||
secp256k1Context(), sig, &len, &sig_imp) != 1)
|
||||
LogicError("sign: secp256k1_ecdsa_signature_serialize_der failed");
|
||||
|
||||
@@ -39,28 +39,12 @@ Seed::~Seed()
|
||||
secure_erase(buf_.data(), buf_.size());
|
||||
}
|
||||
|
||||
Seed::Seed(Slice const& slice)
|
||||
{
|
||||
if (slice.size() != buf_.size())
|
||||
LogicError("Seed::Seed: invalid size");
|
||||
std::memcpy(buf_.data(), slice.data(), buf_.size());
|
||||
}
|
||||
|
||||
Seed::Seed(uint128 const& seed)
|
||||
{
|
||||
if (seed.size() != buf_.size())
|
||||
LogicError("Seed::Seed: invalid size");
|
||||
std::memcpy(buf_.data(), seed.data(), buf_.size());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
Seed
|
||||
randomSeed()
|
||||
{
|
||||
std::array<std::uint8_t, 16> buffer;
|
||||
beast::rngfill(buffer.data(), buffer.size(), crypto_prng());
|
||||
Seed seed(makeSlice(buffer));
|
||||
beast::rngfill(buffer, crypto_prng());
|
||||
Seed seed(buffer);
|
||||
secure_erase(buffer.data(), buffer.size());
|
||||
return seed;
|
||||
}
|
||||
@@ -117,7 +101,7 @@ parseGenericSeed(std::string const& str, bool rfc1751)
|
||||
if (RFC1751::getKeyFromEnglish(key, str) == 1)
|
||||
{
|
||||
Blob const blob(key.rbegin(), key.rend());
|
||||
return Seed{uint128{blob}};
|
||||
return Seed{makeSlice(blob)};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,6 +153,7 @@ transResults()
|
||||
MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."),
|
||||
MAKE_ERROR(tefPAST_MANIFEST_SEQ, "The submitted manifest's sequence is not newer than the current."),
|
||||
MAKE_ERROR(tefREVOKED_MANIFEST, "The submitted manifest is for a revoked master key."),
|
||||
MAKE_ERROR(tefMANIFEST_KEY_IN_USE, "A key the submitted manifest names is already in use by another manifest."),
|
||||
|
||||
MAKE_ERROR(telLOCAL_ERROR, "Local failure."),
|
||||
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),
|
||||
@@ -174,6 +175,7 @@ transResults()
|
||||
MAKE_ERROR(telIMPORT_VL_KEY_NOT_RECOGNISED, "Import vl key was not recognized."),
|
||||
MAKE_ERROR(telCAN_NOT_QUEUE_IMPORT, "Import transaction was not able to be directly applied and cannot be queued."),
|
||||
MAKE_ERROR(telENV_RPC_FAILED, "Unit test RPC failure."),
|
||||
MAKE_ERROR(telMANIFEST_FEE_MISMATCH, "Unsigned SetManifest fee is not, or cannot be confirmed as, canonical for this server's ledger fee settings."),
|
||||
|
||||
MAKE_ERROR(temMALFORMED, "Malformed transaction."),
|
||||
MAKE_ERROR(temBAD_AMM_TOKENS, "Malformed: Invalid LPTokens."),
|
||||
|
||||
@@ -49,6 +49,9 @@ TxMeta::TxMeta(
|
||||
|
||||
if (obj.isFieldPresent(sfHookEmissions))
|
||||
setHookEmissions(obj.getFieldArray(sfHookEmissions));
|
||||
|
||||
if (obj.isFieldPresent(sfRandomData))
|
||||
setRandomData(obj.getFieldH256(sfRandomData));
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
|
||||
@@ -75,6 +78,9 @@ TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
|
||||
|
||||
if (obj.isFieldPresent(sfHookEmissions))
|
||||
setHookEmissions(obj.getFieldArray(sfHookEmissions));
|
||||
|
||||
if (obj.isFieldPresent(sfRandomData))
|
||||
setRandomData(obj.getFieldH256(sfRandomData));
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, Blob const& vec)
|
||||
@@ -245,6 +251,9 @@ TxMeta::getAsObject() const
|
||||
if (hasHookEmissions())
|
||||
metaData.setFieldArray(sfHookEmissions, getHookEmissions());
|
||||
|
||||
if (hasRandomData())
|
||||
metaData.setFieldH256(sfRandomData, getRandomData());
|
||||
|
||||
return metaData;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2014 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or 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 <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/SystemParameters.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <string_view>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
// For details on the protocol-level serialization please visit
|
||||
// https://xrpl.org/serialization.html#currency-codes
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Characters we are willing to allow in the ASCII representation of a
|
||||
// three-letter currency code.
|
||||
constexpr std::string_view isoCharSet =
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"0123456789"
|
||||
"<>(){}[]|?!@#$%^&*";
|
||||
|
||||
// The location (in bytes) of the 3 digit currency inside a 160-bit value
|
||||
constexpr std::size_t isoCodeOffset = 12;
|
||||
|
||||
// The length of an ISO-4217 like code
|
||||
constexpr std::size_t isoCodeLength = 3;
|
||||
|
||||
} // namespace detail
|
||||
|
||||
std::string
|
||||
to_string(Currency const& currency)
|
||||
{
|
||||
if (currency == beast::zero)
|
||||
return systemCurrencyCode();
|
||||
|
||||
if (currency == noCurrency())
|
||||
return "1";
|
||||
|
||||
static constexpr Currency sIsoBits(
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFF000000FFFFFFFFFF");
|
||||
|
||||
if ((currency & sIsoBits).isZero())
|
||||
{
|
||||
std::string const iso(
|
||||
currency.data() + detail::isoCodeOffset,
|
||||
currency.data() + detail::isoCodeOffset + detail::isoCodeLength);
|
||||
|
||||
// Specifying the system currency code using ISO-style representation
|
||||
// is not allowed.
|
||||
if ((iso != systemCurrencyCode()) &&
|
||||
(iso.find_first_not_of(detail::isoCharSet) == std::string::npos))
|
||||
{
|
||||
return iso;
|
||||
}
|
||||
}
|
||||
|
||||
return strHex(currency);
|
||||
}
|
||||
|
||||
bool
|
||||
to_currency(Currency& currency, std::string const& code)
|
||||
{
|
||||
if (code.empty() || !code.compare(systemCurrencyCode()))
|
||||
{
|
||||
currency = beast::zero;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle ISO-4217-like 3-digit character codes.
|
||||
if (code.size() == detail::isoCodeLength)
|
||||
{
|
||||
if (code.find_first_not_of(detail::isoCharSet) != std::string::npos)
|
||||
return false;
|
||||
|
||||
currency = beast::zero;
|
||||
|
||||
std::copy(
|
||||
code.begin(), code.end(), currency.begin() + detail::isoCodeOffset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return currency.parseHex(code);
|
||||
}
|
||||
|
||||
Currency
|
||||
to_currency(std::string const& code)
|
||||
{
|
||||
Currency currency;
|
||||
if (!to_currency(currency, code))
|
||||
currency = noCurrency();
|
||||
return currency;
|
||||
}
|
||||
|
||||
Currency const&
|
||||
xrpCurrency()
|
||||
{
|
||||
static Currency const currency(beast::zero);
|
||||
return currency;
|
||||
}
|
||||
|
||||
Currency const&
|
||||
noCurrency()
|
||||
{
|
||||
static Currency const currency(1);
|
||||
return currency;
|
||||
}
|
||||
|
||||
Currency const&
|
||||
badCurrency()
|
||||
{
|
||||
static Currency const currency(0x5841480000000000);
|
||||
return currency;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -545,7 +545,7 @@ b58_to_b256_be(std::string_view input, std::span<std::uint8_t> out)
|
||||
XRPL_ASSERT(
|
||||
num_b_58_10_coeffs <= b_58_10_coeff.size(),
|
||||
"ripple::b58_fast::detail::b58_to_b256_be : maximum coeff");
|
||||
for (auto c : input.substr(0, partial_coeff_len))
|
||||
for (unsigned char const c : input.substr(0, partial_coeff_len))
|
||||
{
|
||||
auto cur_val = ::ripple::alphabetReverse[c];
|
||||
if (cur_val < 0)
|
||||
@@ -559,7 +559,7 @@ b58_to_b256_be(std::string_view input, std::span<std::uint8_t> out)
|
||||
{
|
||||
for (int j = 0; j < num_full_coeffs; ++j)
|
||||
{
|
||||
auto c = input[partial_coeff_len + j * 10 + i];
|
||||
unsigned char const c = input[partial_coeff_len + j * 10 + i];
|
||||
auto cur_val = ::ripple::alphabetReverse[c];
|
||||
if (cur_val < 0)
|
||||
{
|
||||
@@ -580,6 +580,12 @@ b58_to_b256_be(std::string_view input, std::span<std::uint8_t> out)
|
||||
{
|
||||
std::uint64_t const c = b_58_10_coeff[i];
|
||||
|
||||
// Each digit is < 58, so at most 4 words are in use here and the
|
||||
// span below always fits `result`. Guard the invariant anyway rather
|
||||
// than letting a violation index past the end of the stack array.
|
||||
if (cur_result_size >= result.size())
|
||||
return Unexpected(TokenCodecErrc::inputTooLarge);
|
||||
|
||||
{
|
||||
auto code = ripple::b58_fast::detail::inplace_bigint_mul(
|
||||
std::span(&result[0], cur_result_size + 1), B_58_10);
|
||||
|
||||
@@ -69,7 +69,7 @@ HTTPReply(
|
||||
// CHECKME this returns a different version than the replies below. Is
|
||||
// this by design or an accident or should it be using
|
||||
// BuildInfo::getFullVersionString () as well?
|
||||
output("Server: " + systemName() + "-json-rpc/v1");
|
||||
output("Server: " + std::string(systemName) + "-json-rpc/v1");
|
||||
output("\r\n");
|
||||
|
||||
// Be careful in modifying this! If you change the contents you MUST
|
||||
@@ -147,7 +147,7 @@ HTTPReply(
|
||||
"\r\n"
|
||||
"Content-Type: application/json; charset=UTF-8\r\n");
|
||||
|
||||
output("Server: " + systemName() + "-json-rpc/");
|
||||
output("Server: " + std::string(systemName) + "-json-rpc/");
|
||||
output(BuildInfo::getFullVersionString());
|
||||
output(
|
||||
"\r\n"
|
||||
|
||||
@@ -21,9 +21,15 @@
|
||||
#include <xrpl/beast/core/LexicalCast.h>
|
||||
#include <xrpl/beast/rfc2616.h>
|
||||
#include <xrpl/server/Port.h>
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -63,6 +69,14 @@ operator<<(std::ostream& os, Port const& p)
|
||||
}
|
||||
}
|
||||
|
||||
if (!p.pwa_domains.empty())
|
||||
{
|
||||
os << "domains:";
|
||||
for (auto const& d : p.pwa_domains)
|
||||
os << " " << d;
|
||||
os << ", ";
|
||||
}
|
||||
|
||||
if (p.secure_gateway_nets_v4.size() || p.secure_gateway_nets_v6.size())
|
||||
{
|
||||
os << "secure_gateway nets:";
|
||||
@@ -205,7 +219,7 @@ parse_Port(ParsedPort& port, Section const& section, std::ostream& log)
|
||||
{
|
||||
try
|
||||
{
|
||||
port.ip = boost::asio::ip::address::from_string(*optResult);
|
||||
port.ip = boost::asio::ip::make_address(*optResult);
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
@@ -308,6 +322,64 @@ parse_Port(ParsedPort& port, Section const& section, std::ostream& log)
|
||||
port.secure_gateway_nets_v4,
|
||||
port.secure_gateway_nets_v6);
|
||||
|
||||
// pwa ports: the zone(s) documents are served under, normalised like a
|
||||
// Host header: lower case, no trailing dot, LDH labels of 1..63, with
|
||||
// room left for the longest "pwa<AccountIndex>" label in front.
|
||||
if (auto const optResult = section.get("domain"))
|
||||
{
|
||||
std::vector<std::string> domains;
|
||||
boost::algorithm::split(
|
||||
domains, *optResult, boost::algorithm::is_any_of(","));
|
||||
for (auto& domain : domains)
|
||||
{
|
||||
boost::algorithm::trim(domain);
|
||||
boost::algorithm::to_lower(domain);
|
||||
if (!domain.empty() && domain.back() == '.')
|
||||
domain.pop_back();
|
||||
if (domain.empty())
|
||||
continue;
|
||||
bool valid = domain.size() + 24 <= 253;
|
||||
std::vector<std::string> labels;
|
||||
boost::algorithm::split(
|
||||
labels, domain, boost::algorithm::is_any_of("."));
|
||||
for (auto const& l : labels)
|
||||
valid = valid && !l.empty() && l.size() <= 63 &&
|
||||
l.front() != '-' && l.back() != '-' &&
|
||||
l.find_first_not_of(
|
||||
"abcdefghijklmnopqrstuvwxyz0123456789-") ==
|
||||
std::string::npos;
|
||||
if (!valid)
|
||||
{
|
||||
log << "Invalid value '" << domain << "' for key 'domain' in ["
|
||||
<< section.name()
|
||||
<< "]: expected a DNS zone such as pwapp.example";
|
||||
Throw<std::exception>();
|
||||
}
|
||||
port.pwa_domains.insert(domain);
|
||||
}
|
||||
}
|
||||
|
||||
// Custom domains on pwa ports, and the IPv4 recursive resolver to use for
|
||||
// their TXT records (default: the system resolver).
|
||||
if (auto const v = section.get("custom_domains"))
|
||||
if (!beast::lexicalCastChecked(port.pwa_custom_domains, *v))
|
||||
{
|
||||
log << "Invalid value '" << *v << "' for key 'custom_domains' in ["
|
||||
<< section.name() << "]: expected 0 or 1";
|
||||
Throw<std::exception>();
|
||||
}
|
||||
if (auto const v = section.get("resolver"))
|
||||
{
|
||||
auto const ep = beast::IP::Endpoint::from_string_checked(*v);
|
||||
if (!ep || !ep->is_v4() || ep->address().is_unspecified())
|
||||
{
|
||||
log << "Invalid value '" << *v << "' for key 'resolver' in ["
|
||||
<< section.name() << "]: expected an IPv4 address[:port]";
|
||||
Throw<std::exception>();
|
||||
}
|
||||
port.pwa_resolver = ep->port() ? *ep : ep->at_port(53);
|
||||
}
|
||||
|
||||
set(port.user, "user", section);
|
||||
set(port.password, "password", section);
|
||||
set(port.admin_user, "admin_user", section);
|
||||
|
||||
@@ -1271,6 +1271,79 @@ public:
|
||||
BEAST_EXPECT(table->needValidatedLedger(257));
|
||||
}
|
||||
|
||||
void
|
||||
testIgnoredUnsupported()
|
||||
{
|
||||
testcase("ignore_amendments");
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
weeks constexpr w(1);
|
||||
test::jtx::Env env{*this, makeConfig()};
|
||||
|
||||
static std::vector<AmendmentTable::FeatureInfo> const supported =
|
||||
makeDefaultYes(yes_);
|
||||
|
||||
// Ignore every unsupported_ amendment except the first one.
|
||||
BEAST_EXPECT(unsupported_.size() > 1);
|
||||
std::vector<std::string> const ignoredNames(
|
||||
unsupported_.begin() + 1, unsupported_.end());
|
||||
|
||||
auto table = make_AmendmentTable(
|
||||
env.app(),
|
||||
w,
|
||||
supported,
|
||||
emptySection_,
|
||||
emptySection_,
|
||||
journal_,
|
||||
makeSection(ignoredNames));
|
||||
|
||||
for (auto const& a : ignoredNames)
|
||||
BEAST_EXPECT(table->isSupported(amendmentId(a)));
|
||||
BEAST_EXPECT(!table->isSupported(amendmentId(unsupported_[0])));
|
||||
|
||||
majorityAmendments_t const majority;
|
||||
|
||||
// Enabling only ignored amendments must not block.
|
||||
std::set<uint256> enabled;
|
||||
for (auto const& a : ignoredNames)
|
||||
enabled.insert(amendmentId(a));
|
||||
table->doValidatedLedger(1, enabled, majority);
|
||||
BEAST_EXPECT(!table->hasUnsupportedEnabled());
|
||||
BEAST_EXPECT(table->unsupportedAmendments().empty());
|
||||
|
||||
// An ignored amendment must never be voted for.
|
||||
auto const desired = table->getDesired();
|
||||
for (auto const& a : ignoredNames)
|
||||
BEAST_EXPECT(
|
||||
std::find(desired.begin(), desired.end(), amendmentId(a)) ==
|
||||
desired.end());
|
||||
|
||||
// A non-ignored unsupported amendment still blocks.
|
||||
enabled.insert(amendmentId(unsupported_[0]));
|
||||
table->doValidatedLedger(2, enabled, majority);
|
||||
BEAST_EXPECT(table->hasUnsupportedEnabled());
|
||||
|
||||
// Bad entries are rejected.
|
||||
Section bad("ignore_amendments");
|
||||
bad.append("NotARealFeatureName");
|
||||
try
|
||||
{
|
||||
make_AmendmentTable(
|
||||
env.app(),
|
||||
w,
|
||||
supported,
|
||||
emptySection_,
|
||||
emptySection_,
|
||||
journal_,
|
||||
bad);
|
||||
fail("expected bad [ignore_amendments] entry to throw");
|
||||
}
|
||||
catch (std::runtime_error const&)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFeature(FeatureBitset const& feat)
|
||||
{
|
||||
@@ -1294,6 +1367,7 @@ public:
|
||||
testBadConfig();
|
||||
testEnableVeto();
|
||||
testHasUnsupported();
|
||||
testIgnoredUnsupported();
|
||||
testFeature(all - fixMajorityCalc);
|
||||
testFeature(all);
|
||||
}
|
||||
|
||||
@@ -578,6 +578,25 @@ public:
|
||||
ter(tecNO_TARGET));
|
||||
env.close();
|
||||
}
|
||||
// HookName removed with a HookName-only update
|
||||
// The sfHooks entry keeps an STI_NOTPRESENT placeholder for
|
||||
// sfHookName, which preclaim must treat as "no name".
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::Account] = issuer.human();
|
||||
jv[jss::TransactionType] = jss::SetHook;
|
||||
jv[jss::Flags] = 0;
|
||||
jv[jss::Hooks] = Json::Value{Json::arrayValue};
|
||||
jv[jss::Hooks][0U][jss::Hook][jss::HookName] = "";
|
||||
env(jv, fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
// Vaild HookOn
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
pUrl_.domain,
|
||||
pUrl_.path,
|
||||
port_,
|
||||
env_.app().getIOService(),
|
||||
env_.app().getIOContext(),
|
||||
env_.journal,
|
||||
env_.app().config(),
|
||||
lastEndpoint,
|
||||
@@ -78,10 +78,11 @@ public:
|
||||
isMultipleEndpoints()
|
||||
{
|
||||
using boost::asio::ip::tcp;
|
||||
tcp::resolver resolver(env_.app().getIOService());
|
||||
tcp::resolver resolver(env_.app().getIOContext());
|
||||
std::string port = pUrl_.port ? std::to_string(*pUrl_.port) : "443";
|
||||
tcp::resolver::iterator it = resolver.resolve(pUrl_.domain, port);
|
||||
tcp::resolver::iterator end;
|
||||
auto results = resolver.resolve(pUrl_.domain, port);
|
||||
auto it = results.begin();
|
||||
auto end = results.end();
|
||||
int n = 0;
|
||||
for (; it != end; ++it)
|
||||
++n;
|
||||
|
||||
@@ -301,8 +301,14 @@ struct DepositAuth_test : public beast::unit_test::suite
|
||||
Env env(*this, features);
|
||||
|
||||
env.fund(XRP(10000), gw1, alice, bob);
|
||||
env(trust(gw1, alice["USD"](10), noRipplePrev ? tfSetNoRipple : 0));
|
||||
env(trust(gw1, bob["USD"](10), noRippleNext ? tfSetNoRipple : 0));
|
||||
env(trust(
|
||||
gw1,
|
||||
alice["USD"](10),
|
||||
noRipplePrev ? std::uint32_t(tfSetNoRipple) : 0));
|
||||
env(trust(
|
||||
gw1,
|
||||
bob["USD"](10),
|
||||
noRippleNext ? std::uint32_t(tfSetNoRipple) : 0));
|
||||
env.trust(USD1(10), alice, bob);
|
||||
|
||||
env(pay(gw1, alice, USD1(10)));
|
||||
@@ -324,8 +330,14 @@ struct DepositAuth_test : public beast::unit_test::suite
|
||||
Env env(*this, features);
|
||||
|
||||
env.fund(XRP(10000), gw1, gw2, alice);
|
||||
env(trust(alice, USD1(10), noRipplePrev ? tfSetNoRipple : 0));
|
||||
env(trust(alice, USD2(10), noRippleNext ? tfSetNoRipple : 0));
|
||||
env(trust(
|
||||
alice,
|
||||
USD1(10),
|
||||
noRipplePrev ? std::uint32_t(tfSetNoRipple) : 0));
|
||||
env(trust(
|
||||
alice,
|
||||
USD2(10),
|
||||
noRippleNext ? std::uint32_t(tfSetNoRipple) : 0));
|
||||
env(pay(gw2, alice, USD2(10)));
|
||||
|
||||
if (withDepositAuth)
|
||||
|
||||
@@ -4722,6 +4722,359 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDestinationCancel(FeatureBitset features)
|
||||
{
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
bool const enabled = features[featureEscrowDestinationCancel];
|
||||
TER const destResult =
|
||||
enabled ? TER{tesSUCCESS} : TER{tecNO_PERMISSION};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const carol = Account("carol");
|
||||
auto const gw = Account{"gateway"};
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
auto const ownerDirEmpty = [](Env& env, Account const& acct) {
|
||||
ripple::Dir dir(*env.current(), keylet::ownerDir(acct.id()));
|
||||
return std::distance(dir.begin(), dir.end()) == 0;
|
||||
};
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: before CancelAfter");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, bob, carol);
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)),
|
||||
finish_time(env.now() + 100s),
|
||||
cancel_time(env.now() + 200s));
|
||||
env.close();
|
||||
|
||||
// Neither the owner nor a third party may cancel early.
|
||||
env(cancel(alice, alice, seq), ter(tecNO_PERMISSION));
|
||||
env(cancel(carol, alice, seq), ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
STAmount const preAlice = env.balance(alice);
|
||||
env(cancel(bob, alice, seq), ter(destResult));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq)));
|
||||
// Funds go back to the owner, not the destination.
|
||||
env.require(balance(alice, preAlice + XRP(1000)));
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
BEAST_EXPECT(ownerDirEmpty(env, alice));
|
||||
BEAST_EXPECT(ownerDirEmpty(env, bob));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq)));
|
||||
// Rejected outright: nothing moves.
|
||||
env.require(balance(alice, preAlice));
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 1);
|
||||
BEAST_EXPECT(!ownerDirEmpty(env, bob));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: after FinishAfter");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// Finishable, and with no CancelAfter, otherwise never cancellable.
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)), finish_time(env.now() + 1s));
|
||||
env.close(5s);
|
||||
|
||||
env(cancel(alice, alice, seq), ter(tecNO_PERMISSION));
|
||||
env(cancel(bob, alice, seq), ter(destResult));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq)));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq)));
|
||||
env(finish(bob, alice, seq));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq)));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: conditional");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// The destination does not need the fulfillment to decline.
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)),
|
||||
condition(cb1),
|
||||
cancel_time(env.now() + 200s));
|
||||
env.close();
|
||||
|
||||
env(cancel(bob, alice, seq), ter(destResult));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq)));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq)));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: self-escrow stays locked");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice);
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, alice, XRP(1000)),
|
||||
finish_time(env.now() + 100s),
|
||||
cancel_time(env.now() + 200s));
|
||||
env.close();
|
||||
|
||||
env(cancel(alice, alice, seq), ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq)));
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: EscrowID");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, bob);
|
||||
env.close();
|
||||
|
||||
uint256 const escrowId{keylet::escrow(alice, env.seq(alice)).key};
|
||||
env(escrow(alice, bob, XRP(1000)),
|
||||
finish_time(env.now() + 100s),
|
||||
cancel_time(env.now() + 200s));
|
||||
env.close();
|
||||
|
||||
env(cancel(bob, alice), escrow_id(escrowId), ter(destResult));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::unchecked(escrowId)));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(env.le(keylet::unchecked(escrowId)));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: owner hook cannot block");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, bob, carol);
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s));
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
// Past CancelAfter, so only the owner's hook stands in the way.
|
||||
env(jtx::hook(alice, {{hso(jtx::genesis::RollbackHook)}}, 0),
|
||||
fee(XRP(2)));
|
||||
env.close();
|
||||
|
||||
// The owner stays a strong tsh when a third party cancels.
|
||||
env(cancel(carol, alice, seq), fee(XRP(1)), ter(tecHOOK_REJECTED));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
env(cancel(bob, alice, seq), fee(XRP(1)));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq)));
|
||||
}
|
||||
else
|
||||
{
|
||||
env(cancel(bob, alice, seq),
|
||||
fee(XRP(1)),
|
||||
ter(tecHOOK_REJECTED));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq)));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: IOU");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, bob, gw);
|
||||
env.close();
|
||||
env.trust(USD(10000), alice, bob);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(5000)));
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 100s),
|
||||
cancel_time(env.now() + 200s));
|
||||
env.close();
|
||||
BEAST_EXPECT(-lockedAmount(env, alice, gw, USD) == USD(1000));
|
||||
|
||||
env(cancel(bob, alice, seq), ter(destResult));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq)));
|
||||
BEAST_EXPECT(-lockedAmount(env, alice, gw, USD) == USD(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq)));
|
||||
BEAST_EXPECT(-lockedAmount(env, alice, gw, USD) == USD(1000));
|
||||
}
|
||||
env.require(balance(alice, USD(5000)));
|
||||
env.require(balance(bob, USD(0)));
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: issuer-owned IOU");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, gw);
|
||||
env.close();
|
||||
env.trust(USD(10000), alice);
|
||||
env.close();
|
||||
|
||||
// An issuer escrowing its own IOU locks nothing, so the cancel
|
||||
// only has to unwind the escrow itself.
|
||||
auto const seq = env.seq(gw);
|
||||
env(escrow(gw, alice, USD(1000)),
|
||||
finish_time(env.now() + 100s),
|
||||
cancel_time(env.now() + 200s));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.ownerCount(gw) == 1);
|
||||
|
||||
env(cancel(gw, gw, seq), ter(tecNO_PERMISSION));
|
||||
env(cancel(alice, gw, seq), ter(destResult));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(gw.id(), seq)));
|
||||
BEAST_EXPECT(env.ownerCount(gw) == 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(env.le(keylet::escrow(gw.id(), seq)));
|
||||
BEAST_EXPECT(env.ownerCount(gw) == 1);
|
||||
}
|
||||
env.require(balance(alice, USD(0)));
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: owner hook chain not charged");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, bob, carol);
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s));
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
auto const feeFor = [&](Account const& acct) {
|
||||
return calculateBaseFee(
|
||||
*env.current(), *env.jt(cancel(acct, alice, seq)).stx);
|
||||
};
|
||||
|
||||
XRPAmount const bobBefore = feeFor(bob);
|
||||
XRPAmount const carolBefore = feeFor(carol);
|
||||
|
||||
env(jtx::hook(alice, {{hso(jtx::genesis::RollbackHook)}}, 0),
|
||||
fee(XRP(2)));
|
||||
env.close();
|
||||
|
||||
// A third party still pays for the owner's (strong) hook chain.
|
||||
BEAST_EXPECT(feeFor(carol) > carolBefore);
|
||||
|
||||
// The destination does too, but only without the amendment.
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(feeFor(bob) == bobBefore);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(feeFor(bob) > bobBefore);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// fix1571 is still a live (DefaultYes) amendment on Xahau, so the
|
||||
// legacy CancelAfter check in EscrowCancel remains reachable.
|
||||
testcase("Destination Cancel: without fix1571");
|
||||
Env env{*this, features - fix1571};
|
||||
env.fund(XRP(5000), alice, bob, carol);
|
||||
env.close();
|
||||
|
||||
// No CancelAfter.
|
||||
auto const seq1 = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)), finish_time(env.now() + 100s));
|
||||
|
||||
// CancelAfter in the future.
|
||||
auto const seq2 = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)),
|
||||
finish_time(env.now() + 100s),
|
||||
cancel_time(env.now() + 200s));
|
||||
|
||||
// CancelAfter about to pass.
|
||||
auto const seq3 = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s));
|
||||
env.close();
|
||||
|
||||
env(cancel(alice, alice, seq1), ter(tecNO_PERMISSION));
|
||||
env(cancel(carol, alice, seq2), ter(tecNO_PERMISSION));
|
||||
env(cancel(bob, alice, seq1), ter(destResult));
|
||||
env(cancel(bob, alice, seq2), ter(destResult));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq1)));
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq2)));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq1)));
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq2)));
|
||||
}
|
||||
|
||||
// Past CancelAfter anyone may cancel, with or without the
|
||||
// amendment.
|
||||
env(cancel(carol, alice, seq3));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq3)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -4770,12 +5123,17 @@ public:
|
||||
{
|
||||
using namespace test::jtx;
|
||||
FeatureBitset const all{supported_amendments() | featureCredentials};
|
||||
testWithFeats(all - featurePaychanAndEscrowForTokens);
|
||||
testWithFeats(all);
|
||||
testIOUWithFeats(all - featureClawback);
|
||||
testIOUWithFeats(all);
|
||||
// The pre-existing suites assert that the destination cannot cancel
|
||||
// early, so they run without featureEscrowDestinationCancel.
|
||||
FeatureBitset const legacy{all - featureEscrowDestinationCancel};
|
||||
testWithFeats(legacy - featurePaychanAndEscrowForTokens);
|
||||
testWithFeats(legacy);
|
||||
testIOUWithFeats(legacy - featureClawback);
|
||||
testIOUWithFeats(legacy);
|
||||
testEscrowID(all);
|
||||
testCredentials(all);
|
||||
testDestinationCancel(legacy);
|
||||
testDestinationCancel(all);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+490
-47
@@ -590,6 +590,134 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
test_emitted_txn_entry(FeatureBitset features)
|
||||
{
|
||||
testcase("Test emitted txn ledger entry");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace hook;
|
||||
using namespace hook_api;
|
||||
|
||||
auto const alice = Account{"alice"};
|
||||
|
||||
for (bool const withFix : {true, false})
|
||||
{
|
||||
Env env{*this, withFix ? features : features - fix20261005};
|
||||
|
||||
// emissions are only written into non-open views, so apply on
|
||||
// top of the last closed ledger the way consensus does
|
||||
auto const closed = env.closed();
|
||||
OpenView ov{&*closed};
|
||||
BEAST_EXPECT(!ov.open());
|
||||
|
||||
STTx const invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
||||
ApplyContext applyCtx{
|
||||
env.app(),
|
||||
ov,
|
||||
invokeTx,
|
||||
tesSUCCESS,
|
||||
env.current()->fees().base,
|
||||
tapNONE,
|
||||
env.journal};
|
||||
|
||||
STTx const emitTx = STTx(ttINVOKE, [&](STObject& obj) {
|
||||
obj[sfAccount] = alice.id();
|
||||
obj[sfSequence] = 0;
|
||||
obj[sfSigningPubKey] = Slice{};
|
||||
obj[sfFirstLedgerSequence] = closed->seq() + 1;
|
||||
obj[sfLastLedgerSequence] = closed->seq() + 5;
|
||||
obj[sfFee] = closed->fees().base;
|
||||
|
||||
auto& emitDetails = obj.peekFieldObject(sfEmitDetails);
|
||||
emitDetails[sfEmitGeneration] = 1;
|
||||
emitDetails[sfEmitBurden] = 1;
|
||||
emitDetails[sfEmitParentTxnID] = invokeTx.getTransactionID();
|
||||
emitDetails[sfEmitNonce] = uint256();
|
||||
emitDetails[sfEmitHookHash] = uint256();
|
||||
});
|
||||
|
||||
std::string reason;
|
||||
auto const emitted = std::make_shared<ripple::Transaction>(
|
||||
std::make_shared<STTx const>(emitTx), reason, env.app());
|
||||
std::queue<std::shared_ptr<ripple::Transaction>> emittedTxn;
|
||||
emittedTxn.push(emitted);
|
||||
|
||||
auto hookCtx = makeStubHookContext(
|
||||
applyCtx,
|
||||
alice.id(),
|
||||
alice.id(),
|
||||
{.result = {.emittedTxn = emittedTxn}});
|
||||
|
||||
BEAST_EXPECT(
|
||||
hook::finalizeHookResult(hookCtx.result, applyCtx, true) ==
|
||||
tesSUCCESS);
|
||||
|
||||
auto const kl = keylet::emittedTxn(emitted->getID());
|
||||
auto const sle = applyCtx.view().peek(kl);
|
||||
if (!BEAST_EXPECT(sle))
|
||||
continue;
|
||||
|
||||
Serializer expected;
|
||||
emitTx.add(expected);
|
||||
|
||||
// in memory: with the fix sfEmittedTxn occupies its template
|
||||
// slot exactly once, without it a duplicate is appended behind
|
||||
// the (non-present) template slot and the field reads absent
|
||||
auto const entries =
|
||||
std::count_if(sle->begin(), sle->end(), [](STBase const& f) {
|
||||
return f.getFName() == sfEmittedTxn;
|
||||
});
|
||||
BEAST_EXPECT(entries == (withFix ? 1 : 2));
|
||||
BEAST_EXPECT(sle->isFieldPresent(sfEmittedTxn) == withFix);
|
||||
|
||||
// serialized: identical either way, re-reading the entry always
|
||||
// yields exactly the emitted transaction
|
||||
{
|
||||
Serializer s;
|
||||
sle->add(s);
|
||||
SerialIter sit(s.slice());
|
||||
STLedgerEntry const reread(sit, kl.key);
|
||||
BEAST_EXPECT(reread.isFieldPresent(sfEmittedTxn));
|
||||
Serializer txn;
|
||||
reread.peekAtField(sfEmittedTxn).add(txn);
|
||||
BEAST_EXPECT(txn.peekData() == expected.peekData());
|
||||
}
|
||||
|
||||
// a hook running later in the same ledger can tell them apart:
|
||||
// slot_subfield only finds sfEmittedTxn with the fix
|
||||
auto readerCtx =
|
||||
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
||||
auto& api = readerCtx.api();
|
||||
|
||||
Bytes klBytes{
|
||||
static_cast<uint8_t>((kl.type >> 8) & 0xFFU),
|
||||
static_cast<uint8_t>(kl.type & 0xFFU)};
|
||||
klBytes.insert(klBytes.end(), kl.key.begin(), kl.key.end());
|
||||
|
||||
auto const slotNo = api.slot_set(klBytes, 0);
|
||||
if (!BEAST_EXPECT(slotNo.has_value()))
|
||||
continue;
|
||||
|
||||
auto const sub =
|
||||
api.slot_subfield(slotNo.value(), sfEmittedTxn.getCode(), 0);
|
||||
if (!withFix)
|
||||
{
|
||||
BEAST_EXPECT(!sub.has_value() && sub.error() == DOESNT_EXIST);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!BEAST_EXPECT(sub.has_value()))
|
||||
continue;
|
||||
auto const field = api.slot(sub.value());
|
||||
if (!BEAST_EXPECT(field.has_value()))
|
||||
continue;
|
||||
Serializer s;
|
||||
field.value()->add(s);
|
||||
BEAST_EXPECT(s.peekData() == expected.peekData());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
test_etxn_details(FeatureBitset features)
|
||||
{
|
||||
@@ -733,7 +861,7 @@ public:
|
||||
BEAST_EXPECT(result.value() == baseFee + memoSize);
|
||||
}
|
||||
{
|
||||
// fix20261001: the minimum fee is computed against the view
|
||||
// fix20260929: the minimum fee is computed against the view
|
||||
// being applied, not the node-local open ledger. Simulate that
|
||||
// by inserting a hook definition (with a callback fee) into
|
||||
// applyCtx.view() only -- it is never inserted into the open
|
||||
@@ -777,7 +905,7 @@ public:
|
||||
auto const result = api.etxn_fee_base(emittedTxSerializer.slice());
|
||||
BEAST_EXPECT(result.has_value());
|
||||
|
||||
if (env.closed()->rules().enabled(fix20261001))
|
||||
if (env.closed()->rules().enabled(fix20260929))
|
||||
BEAST_EXPECT(result.value() == baseline.value() + 12345);
|
||||
else
|
||||
BEAST_EXPECT(result.value() == baseline.value());
|
||||
@@ -1828,11 +1956,31 @@ public:
|
||||
BEAST_EXPECT(api.float_set(-50, 0).value() == 0);
|
||||
BEAST_EXPECT(api.float_set(0, 0).value() == 0);
|
||||
|
||||
// an exponent lower than -96 should produce an invalid float error
|
||||
BEAST_EXPECT(api.float_set(-97, 1).error() == INVALID_FLOAT);
|
||||
// fix20261005: exponent out of range is reported as
|
||||
// EXPONENT_UNDERSIZED / EXPONENT_OVERSIZED; before the fix both
|
||||
// cases were reported as INVALID_FLOAT
|
||||
bool const fixExpCodes = env.current()->rules().enabled(fix20261005);
|
||||
auto const undersized =
|
||||
fixExpCodes ? EXPONENT_UNDERSIZED : INVALID_FLOAT;
|
||||
auto const oversized = fixExpCodes ? EXPONENT_OVERSIZED : INVALID_FLOAT;
|
||||
|
||||
// an exponent larger than +96 should produce an invalid float error
|
||||
BEAST_EXPECT(api.float_set(+97, 1).error() == INVALID_FLOAT);
|
||||
// an exponent lower than -96 should produce an underflow error
|
||||
BEAST_EXPECT(api.float_set(-97, 1).error() == undersized);
|
||||
|
||||
// an exponent larger than +96 should produce an overflow error
|
||||
BEAST_EXPECT(api.float_set(+97, 1).error() == oversized);
|
||||
|
||||
// the -96..80 range applies to the exponent after the mantissa has
|
||||
// been normalized to 16 digits (1 -> 1e15, exponent - 15)
|
||||
auto const oneE15 = [](int32_t exp) {
|
||||
return hook::hook_float::make_float(1000000000000000ULL, exp, false)
|
||||
.value();
|
||||
};
|
||||
BEAST_EXPECT(api.float_set(81, 1).value() == oneE15(66));
|
||||
BEAST_EXPECT(api.float_set(95, 1).value() == oneE15(80));
|
||||
BEAST_EXPECT(api.float_set(96, 1).error() == oversized);
|
||||
BEAST_EXPECT(api.float_set(-81, 1).value() == oneE15(-96));
|
||||
BEAST_EXPECT(api.float_set(-82, 1).error() == undersized);
|
||||
|
||||
// clang-format off
|
||||
std::vector<std::tuple<int32_t, int64_t, uint64_t>> tests = {
|
||||
@@ -2222,22 +2370,19 @@ public:
|
||||
BEAST_EXPECT(api.hook_param(Bytes(33, 1)).error() == TOO_BIG);
|
||||
}
|
||||
|
||||
// {
|
||||
// // Hook params map
|
||||
// Bytes const name{'k'};
|
||||
// Bytes const value{1, 2, 3};
|
||||
// StubHookContext stubCtx{
|
||||
// .result = {
|
||||
// .hookParams = {{{name, value}}},
|
||||
// }};
|
||||
// auto hookCtx =
|
||||
// makeStubHookContext(applyCtx, alice.id(), alice.id(),
|
||||
// stubCtx);
|
||||
// hook::HookAPI api(hookCtx);
|
||||
// auto const result = api.hook_param(name);
|
||||
// BEAST_EXPECT(result.has_value());
|
||||
// BEAST_EXPECT(result.value() == value);
|
||||
// }
|
||||
{
|
||||
// Hook params map
|
||||
Bytes const name{'k'};
|
||||
Bytes const value{1, 2, 3};
|
||||
StubHookContext stubCtx{};
|
||||
stubCtx.result.hookParams = std::map<Bytes, Bytes>{{name, value}};
|
||||
auto hookCtx =
|
||||
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
||||
auto& api = hookCtx.api();
|
||||
auto const result = api.hook_param(name);
|
||||
BEAST_EXPECT(result.has_value());
|
||||
BEAST_EXPECT(result.value() == value);
|
||||
}
|
||||
|
||||
{
|
||||
// Override deletion wins
|
||||
@@ -2898,6 +3043,91 @@ public:
|
||||
BEAST_EXPECT(acct->value() == alice.id());
|
||||
}
|
||||
|
||||
void
|
||||
test_otxn_field_emit_failure(FeatureBitset features)
|
||||
{
|
||||
testcase("Test otxn_field on emit failure callback");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace hook_api;
|
||||
|
||||
auto const alice = Account{"alice"};
|
||||
auto const bob = Account{"bob"};
|
||||
|
||||
// the cleanup pseudo carries sfLedgerSequence / sfTransactionHash,
|
||||
// the original emitted txn carries sfDestination
|
||||
STTx const pseudo = STTx(ttEMIT_FAILURE, [&](STObject& obj) {
|
||||
obj.setFieldU32(sfLedgerSequence, 1);
|
||||
obj.setFieldH256(sfTransactionHash, uint256{5});
|
||||
});
|
||||
STTx const emitted = STTx(ttPAYMENT, [&](STObject& obj) {
|
||||
obj[sfAccount] = alice.id();
|
||||
obj[sfDestination] = bob.id();
|
||||
});
|
||||
|
||||
for (bool const withFix : {false, true})
|
||||
{
|
||||
Env env{
|
||||
*this,
|
||||
withFix ? features | fix20261005 : features - fix20261005};
|
||||
OpenView ov{*env.current()};
|
||||
ApplyContext applyCtx = createApplyContext(env, ov, pseudo);
|
||||
StubHookContext stubCtx{.emitFailure = emitted};
|
||||
auto hookCtx =
|
||||
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
||||
auto& api = hookCtx.api();
|
||||
|
||||
// present on both: read from the emitted txn either way
|
||||
{
|
||||
auto const result = api.otxn_field(sfAccount.getCode());
|
||||
BEAST_EXPECT(result.has_value());
|
||||
auto const* acct =
|
||||
dynamic_cast<STAccount const*>(result.value());
|
||||
BEAST_EXPECT(acct && acct->value() == alice.id());
|
||||
}
|
||||
|
||||
// only on the emitted txn: before the fix presence was checked
|
||||
// on the pseudo, so the field was reported missing
|
||||
{
|
||||
auto const result = api.otxn_field(sfDestination.getCode());
|
||||
if (withFix)
|
||||
{
|
||||
BEAST_EXPECT(result.has_value());
|
||||
auto const* dest =
|
||||
dynamic_cast<STAccount const*>(result.value());
|
||||
BEAST_EXPECT(dest && dest->value() == bob.id());
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
!result.has_value() && result.error() == DOESNT_EXIST);
|
||||
}
|
||||
}
|
||||
|
||||
// only on the pseudo: before the fix this throws out of
|
||||
// getField on the emitted txn
|
||||
if (withFix)
|
||||
{
|
||||
auto const result = api.otxn_field(sfLedgerSequence.getCode());
|
||||
BEAST_EXPECT(
|
||||
!result.has_value() && result.error() == DOESNT_EXIST);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool threw = false;
|
||||
try
|
||||
{
|
||||
(void)api.otxn_field(sfLedgerSequence.getCode());
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
threw = true;
|
||||
}
|
||||
BEAST_EXPECT(threw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
test_otxn_id(FeatureBitset features)
|
||||
{
|
||||
@@ -3284,21 +3514,30 @@ public:
|
||||
using namespace hook_api;
|
||||
|
||||
auto const alice = Account{"alice"};
|
||||
Env env{*this, features};
|
||||
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
||||
OpenView ov{*env.current()};
|
||||
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
||||
auto hookCtx =
|
||||
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
||||
auto& api = hookCtx.api();
|
||||
for (bool const withFix : {true, false})
|
||||
{
|
||||
Env env{*this, withFix ? features : features - fix20261005};
|
||||
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
||||
OpenView ov{*env.current()};
|
||||
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
||||
auto hookCtx =
|
||||
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
||||
auto& api = hookCtx.api();
|
||||
|
||||
// Invalid argument (wrong size)
|
||||
BEAST_EXPECT(
|
||||
api.slot_set(Bytes{1, 2, 3}, 0).error() == INVALID_ARGUMENT);
|
||||
// Invalid argument (slot_no beyond max)
|
||||
BEAST_EXPECT(
|
||||
api.slot_set(Bytes(32, 0), hook_api::max_slots + 1).error() ==
|
||||
INVALID_ARGUMENT);
|
||||
// Invalid argument (wrong size)
|
||||
BEAST_EXPECT(
|
||||
api.slot_set(Bytes{1, 2, 3}, 0).error() == INVALID_ARGUMENT);
|
||||
// Invalid argument (slot_no beyond max)
|
||||
BEAST_EXPECT(
|
||||
api.slot_set(Bytes(32, 0), hook_api::max_slots + 1).error() ==
|
||||
INVALID_ARGUMENT);
|
||||
|
||||
// 32-byte txn hash: rejected with fix20261005, looked up (and
|
||||
// not found here) without it
|
||||
BEAST_EXPECT(
|
||||
api.slot_set(Bytes(32, 0), 1).error() ==
|
||||
(withFix ? INVALID_ARGUMENT : DOESNT_EXIST));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -3587,6 +3826,174 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
test_state_foreign_read_accounting(FeatureBitset features)
|
||||
{
|
||||
testcase("Test state_foreign read does not consume write resources");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace hook_api;
|
||||
using namespace hook;
|
||||
|
||||
bool const hasFix = features[fix20261005];
|
||||
bool const hasExtended = features[featureExtendedHookState];
|
||||
|
||||
Env env{*this, features};
|
||||
|
||||
auto const fees = env.current()->fees();
|
||||
auto const alice = Account{"alice"};
|
||||
auto const bob = Account{"bob"};
|
||||
auto const carol = Account{"carol"};
|
||||
env.fund(XRP(10000), alice);
|
||||
// exactly one state reserve slot available
|
||||
env.fund(fees.accountReserve(0) + fees.increment, bob);
|
||||
// no state reserve slot available
|
||||
env.fund(fees.accountReserve(0), carol);
|
||||
env.close();
|
||||
|
||||
uint256 const readKey{1};
|
||||
uint256 const writeKey{2};
|
||||
uint256 const ns{3};
|
||||
Bytes const existingData{0xAA, 0xBB};
|
||||
Bytes writeData{0x01};
|
||||
|
||||
OpenView ov{*env.current()};
|
||||
|
||||
// Place an existing state entry (and its namespace directory) on
|
||||
// ledger for each account, so state_foreign's cache-miss path reads it.
|
||||
auto insertState = [&](AccountID const& acc) {
|
||||
auto const dirKeylet = keylet::hookStateDir(acc, ns);
|
||||
auto dir = std::make_shared<SLE>(dirKeylet);
|
||||
dir->setFieldH256(sfRootIndex, dirKeylet.key);
|
||||
dir->setFieldV256(
|
||||
sfIndexes,
|
||||
STVector256{std::vector<uint256>{
|
||||
keylet::hookState(acc, readKey, ns).key}});
|
||||
ov.rawInsert(dir);
|
||||
|
||||
auto hs =
|
||||
std::make_shared<SLE>(keylet::hookState(acc, readKey, ns));
|
||||
hs->setFieldVL(sfHookStateData, existingData);
|
||||
hs->setFieldH256(sfHookStateKey, readKey);
|
||||
hs->setFieldU64(sfOwnerNode, 0);
|
||||
ov.rawInsert(hs);
|
||||
};
|
||||
insertState(alice.id());
|
||||
insertState(bob.id());
|
||||
insertState(carol.id());
|
||||
|
||||
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
||||
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
||||
|
||||
auto availableSlots = [&](AccountID const& acc) -> int64_t {
|
||||
auto const sle = ov.read(keylet::account(acc));
|
||||
int64_t avail = sle->getFieldAmount(sfBalance).xrp().drops() -
|
||||
fees.accountReserve(sle->getFieldU32(sfOwnerCount)).drops();
|
||||
return avail / fees.increment.drops();
|
||||
};
|
||||
|
||||
{
|
||||
// A read must not consume the shared modification limit.
|
||||
HookStateMap stateMap;
|
||||
auto hookCtx = makeStubHookContext(
|
||||
applyCtx, alice.id(), alice.id(), {}, stateMap);
|
||||
auto& api = hookCtx.api();
|
||||
|
||||
stateMap.modified_entry_count = max_state_modifications - 1;
|
||||
|
||||
auto const read = api.state_foreign(readKey, ns, alice.id());
|
||||
BEAST_EXPECT(read.has_value() && read.value() == existingData);
|
||||
|
||||
int64_t const slots = availableSlots(alice.id());
|
||||
BEAST_EXPECT(slots > 1);
|
||||
BEAST_EXPECT(
|
||||
stateMap.modified_entry_count ==
|
||||
max_state_modifications - (hasFix ? 1 : 0));
|
||||
BEAST_EXPECT(
|
||||
std::get<0>(stateMap[alice.id()]) == slots - (hasFix ? 0 : 1));
|
||||
|
||||
auto const write =
|
||||
api.state_foreign_set(writeKey, ns, alice.id(), writeData);
|
||||
if (hasFix)
|
||||
{
|
||||
BEAST_EXPECT(write.has_value());
|
||||
BEAST_EXPECT(
|
||||
stateMap.modified_entry_count == max_state_modifications);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(!write.has_value());
|
||||
BEAST_EXPECT(write.error() == TOO_MANY_STATE_MODIFICATIONS);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// A read must not consume the reserve budget.
|
||||
HookStateMap stateMap;
|
||||
auto hookCtx =
|
||||
makeStubHookContext(applyCtx, bob.id(), bob.id(), {}, stateMap);
|
||||
auto& api = hookCtx.api();
|
||||
|
||||
BEAST_EXPECT(availableSlots(bob.id()) == 1);
|
||||
|
||||
auto const read = api.state_foreign(readKey, ns, bob.id());
|
||||
BEAST_EXPECT(read.has_value() && read.value() == existingData);
|
||||
BEAST_EXPECT(std::get<0>(stateMap[bob.id()]) == (hasFix ? 1 : 0));
|
||||
|
||||
auto const write =
|
||||
api.state_foreign_set(writeKey, ns, bob.id(), writeData);
|
||||
if (hasFix)
|
||||
{
|
||||
BEAST_EXPECT(write.has_value());
|
||||
BEAST_EXPECT(std::get<0>(stateMap[bob.id()]) == 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(!write.has_value());
|
||||
BEAST_EXPECT(write.error() == RESERVE_INSUFFICIENT);
|
||||
}
|
||||
|
||||
// Modifying the entry that was read needs no new reserve and
|
||||
// is still charged as a modification.
|
||||
auto const before = stateMap.modified_entry_count;
|
||||
Bytes overwrite{0x02};
|
||||
auto const update =
|
||||
api.state_foreign_set(readKey, ns, bob.id(), overwrite);
|
||||
BEAST_EXPECT(update.has_value());
|
||||
BEAST_EXPECT(stateMap.modified_entry_count == before + 1);
|
||||
}
|
||||
|
||||
{
|
||||
// A read on an account with no spare reserve must succeed;
|
||||
// previously ExtendedHookState's sanity check rejected it.
|
||||
HookStateMap stateMap;
|
||||
auto hookCtx = makeStubHookContext(
|
||||
applyCtx, carol.id(), carol.id(), {}, stateMap);
|
||||
auto& api = hookCtx.api();
|
||||
|
||||
BEAST_EXPECT(availableSlots(carol.id()) == 0);
|
||||
|
||||
auto const read = api.state_foreign(readKey, ns, carol.id());
|
||||
if (hasExtended && !hasFix)
|
||||
{
|
||||
BEAST_EXPECT(!read.has_value());
|
||||
BEAST_EXPECT(read.error() == INTERNAL_ERROR);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(read.has_value() && read.value() == existingData);
|
||||
BEAST_EXPECT(stateMap.modified_entry_count == (hasFix ? 0 : 1));
|
||||
}
|
||||
|
||||
// Writing a new key still fails on reserve either way.
|
||||
auto const write =
|
||||
api.state_foreign_set(writeKey, ns, carol.id(), writeData);
|
||||
BEAST_EXPECT(!write.has_value());
|
||||
if (!write.has_value())
|
||||
BEAST_EXPECT(write.error() == RESERVE_INSUFFICIENT);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
test_state_foreign_set_max(FeatureBitset features)
|
||||
{
|
||||
@@ -4050,7 +4457,7 @@ public:
|
||||
sfBaseAsset.getCode(),
|
||||
"011A0000000000000000000000005553440000000000"},
|
||||
};
|
||||
if (env.closed()->rules().enabled(fix20261001))
|
||||
if (env.closed()->rules().enabled(fix20260929))
|
||||
{
|
||||
// encodings the legacy parser measures incorrectly
|
||||
data_list.insert(
|
||||
@@ -4069,7 +4476,7 @@ public:
|
||||
"00000"},
|
||||
});
|
||||
}
|
||||
bool const fixEnabled = env.closed()->rules().enabled(fix20261001);
|
||||
bool const fixEnabled = env.closed()->rules().enabled(fix20260929);
|
||||
for (auto const& data : data_list)
|
||||
{
|
||||
auto source_object = _source_object;
|
||||
@@ -4170,7 +4577,7 @@ public:
|
||||
// should be "F9EA7D02BEEFE1F1" {Memos:[{Memo:{MemoData:"BEEF"}}]}
|
||||
auto const invalid_end_marker = *strUnHex("F9EA7D02BEEFE100");
|
||||
auto const result = api.sto_subarray(invalid_end_marker, 0);
|
||||
if (env.closed()->rules().enabled(fix20261001))
|
||||
if (env.closed()->rules().enabled(fix20260929))
|
||||
{
|
||||
BEAST_EXPECT(!result.has_value());
|
||||
BEAST_EXPECT(result.error() == PARSE_ERROR);
|
||||
@@ -4186,7 +4593,7 @@ public:
|
||||
// truncated.
|
||||
auto const invalid_tail = *strUnHex("F9EA7D02BEEFE1EA7D02BEEFF1");
|
||||
auto const result = api.sto_subarray(invalid_tail, 0);
|
||||
if (env.closed()->rules().enabled(fix20261001))
|
||||
if (env.closed()->rules().enabled(fix20260929))
|
||||
{
|
||||
BEAST_EXPECT(!result.has_value());
|
||||
BEAST_EXPECT(result.error() == PARSE_ERROR);
|
||||
@@ -4260,7 +4667,7 @@ public:
|
||||
auto const invalid_tail = Blob{data.begin(), data.end() - 1};
|
||||
auto const result =
|
||||
api.sto_subfield(invalid_tail, sfSequence.getCode());
|
||||
if (env.closed()->rules().enabled(fix20261001))
|
||||
if (env.closed()->rules().enabled(fix20260929))
|
||||
{
|
||||
BEAST_EXPECT(!result.has_value());
|
||||
BEAST_EXPECT(result.error() == PARSE_ERROR);
|
||||
@@ -4318,8 +4725,20 @@ public:
|
||||
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
||||
auto& api = hookCtx.api();
|
||||
|
||||
BEAST_EXPECT(api.sto_validate(Bytes{}).error() == TOO_SMALL);
|
||||
BEAST_EXPECT(api.sto_validate(Bytes{0x00}).error() == TOO_SMALL);
|
||||
// a buffer shorter than two bytes can't be a valid STObject:
|
||||
// fix20261005 reports it as invalid rather than as TOO_SMALL
|
||||
if (env.closed()->rules().enabled(fix20261005))
|
||||
{
|
||||
BEAST_EXPECT(api.sto_validate(Bytes{}).value() == false);
|
||||
BEAST_EXPECT(api.sto_validate(Bytes{0x00}).value() == false);
|
||||
BEAST_EXPECT(api.sto_validate(Bytes{0xE1}).value() == false);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(api.sto_validate(Bytes{}).error() == TOO_SMALL);
|
||||
BEAST_EXPECT(api.sto_validate(Bytes{0x00}).error() == TOO_SMALL);
|
||||
BEAST_EXPECT(api.sto_validate(Bytes{0xE1}).error() == TOO_SMALL);
|
||||
}
|
||||
|
||||
// { Memo: {MemoData: "BEEF"} }
|
||||
auto const memos = *strUnHex("EA7D02BEEFE1");
|
||||
@@ -4393,6 +4812,22 @@ public:
|
||||
BEAST_EXPECT(accid.has_value());
|
||||
auto aliceid = alice.id();
|
||||
BEAST_EXPECT(accid.value() == Bytes(aliceid.begin(), aliceid.end()));
|
||||
|
||||
// Bytes outside the base58 alphabet, including >= 0x80, must be
|
||||
// rejected wherever they appear (signed-char lookup regression)
|
||||
{
|
||||
std::string const addr = alice.human();
|
||||
for (std::size_t pos = 0; pos < addr.size(); ++pos)
|
||||
{
|
||||
for (int const b : {0x00, 0x7f, 0x80, 0xc2, 0xff})
|
||||
{
|
||||
std::string bad = addr;
|
||||
bad[pos] = static_cast<char>(b);
|
||||
auto const r = api.util_accid(bad);
|
||||
BEAST_EXPECT(!r && r.error() == INVALID_ARGUMENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -4762,19 +5197,21 @@ public:
|
||||
|
||||
test_prepare(features);
|
||||
test_emit(features);
|
||||
test_emitted_txn_entry(features);
|
||||
test_etxn_burden(features);
|
||||
test_etxn_generation(features);
|
||||
test_otxn_burden(features);
|
||||
test_otxn_generation(features);
|
||||
test_etxn_details(features);
|
||||
test_etxn_fee_base(features - fixHookAPI20251128);
|
||||
test_etxn_fee_base(features - fix20261001);
|
||||
test_etxn_fee_base(features - fix20260929);
|
||||
test_etxn_fee_base(features);
|
||||
test_etxn_nonce(features);
|
||||
test_etxn_reserve(features);
|
||||
test_fee_base(features);
|
||||
|
||||
test_otxn_field(features);
|
||||
test_otxn_field_emit_failure(features);
|
||||
|
||||
test_ledger_keylet(features);
|
||||
|
||||
@@ -4790,6 +5227,7 @@ public:
|
||||
test_float_one(features);
|
||||
test_float_root(features);
|
||||
test_float_set(features);
|
||||
test_float_set(features - fix20261005);
|
||||
test_float_sign(features);
|
||||
test_float_sto(features);
|
||||
test_float_sto_set(features);
|
||||
@@ -4828,19 +5266,24 @@ public:
|
||||
|
||||
test_state(features);
|
||||
test_state_foreign(features);
|
||||
test_state_foreign_read_accounting(features);
|
||||
test_state_foreign_read_accounting(features - fix20261005);
|
||||
test_state_foreign_read_accounting(
|
||||
features - fix20261005 - featureExtendedHookState);
|
||||
test_state_foreign_set(features - fixHookMap);
|
||||
test_state_foreign_set(features);
|
||||
test_state_foreign_set_max(features);
|
||||
test_state_set(features);
|
||||
|
||||
test_sto_emplace(features);
|
||||
test_sto_emplace(features - fix20261001);
|
||||
test_sto_emplace(features - fix20260929);
|
||||
// test_sto_erase(features); // tested in test_sto_emplace
|
||||
test_sto_subarray(features);
|
||||
test_sto_subarray(features - fix20261001);
|
||||
test_sto_subarray(features - fix20260929);
|
||||
test_sto_subfield(features);
|
||||
test_sto_subfield(features - fix20261001);
|
||||
test_sto_subfield(features - fix20260929);
|
||||
test_sto_validate(features);
|
||||
test_sto_validate(features - fix20261005);
|
||||
|
||||
test_trace(features);
|
||||
test_trace_float(features);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <test/app/Import_json.h>
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/AMM.h>
|
||||
#include <test/jtx/CaptureLogs.h>
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/misc/AmendmentTable.h>
|
||||
#include <xrpld/app/misc/HashRouter.h>
|
||||
@@ -29,7 +30,9 @@
|
||||
#include <xrpl/json/json_reader.h>
|
||||
#include <xrpl/json/json_writer.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/Import.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#define BEAST_REQUIRE(x) \
|
||||
@@ -194,6 +197,35 @@ class Import_test : public beast::unit_test::suite
|
||||
env(import::import(alice, tmpXpop), ter(temMALFORMED));
|
||||
}
|
||||
|
||||
auto const upperHash =
|
||||
"E000CC0736630D66DAB573A2642E1BD0646DFB13A871B2CCFA6E4226F477D88C";
|
||||
auto const makeList = [](std::string const& hash) {
|
||||
Json::Value proof{Json::arrayValue};
|
||||
for (int i = 0; i < 16; ++i)
|
||||
proof.append(hash);
|
||||
return proof;
|
||||
};
|
||||
|
||||
// XPOP.transaction.proof list should be exactly 16 entries
|
||||
{
|
||||
Json::Value proof = makeList(upperHash);
|
||||
proof.append(upperHash);
|
||||
BEAST_EXPECT(syntaxCheckProof(proof, env.journal) == false);
|
||||
}
|
||||
// XPOP.transaction.proof list entry missing or wrong format
|
||||
// (should be hex string with 64 characters)
|
||||
{
|
||||
Json::Value proof = makeList(upperHash);
|
||||
proof[Json::Value::ArrayIndex{0}] = "wrong format";
|
||||
BEAST_EXPECT(syntaxCheckProof(proof, env.journal) == false);
|
||||
}
|
||||
// XPOP.transaction.proof list entry has wrong format
|
||||
{
|
||||
Json::Value proof = makeList(upperHash);
|
||||
proof[Json::Value::ArrayIndex{0}] = 1234;
|
||||
BEAST_EXPECT(syntaxCheckProof(proof, env.journal) == false);
|
||||
}
|
||||
|
||||
// // XPOP.transaction.proof list should be exactly 16 entries
|
||||
// {
|
||||
// Json::Value tmpXpop =
|
||||
@@ -334,6 +366,58 @@ class Import_test : public beast::unit_test::suite
|
||||
tmpProof[jss::children]["3"];
|
||||
BEAST_EXPECT(syntaxCheckProof(tmpProof, env.journal) == false);
|
||||
}
|
||||
// XPOP.transaction.proof tree node has wrong format
|
||||
// invalid hash (must be hex)
|
||||
{
|
||||
Json::Value tmpProof = proof;
|
||||
tmpProof[jss::children]["3"][jss::hash] =
|
||||
"ZZ126BA0486ADAE575BBC5335E42E236275A452037CA9A876D1A8CDACA1AE5"
|
||||
"42";
|
||||
BEAST_EXPECT(syntaxCheckProof(tmpProof, env.journal) == false);
|
||||
}
|
||||
// XPOP.transaction.proof tree node has wrong format
|
||||
// invalid key (must be hex)
|
||||
{
|
||||
Json::Value tmpProof = proof;
|
||||
tmpProof[jss::children]["3"][jss::key] =
|
||||
"ZZD6F6AB7D0A827DD1502B7B9571626A2B601DBE5BC786EF8ADD00E0CB7FCE"
|
||||
"B3";
|
||||
BEAST_EXPECT(syntaxCheckProof(tmpProof, env.journal) == false);
|
||||
}
|
||||
// XPOP.transaction.proof tree node has wrong format (root)
|
||||
// invalid root hash (must be hex)
|
||||
{
|
||||
Json::Value tmpProof = proof;
|
||||
tmpProof[jss::hash] =
|
||||
"ZZ9C8D073DDB5AB07FD2CD4F14467A8F3BC8FFBA16A0032D12D823D8511C12"
|
||||
"F4";
|
||||
BEAST_EXPECT(syntaxCheckProof(tmpProof, env.journal) == false);
|
||||
}
|
||||
// XPOP.transaction.proof child node was not 0-F hex nibble
|
||||
// lower case nibble
|
||||
{
|
||||
Json::Value tmpProof = proof;
|
||||
tmpProof[jss::children]["a"] = tmpProof[jss::children]["3"];
|
||||
BEAST_EXPECT(syntaxCheckProof(tmpProof, env.journal) == false);
|
||||
}
|
||||
// success: hash and key values may be upper or lower case hex. Only
|
||||
// child branch keys are constrained to upper case 0-F.
|
||||
{
|
||||
Json::Value tmpProof = proof;
|
||||
tmpProof[jss::hash] =
|
||||
"409c8d073ddb5ab07fd2cd4f14467a8f3bc8ffba16a0032d12d823d8511c12"
|
||||
"f4";
|
||||
tmpProof[jss::key] =
|
||||
"00000000000000000000000000000000000000000000000000000000000000"
|
||||
"00";
|
||||
tmpProof[jss::children]["3"][jss::hash] =
|
||||
"aa126ba0486adae575bbc5335e42e236275a452037ca9a876d1a8cdaca1ae5"
|
||||
"42";
|
||||
tmpProof[jss::children]["3"][jss::key] =
|
||||
"39d6f6ab7d0a827dd1502b7b9571626a2b601dbe5bc786ef8add00e0cb7fce"
|
||||
"b3";
|
||||
BEAST_EXPECT(syntaxCheckProof(tmpProof, env.journal) == true);
|
||||
}
|
||||
// success
|
||||
{
|
||||
BEAST_EXPECT(syntaxCheckProof(proof, env.journal) == true);
|
||||
@@ -1484,6 +1568,117 @@ class Import_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the XPOP with its blob/meta pair optionally tampered with
|
||||
// (tamperMeta bumps the metadata's TransactionIndex, so the pair no longer
|
||||
// hashes to any leaf in the proof) and, if plant is set, that pair's leaf
|
||||
// hash written over the declared hash of the proof's nibble 0 child. In
|
||||
// the w_seed fixture that child is an inner node, whose declared hash the
|
||||
// txroot computation ignores.
|
||||
static Json::Value
|
||||
spoofInnerNode(Json::Value xpop, bool tamperMeta, bool plant)
|
||||
{
|
||||
auto& txn = xpop[jss::transaction];
|
||||
auto const rawTx = *strUnHex(txn[jss::blob].asString());
|
||||
auto rawMeta = *strUnHex(txn[jss::meta].asString());
|
||||
|
||||
if (tamperMeta)
|
||||
{
|
||||
STObject meta(
|
||||
SerialIter(rawMeta.data(), rawMeta.size()), sfMetadata);
|
||||
meta.setFieldU32(
|
||||
sfTransactionIndex, meta.getFieldU32(sfTransactionIndex) + 1);
|
||||
Serializer ms;
|
||||
meta.add(ms);
|
||||
rawMeta = ms.peekData();
|
||||
txn[jss::meta] = strHex(rawMeta);
|
||||
}
|
||||
|
||||
if (plant)
|
||||
{
|
||||
// leaf hash exactly as Import::preflight computes it
|
||||
STTx const stx(SerialIter(rawTx.data(), rawTx.size()));
|
||||
Serializer s(rawTx.size() + rawMeta.size() + 40);
|
||||
s.addVL(rawTx);
|
||||
s.addVL(rawMeta);
|
||||
s.addBitString(stx.getTransactionID());
|
||||
txn[jss::proof][jss::children]["0"][jss::hash] =
|
||||
strHex(sha512Half(HashPrefix::txNode, s.slice()));
|
||||
}
|
||||
|
||||
return xpop;
|
||||
}
|
||||
|
||||
void
|
||||
testProofInnerNodeSpoof(FeatureBitset features)
|
||||
{
|
||||
testcase("import proof inner node spoof");
|
||||
|
||||
using namespace test::jtx;
|
||||
using namespace std::literals;
|
||||
|
||||
// the spoof relies on nibble 0 being an inner node in this fixture
|
||||
auto const fixture = import::loadXpop(ImportTCAccountSet::w_seed);
|
||||
auto const& nibble0 =
|
||||
fixture[jss::transaction][jss::proof][jss::children]["0"];
|
||||
BEAST_EXPECT(nibble0[jss::children].size() != 0);
|
||||
|
||||
std::string const notContained =
|
||||
"xpop proof did not contain the specified txn hash";
|
||||
|
||||
// import the (possibly spoofed) w_seed XPOP into a fresh account and
|
||||
// return the captured log
|
||||
auto const runImport =
|
||||
[&](bool tamperMeta, bool plant, TER expected) -> std::string {
|
||||
std::string logs;
|
||||
{
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
network::makeNetworkVLConfig(21337, keys),
|
||||
features,
|
||||
std::make_unique<CaptureLogs>(&logs),
|
||||
beast::severities::kWarning};
|
||||
|
||||
// burn 10'000 xrp
|
||||
auto const master = Account("masterpassphrase");
|
||||
env(noop(master), fee(10'000'000'000), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
auto const alice = Account("alice");
|
||||
env.memoize(alice);
|
||||
|
||||
Json::Value tx = import::import(
|
||||
alice, spoofInnerNode(fixture, tamperMeta, plant));
|
||||
tx[jss::Sequence] = 0;
|
||||
tx[jss::Fee] = 0;
|
||||
env(tx, alice, ter(expected));
|
||||
env.close();
|
||||
|
||||
auto const [acct, acctSle] =
|
||||
accountKeyAndSle(*env.current(), alice);
|
||||
BEAST_EXPECT((acctSle != nullptr) == isTesSuccess(expected));
|
||||
}
|
||||
return logs;
|
||||
};
|
||||
|
||||
// control: overwriting the inner node's declared hash alone leaves the
|
||||
// computed txroot unchanged, so the genuine pair still imports
|
||||
BEAST_EXPECT(
|
||||
runImport(false, true, tesSUCCESS).find(notContained) ==
|
||||
std::string::npos);
|
||||
|
||||
// a tampered blob/meta pair is not in the proof
|
||||
BEAST_EXPECT(
|
||||
runImport(true, false, temMALFORMED).find(notContained) !=
|
||||
std::string::npos);
|
||||
|
||||
// spoof: the tampered pair's hash planted on the inner node. The
|
||||
// txroot still verifies (see control), so the containment check is
|
||||
// the only thing that can reject it
|
||||
BEAST_EXPECT(
|
||||
runImport(true, true, temMALFORMED).find(notContained) !=
|
||||
std::string::npos);
|
||||
}
|
||||
|
||||
void
|
||||
testGetVLInfo(FeatureBitset features)
|
||||
{
|
||||
@@ -1700,6 +1895,12 @@ class Import_test : public beast::unit_test::suite
|
||||
env.fund(XRP(1000), alice, bob, carol, dave);
|
||||
env.close();
|
||||
|
||||
auto const importWithUnlBlob = [&](std::string const& json) {
|
||||
Json::Value tmpXpop = import::loadXpop(ImportTCAccountSet::w_seed);
|
||||
tmpXpop[jss::validation][jss::unl][jss::blob] = base64_encode(json);
|
||||
env(import::import(alice, tmpXpop), ter(temMALFORMED));
|
||||
};
|
||||
|
||||
// temMALFORMED
|
||||
// Issuer cannot be the source account.
|
||||
{
|
||||
@@ -1713,7 +1914,7 @@ class Import_test : public beast::unit_test::suite
|
||||
{
|
||||
Json::Value tx = import::import(
|
||||
alice, import::loadXpop(ImportTCAccountSet::w_seed));
|
||||
STAmount const& fee = XRP(0);
|
||||
STAmount const fee = XRP(0);
|
||||
tx[jss::Fee] = fee.getJson(JsonOptions::none);
|
||||
env(tx, ter(telINSUF_FEE_P));
|
||||
}
|
||||
@@ -1732,7 +1933,7 @@ class Import_test : public beast::unit_test::suite
|
||||
{
|
||||
Json::Value tx = import::import(
|
||||
alice, import::loadXpop(ImportTCAccountSet::w_seed));
|
||||
STAmount const& amount = XRP(-1);
|
||||
STAmount const amount = XRP(-1);
|
||||
tx[jss::Amount] = amount.getJson(JsonOptions::none);
|
||||
env(tx, ter(temMALFORMED));
|
||||
}
|
||||
@@ -2076,6 +2277,12 @@ class Import_test : public beast::unit_test::suite
|
||||
"WFo3VFFFPSJ9XX0=";
|
||||
env(import::import(alice, tmpXpop), ter(temMALFORMED));
|
||||
}
|
||||
// temMALFORMED - Import: unl blob json (after base64 decoding) wrong
|
||||
// required field (sequence) and/or types: negative int
|
||||
{
|
||||
importWithUnlBlob(
|
||||
R"({"sequence":-1,"expiration":741398400,"validators":[]})");
|
||||
}
|
||||
// temMALFORMED - Import: unl blob json (after base64 decoding)
|
||||
// lacked required field (expiration) and/or types
|
||||
{
|
||||
@@ -2127,6 +2334,12 @@ class Import_test : public beast::unit_test::suite
|
||||
"WFo3VFFFPSJ9XX0=";
|
||||
env(import::import(alice, tmpXpop), ter(temMALFORMED));
|
||||
}
|
||||
// temMALFORMED - Import: unl blob json (after base64 decoding) wrong
|
||||
// required field (expiration) and/or types: negative int
|
||||
{
|
||||
importWithUnlBlob(
|
||||
R"({"sequence":2,"expiration":-1,"validators":[]})");
|
||||
}
|
||||
// temMALFORMED - Import: unl blob json (after base64 decoding)
|
||||
// lacked required field (effective) and/or types
|
||||
{
|
||||
@@ -2153,6 +2366,13 @@ class Import_test : public beast::unit_test::suite
|
||||
"WGJuS1dqVmhxTmFHbTU3N0s2SjY5N1haN1RRRT0ifV19";
|
||||
env(import::import(alice, tmpXpop), ter(temMALFORMED));
|
||||
}
|
||||
// temMALFORMED - Import: unl blob json (after base64 decoding) wrong
|
||||
// required field (effective) and/or types: negative int
|
||||
{
|
||||
importWithUnlBlob(
|
||||
R"({"sequence":2,"effective":-1,"expiration":741398400,)"
|
||||
R"("validators":[]})");
|
||||
}
|
||||
// temMALFORMED - Import: unl blob json (after base64 decoding)
|
||||
// lacked required field (validators) and/or types
|
||||
{
|
||||
@@ -2201,7 +2421,10 @@ class Import_test : public beast::unit_test::suite
|
||||
env(import::import(alice, tmpXpop), ter(temMALFORMED));
|
||||
}
|
||||
|
||||
// temMALFORMED - Import: unl blob expired
|
||||
// temMALFORMED - unl blob expired. The window is now checked in
|
||||
// preclaim (see testVLWindowClock). This blob's validator list
|
||||
// does not match the xpop's validations, so preflight rejects it
|
||||
// first.
|
||||
{
|
||||
Json::Value tmpXpop = import::loadXpop(ImportTCAccountSet::w_seed);
|
||||
tmpXpop[jss::validation][jss::unl][jss::blob] =
|
||||
@@ -2231,7 +2454,10 @@ class Import_test : public beast::unit_test::suite
|
||||
env(import::import(alice, tmpXpop), ter(temMALFORMED));
|
||||
}
|
||||
|
||||
// temMALFORMED - Import: unl blob not yet valid
|
||||
// temMALFORMED - unl blob not yet valid. The window is now checked in
|
||||
// preclaim (see testVLWindowClock). This blob's validator list
|
||||
// does not match the xpop's validations, so preflight rejects it
|
||||
// first.
|
||||
{
|
||||
Json::Value tmpXpop = import::loadXpop(ImportTCAccountSet::w_seed);
|
||||
tmpXpop[jss::validation][jss::unl][jss::blob] =
|
||||
@@ -6257,6 +6483,162 @@ class Import_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testGetVLWindow(FeatureBitset features)
|
||||
{
|
||||
testcase("import utils - getVLWindow");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
beast::Journal const j{beast::Journal::getNullSink()};
|
||||
|
||||
auto const at = [](std::uint32_t s) {
|
||||
return NetClock::time_point{NetClock::duration{s}};
|
||||
};
|
||||
auto const withBlob = [](std::string const& json) {
|
||||
Json::Value xpop;
|
||||
xpop[jss::validation][jss::unl][jss::blob] = base64_encode(json);
|
||||
return xpop;
|
||||
};
|
||||
|
||||
// effective absent: valid from the epoch
|
||||
{
|
||||
auto const window =
|
||||
getVLWindow(import::loadXpop(ImportTCAccountSet::w_seed), j);
|
||||
BEAST_REQUIRE(window);
|
||||
BEAST_EXPECT(window->first == at(0));
|
||||
BEAST_EXPECT(window->second == at(757349624));
|
||||
}
|
||||
|
||||
// effective present
|
||||
{
|
||||
auto const window = getVLWindow(
|
||||
withBlob(R"({"sequence":1,"effective":3600,)"
|
||||
R"("expiration":86400,"validators":[]})"),
|
||||
j);
|
||||
BEAST_REQUIRE(window);
|
||||
BEAST_EXPECT(window->first == at(3600));
|
||||
BEAST_EXPECT(window->second == at(86400));
|
||||
}
|
||||
|
||||
// not json, not an object, or missing / ill-typed fields
|
||||
for (std::string const bad :
|
||||
{"badJson",
|
||||
"[1,2,3]",
|
||||
R"({"sequence":1})",
|
||||
R"({"expiration":"86400"})",
|
||||
R"({"expiration":-1})",
|
||||
R"({"effective":"0","expiration":86400})",
|
||||
R"({"effective":-1,"expiration":86400})"})
|
||||
BEAST_EXPECT(!getVLWindow(withBlob(bad), j));
|
||||
}
|
||||
|
||||
void
|
||||
testVLWindowClock(FeatureBitset features)
|
||||
{
|
||||
testcase("import vl window clock");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
auto const at = [](std::uint32_t s) {
|
||||
return NetClock::time_point{NetClock::duration{s}};
|
||||
};
|
||||
|
||||
// expiration of the w_seed unl blob
|
||||
std::uint32_t const expiry = 757349624;
|
||||
auto const before = at(expiry - 1000);
|
||||
auto const after = at(expiry + 1000);
|
||||
auto const closeParentAt = [&](Env& env,
|
||||
NetClock::time_point closeTime) {
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto const resolution = env.closed()->info().closeTimeResolution;
|
||||
env.close(closeTime - resolution + 1s);
|
||||
};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const importTx = [&alice]() {
|
||||
Json::Value tx = import::import(
|
||||
alice, import::loadXpop(ImportTCAccountSet::w_seed));
|
||||
tx[jss::Sequence] = 0;
|
||||
tx[jss::Fee] = 0;
|
||||
return tx;
|
||||
};
|
||||
// Keep this test focused on the VL clock, not max-supply accounting.
|
||||
// w_seed creates an account and, without ZeroB2M, credits its 1000 XRP
|
||||
// inner fee as valid legacy burn-to-mint.
|
||||
auto const burnMintHeadroom = [&](Env& env) {
|
||||
auto const initCoins = env.current()->info().drops;
|
||||
auto headroom = Import::computeStartingBonus(*env.current());
|
||||
if (!env.current()->rules().enabled(featureZeroB2M))
|
||||
headroom += XRP(1000).value().xrp();
|
||||
auto const master = Account("masterpassphrase");
|
||||
env(noop(master),
|
||||
fee(static_cast<std::uint64_t>(headroom.drops())),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.current()->info().drops == initCoins - headroom);
|
||||
};
|
||||
|
||||
// parent ledger inside the window, node clock past it
|
||||
{
|
||||
Env env{*this, network::makeNetworkVLConfig(21337, keys), features};
|
||||
burnMintHeadroom(env);
|
||||
env.memoize(alice);
|
||||
env.close(before);
|
||||
BEAST_EXPECT(env.current()->parentCloseTime() < at(expiry));
|
||||
|
||||
env.timeKeeper().set(after);
|
||||
env(importTx(), alice, ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(alice) != nullptr);
|
||||
}
|
||||
|
||||
// parent ledger past the window, node clock inside it
|
||||
{
|
||||
Env env{*this, network::makeNetworkVLConfig(21337, keys), features};
|
||||
burnMintHeadroom(env);
|
||||
env.memoize(alice);
|
||||
env.close(after);
|
||||
BEAST_EXPECT(env.current()->parentCloseTime() >= at(expiry));
|
||||
|
||||
env.timeKeeper().set(before);
|
||||
env(importTx(), alice, ter(temMALFORMED));
|
||||
BEAST_EXPECT(env.le(alice) == nullptr);
|
||||
}
|
||||
|
||||
// Env::close uses close-time resolution, so the requested expiration
|
||||
// time lands on the closest representable parent close before
|
||||
// expiration for this blob.
|
||||
{
|
||||
Env env{*this, network::makeNetworkVLConfig(21337, keys), features};
|
||||
burnMintHeadroom(env);
|
||||
env.memoize(alice);
|
||||
closeParentAt(env, at(expiry));
|
||||
BEAST_EXPECT(env.current()->parentCloseTime() < at(expiry));
|
||||
|
||||
env(importTx(), alice, ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(alice) != nullptr);
|
||||
}
|
||||
|
||||
// accepted into the open ledger, which then closes after the window.
|
||||
// Env::close() sets the node clock to the close time before the
|
||||
// ledger is built, so this also covers the rebuild
|
||||
{
|
||||
Env env{*this, network::makeNetworkVLConfig(21337, keys), features};
|
||||
burnMintHeadroom(env);
|
||||
env.memoize(alice);
|
||||
env.close(before);
|
||||
env(importTx(), alice, ter(tesSUCCESS));
|
||||
BEAST_EXPECT(env.le(alice) != nullptr);
|
||||
|
||||
env.close(after);
|
||||
BEAST_EXPECT(env.closed()->info().parentCloseTime < at(expiry));
|
||||
BEAST_EXPECT(env.le(alice) != nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -6278,6 +6660,7 @@ public:
|
||||
testSyntaxCheckProofArray(features);
|
||||
testSyntaxCheckProofObject(features);
|
||||
testSyntaxCheckXPOP(features);
|
||||
testProofInnerNodeSpoof(features);
|
||||
testGetVLInfo(features);
|
||||
testEnabled(features);
|
||||
testInvalidPreflight(features);
|
||||
@@ -6297,6 +6680,8 @@ public:
|
||||
testMinMax(features);
|
||||
testHalving(features - featureOwnerPaysFee);
|
||||
testBlackhole(features);
|
||||
testGetVLWindow(features);
|
||||
testVLWindowClock(features);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Loaded 100 of 344 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user