mirror of
https://github.com/Xahau/xahaud.git
synced 2026-10-22 19:57:09 +00:00
Compare commits
31
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 |
No files matched your search
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -50,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
|
||||
@@ -96,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
|
||||
@@ -123,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
|
||||
@@ -191,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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
+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;
|
||||
|
||||
@@ -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')
|
||||
|
||||
+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
|
||||
@@ -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.
|
||||
|
||||
@@ -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,10 +34,13 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
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::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)
|
||||
@@ -87,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)
|
||||
@@ -163,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)
|
||||
|
||||
@@ -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,6 +646,7 @@ 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},
|
||||
}))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -298,7 +298,7 @@ Door<Handler>::Door(
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -208,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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -29,7 +29,9 @@ bool
|
||||
is_private(AddressV6 const& addr)
|
||||
{
|
||||
return (
|
||||
(addr.to_bytes()[0] & 0xfd) || // TODO fc00::/8 too ?
|
||||
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))));
|
||||
@@ -38,8 +40,7 @@ is_private(AddressV6 const& 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
|
||||
|
||||
@@ -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,7 +21,10 @@
|
||||
#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>
|
||||
@@ -66,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:";
|
||||
@@ -311,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);
|
||||
|
||||
+466
-20
@@ -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)
|
||||
{
|
||||
@@ -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 = {
|
||||
@@ -2895,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)
|
||||
{
|
||||
@@ -3281,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
|
||||
@@ -3584,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)
|
||||
{
|
||||
@@ -4315,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");
|
||||
@@ -4390,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
|
||||
@@ -4759,6 +5197,7 @@ public:
|
||||
|
||||
test_prepare(features);
|
||||
test_emit(features);
|
||||
test_emitted_txn_entry(features);
|
||||
test_etxn_burden(features);
|
||||
test_etxn_generation(features);
|
||||
test_otxn_burden(features);
|
||||
@@ -4772,6 +5211,7 @@ public:
|
||||
test_fee_base(features);
|
||||
|
||||
test_otxn_field(features);
|
||||
test_otxn_field_emit_failure(features);
|
||||
|
||||
test_ledger_keylet(features);
|
||||
|
||||
@@ -4787,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);
|
||||
@@ -4825,6 +5266,10 @@ 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);
|
||||
@@ -4838,6 +5283,7 @@ public:
|
||||
test_sto_subfield(features);
|
||||
test_sto_subfield(features - fix20260929);
|
||||
test_sto_validate(features);
|
||||
test_sto_validate(features - fix20261005);
|
||||
|
||||
test_trace(features);
|
||||
test_trace_float(features);
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <xrpld/app/main/LoadManager.h>
|
||||
#include <xrpld/app/misc/LoadFeeTrack.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/**
|
||||
* Unit tests for LoadFeeTrack fee adjustment mechanics.
|
||||
*
|
||||
* These tests document the expected per-second behaviour of
|
||||
* raiseLocalFee() and lowerLocalFee() as called by LoadManager::run().
|
||||
*
|
||||
* Background: prior to the fix in this PR, the fee adjustment block in
|
||||
* LoadManager::run() was placed *outside* the while loop, meaning
|
||||
* raiseLocalFee()/lowerLocalFee() fired only once on shutdown rather
|
||||
* than every second during normal operation. localTxnLoadFee_ (and thus
|
||||
* load_factor_local in server_info) was therefore never adjusted by
|
||||
* local job queue load during normal operation.
|
||||
*/
|
||||
class LoadManager_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
// The normal/minimum fee factor (kLftNormalFee in LoadFeeTrack)
|
||||
static constexpr std::uint32_t kNormalFee = 256;
|
||||
|
||||
void
|
||||
testRaiseHysteresis()
|
||||
{
|
||||
testcase("raiseLocalFee requires two consecutive calls");
|
||||
|
||||
LoadFeeTrack track;
|
||||
|
||||
// First call: raiseCount_ goes from 0 to 1, returns false (hysteresis)
|
||||
BEAST_EXPECT(!track.raiseLocalFee());
|
||||
BEAST_EXPECT(track.getLocalFee() == kNormalFee);
|
||||
|
||||
// Second call: raiseCount_ reaches 2, fee is raised
|
||||
BEAST_EXPECT(track.raiseLocalFee());
|
||||
BEAST_EXPECT(track.getLocalFee() > kNormalFee);
|
||||
}
|
||||
|
||||
void
|
||||
testLowerDecaysToBaseline()
|
||||
{
|
||||
testcase("lowerLocalFee decays elevated fee back to baseline");
|
||||
|
||||
LoadFeeTrack track;
|
||||
|
||||
// Raise the fee above baseline (requires two calls due to hysteresis)
|
||||
track.raiseLocalFee();
|
||||
track.raiseLocalFee();
|
||||
auto const elevated = track.getLocalFee();
|
||||
BEAST_EXPECT(elevated > kNormalFee);
|
||||
|
||||
// lowerLocalFee() should decay it each call
|
||||
// Each call reduces by 1/kLftFeeDecFraction (1/4), so a few
|
||||
// iterations should bring it back to kNormalFee
|
||||
bool changed = false;
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
changed |= track.lowerLocalFee();
|
||||
if (track.getLocalFee() == kNormalFee)
|
||||
break;
|
||||
}
|
||||
|
||||
BEAST_EXPECT(changed);
|
||||
BEAST_EXPECT(track.getLocalFee() == kNormalFee);
|
||||
}
|
||||
|
||||
void
|
||||
testLowerAtBaselineIsNoop()
|
||||
{
|
||||
testcase("lowerLocalFee at baseline returns false");
|
||||
|
||||
LoadFeeTrack track;
|
||||
|
||||
// Already at baseline — should return false (no change)
|
||||
BEAST_EXPECT(!track.lowerLocalFee());
|
||||
BEAST_EXPECT(track.getLocalFee() == kNormalFee);
|
||||
}
|
||||
|
||||
void
|
||||
testRaiseResetsOnLower()
|
||||
{
|
||||
testcase("lowerLocalFee resets raiseCount");
|
||||
|
||||
LoadFeeTrack track;
|
||||
|
||||
// One raise call (hysteresis: count=1, no change yet)
|
||||
BEAST_EXPECT(!track.raiseLocalFee());
|
||||
|
||||
// Lower resets raiseCount_ to 0
|
||||
track.lowerLocalFee();
|
||||
|
||||
// Next raise call starts from 0 again — still needs two calls
|
||||
BEAST_EXPECT(!track.raiseLocalFee());
|
||||
BEAST_EXPECT(track.getLocalFee() == kNormalFee);
|
||||
}
|
||||
|
||||
void
|
||||
testIsLoadedLocal()
|
||||
{
|
||||
testcase("isLoadedLocal reflects fee state correctly");
|
||||
|
||||
LoadFeeTrack track;
|
||||
|
||||
// At baseline, not loaded
|
||||
BEAST_EXPECT(!track.isLoadedLocal());
|
||||
|
||||
// After one raise (hysteresis, fee unchanged) — raiseCount_ != 0
|
||||
track.raiseLocalFee();
|
||||
BEAST_EXPECT(track.isLoadedLocal());
|
||||
|
||||
// After lower, raiseCount_ reset and fee back at baseline
|
||||
track.lowerLocalFee();
|
||||
BEAST_EXPECT(!track.isLoadedLocal());
|
||||
}
|
||||
|
||||
void
|
||||
testLoadManagerLoop()
|
||||
{
|
||||
testcase("LoadManager loop raises and decays load_factor_local");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env(*this, envconfig());
|
||||
|
||||
// Stop the real LoadManager thread so we can drive the fee track
|
||||
// manually without races. On stock code the fee adjustment block
|
||||
// is outside the while loop and never fires during normal operation,
|
||||
// so getFeeTrack().getLocalFee() stays at kNormalFee (256) regardless.
|
||||
env.app().getLoadManager().stop();
|
||||
|
||||
auto& feeTrack = env.app().getFeeTrack();
|
||||
|
||||
// Confirm baseline
|
||||
BEAST_EXPECT(feeTrack.getLocalFee() == kNormalFee);
|
||||
BEAST_EXPECT(!feeTrack.isLoadedLocal());
|
||||
|
||||
// raiseLocalFee() requires two consecutive calls (hysteresis guard).
|
||||
// This is the exact sequence LoadManager::run() executes each tick
|
||||
// when the job queue is overloaded.
|
||||
BEAST_EXPECT(!feeTrack.raiseLocalFee()); // tick 1: count=1, no change
|
||||
BEAST_EXPECT(feeTrack.raiseLocalFee()); // tick 2: count=2, fee raised
|
||||
BEAST_EXPECT(feeTrack.getLocalFee() > kNormalFee);
|
||||
BEAST_EXPECT(feeTrack.isLoadedLocal());
|
||||
|
||||
// Now simulate the LoadManager loop calling lowerLocalFee() each tick
|
||||
// until the fee decays back to baseline. On stock code this block
|
||||
// never runs during normal operation so the fee would stay elevated.
|
||||
bool decayed = false;
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
feeTrack.lowerLocalFee();
|
||||
if (feeTrack.getLocalFee() == kNormalFee)
|
||||
{
|
||||
decayed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BEAST_EXPECT(decayed);
|
||||
BEAST_EXPECT(feeTrack.getLocalFee() == kNormalFee);
|
||||
BEAST_EXPECT(!feeTrack.isLoadedLocal());
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testRaiseHysteresis();
|
||||
testLowerDecaysToBaseline();
|
||||
testLowerAtBaselineIsNoop();
|
||||
testRaiseResetsOnLower();
|
||||
testIsLoadedLocal();
|
||||
testLoadManagerLoop();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(LoadManager, app, ripple);
|
||||
|
||||
} // namespace ripple
|
||||
File diff suppressed because it is too large.
Load diff
@@ -24,6 +24,7 @@
|
||||
#include <xrpld/app/rdb/Wallet.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/base64.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/protocol/STExchange.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
@@ -975,9 +976,482 @@ public:
|
||||
".example.com"));
|
||||
}
|
||||
|
||||
struct Validator
|
||||
{
|
||||
SecretKey masterSecret;
|
||||
PublicKey master;
|
||||
SecretKey signingSecret;
|
||||
PublicKey signing;
|
||||
};
|
||||
|
||||
static Validator
|
||||
makeValidator()
|
||||
{
|
||||
auto const ms = randomSecretKey();
|
||||
auto const ss = randomSecretKey();
|
||||
return {
|
||||
ms,
|
||||
derivePublicKey(KeyType::ed25519, ms),
|
||||
ss,
|
||||
derivePublicKey(KeyType::secp256k1, ss)};
|
||||
}
|
||||
|
||||
/** The same master key with a fresh ephemeral key. */
|
||||
static Validator
|
||||
rotate(Validator const& v)
|
||||
{
|
||||
auto const ss = randomSecretKey();
|
||||
return {
|
||||
v.masterSecret,
|
||||
v.master,
|
||||
ss,
|
||||
derivePublicKey(KeyType::secp256k1, ss)};
|
||||
}
|
||||
|
||||
/** Another master key claiming the same ephemeral key. */
|
||||
static Validator
|
||||
impostor(Validator const& v)
|
||||
{
|
||||
auto const ms = randomSecretKey();
|
||||
return {
|
||||
ms,
|
||||
derivePublicKey(KeyType::ed25519, ms),
|
||||
v.signingSecret,
|
||||
v.signing};
|
||||
}
|
||||
|
||||
Manifest
|
||||
manifestFor(Validator const& v, int seq, bool invalidSig = false)
|
||||
{
|
||||
return makeManifest(
|
||||
v.masterSecret,
|
||||
KeyType::ed25519,
|
||||
v.signingSecret,
|
||||
KeyType::secp256k1,
|
||||
seq,
|
||||
invalidSig);
|
||||
}
|
||||
|
||||
static std::size_t
|
||||
offered(ManifestCache const& cache)
|
||||
{
|
||||
std::size_t n = 0;
|
||||
cache.for_each_gossip_manifest(
|
||||
[](std::size_t) {}, [&n](Manifest const&) { ++n; });
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
testGossipAdmission()
|
||||
{
|
||||
testcase("gossip admission");
|
||||
|
||||
using D = ManifestDisposition;
|
||||
auto const gossip = ManifestSource::gossip;
|
||||
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
|
||||
// Gossip for a master key not held, before its ephemeral key has
|
||||
// signed anything: held back, not admitted.
|
||||
auto const a = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(a, 1), gossip) == D::unseen);
|
||||
BEAST_EXPECT(cache.getMasterKey(a.signing) == a.signing);
|
||||
BEAST_EXPECT(!cache.getTier(a.master));
|
||||
|
||||
// Its first validation releases it, once.
|
||||
auto const seq = cache.sequence();
|
||||
auto const released = cache.noteValidation(a.signing);
|
||||
BEAST_EXPECT(released && released->masterKey == a.master);
|
||||
BEAST_EXPECT(cache.getMasterKey(a.signing) == a.master);
|
||||
BEAST_EXPECT(cache.getTier(a.master) == gossip);
|
||||
BEAST_EXPECT(cache.sequence() > seq);
|
||||
BEAST_EXPECT(!cache.noteValidation(a.signing));
|
||||
|
||||
// Seen first, then gossiped: admitted at once.
|
||||
auto const b = makeValidator();
|
||||
BEAST_EXPECT(!cache.noteValidation(b.signing));
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(b, 1), gossip) == D::accepted);
|
||||
|
||||
// A master key held from gossip rotates without its new key having
|
||||
// been seen...
|
||||
auto const b2 = rotate(b);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(b2, 2), gossip) == D::accepted);
|
||||
BEAST_EXPECT(cache.getMasterKey(b2.signing) == b.master);
|
||||
BEAST_EXPECT(cache.getMasterKey(b.signing) == b.signing);
|
||||
|
||||
// ...and is revoked from gossip too, since that only takes trust away.
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(
|
||||
makeRevocation(b.masterSecret, KeyType::ed25519), gossip) ==
|
||||
D::accepted);
|
||||
BEAST_EXPECT(cache.revoked(b.master));
|
||||
BEAST_EXPECT(cache.getMasterKey(b2.signing) == b2.signing);
|
||||
BEAST_EXPECT(cache.getTier(b.master) == gossip);
|
||||
|
||||
// A revocation for a master key not held names no ephemeral key that
|
||||
// could have been seen, so gossip cannot introduce one...
|
||||
auto const c = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(
|
||||
makeRevocation(c.masterSecret, KeyType::ed25519), gossip) ==
|
||||
D::unseen);
|
||||
BEAST_EXPECT(!cache.getTier(c.master));
|
||||
|
||||
// ...but a list can.
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(makeRevocation(
|
||||
c.masterSecret, KeyType::ed25519)) == D::accepted);
|
||||
BEAST_EXPECT(cache.getTier(c.master) == ManifestSource::list);
|
||||
BEAST_EXPECT(cache.revoked(c.master));
|
||||
|
||||
// A forgery waits unverified, and is dropped when released.
|
||||
auto const d = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(d, 1, true), gossip) == D::unseen);
|
||||
BEAST_EXPECT(!cache.noteValidation(d.signing));
|
||||
BEAST_EXPECT(!cache.getTier(d.master));
|
||||
|
||||
// A key is remembered for three to four epochs of the filter.
|
||||
auto const e = makeValidator();
|
||||
cache.noteValidation(e.signing);
|
||||
clock.advance(ManifestCache::seenInterval * 3);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(e, 1), gossip) == D::accepted);
|
||||
|
||||
auto const f = makeValidator();
|
||||
cache.noteValidation(f.signing);
|
||||
clock.advance(ManifestCache::seenInterval * 4);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(f, 1), gossip) == D::unseen);
|
||||
}
|
||||
|
||||
void
|
||||
testPendingSubstitution()
|
||||
{
|
||||
testcase("pending ring: forged replacement");
|
||||
|
||||
using D = ManifestDisposition;
|
||||
auto const gossip = ManifestSource::gossip;
|
||||
|
||||
// Same master and signing key, higher sequence, bad master signature.
|
||||
{
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
auto const v = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 1), gossip) == D::unseen);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 2, true), gossip) ==
|
||||
D::unseen);
|
||||
auto const released = cache.noteValidation(v.signing);
|
||||
BEAST_EXPECT(released && released->sequence == 1);
|
||||
BEAST_EXPECT(cache.getMasterKey(v.signing) == v.master);
|
||||
}
|
||||
|
||||
// Different master claiming the same signing key, bad master signature.
|
||||
{
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
auto const v = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 1), gossip) == D::unseen);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(
|
||||
manifestFor(impostor(v), 2, true), gossip) == D::unseen);
|
||||
auto const released = cache.noteValidation(v.signing);
|
||||
BEAST_EXPECT(released && released->masterKey == v.master);
|
||||
BEAST_EXPECT(cache.getMasterKey(v.signing) == v.master);
|
||||
}
|
||||
|
||||
// A forgery that arrives first, for the same master key with a
|
||||
// higher sequence, does not keep out the genuine manifest.
|
||||
{
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
auto const v = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 2, true), gossip) ==
|
||||
D::unseen);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 1), gossip) == D::unseen);
|
||||
auto const released = cache.noteValidation(v.signing);
|
||||
BEAST_EXPECT(released && released->sequence == 1);
|
||||
BEAST_EXPECT(cache.getMasterKey(v.signing) == v.master);
|
||||
}
|
||||
|
||||
// Nor does one that arrives first for another master key.
|
||||
{
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
auto const v = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(
|
||||
manifestFor(impostor(v), 2, true), gossip) == D::unseen);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 1), gossip) == D::unseen);
|
||||
auto const released = cache.noteValidation(v.signing);
|
||||
BEAST_EXPECT(released && released->masterKey == v.master);
|
||||
BEAST_EXPECT(cache.getMasterKey(v.signing) == v.master);
|
||||
}
|
||||
|
||||
// A genuine manifest waiting is not displaced by an older genuine one.
|
||||
{
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
auto const v = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 2), gossip) == D::unseen);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 1), gossip) == D::unseen);
|
||||
auto const released = cache.noteValidation(v.signing);
|
||||
BEAST_EXPECT(released && released->sequence == 2);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testGossipCapacity()
|
||||
{
|
||||
testcase("gossip capacity and decay");
|
||||
|
||||
using D = ManifestDisposition;
|
||||
auto const gossip = ManifestSource::gossip;
|
||||
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
|
||||
std::vector<Validator> held;
|
||||
for (std::size_t i = 0; i < ManifestCache::gossipCapacity; ++i)
|
||||
{
|
||||
held.push_back(makeValidator());
|
||||
cache.noteValidation(held.back().signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(held.back(), 1), gossip) ==
|
||||
D::accepted);
|
||||
}
|
||||
BEAST_EXPECT(offered(cache) == ManifestCache::gossipCapacity);
|
||||
|
||||
// Every entry is exactly as active as a newcomer: none gives way.
|
||||
auto const newcomer = makeValidator();
|
||||
cache.noteValidation(newcomer.signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(newcomer, 1), gossip) == D::full);
|
||||
|
||||
// Four half-lives on, all but the first have gone on validating. The
|
||||
// first has decayed to a sixteenth of a sighting, the rest stand at
|
||||
// seventeen sixteenths, and a newcomer is worth a whole one: it
|
||||
// displaces the first and nothing else.
|
||||
clock.advance(ManifestCache::halfLife * 4);
|
||||
for (std::size_t i = 1; i < held.size(); ++i)
|
||||
cache.noteValidation(held[i].signing);
|
||||
|
||||
auto const seq = cache.sequence();
|
||||
cache.noteValidation(newcomer.signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(newcomer, 1), gossip) ==
|
||||
D::accepted);
|
||||
BEAST_EXPECT(cache.sequence() > seq);
|
||||
BEAST_EXPECT(!cache.getTier(held[0].master));
|
||||
BEAST_EXPECT(cache.getMasterKey(held[0].signing) == held[0].signing);
|
||||
for (std::size_t i = 1; i < held.size(); ++i)
|
||||
BEAST_EXPECT(cache.getTier(held[i].master) == gossip);
|
||||
BEAST_EXPECT(offered(cache) == ManifestCache::gossipCapacity);
|
||||
|
||||
// The newcomer is now the least active, but no less active than
|
||||
// another newcomer, which therefore finds no room.
|
||||
auto const late = makeValidator();
|
||||
cache.noteValidation(late.signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(late, 1), gossip) == D::full);
|
||||
|
||||
// Far enough on that the weights are rebased. Their order survives:
|
||||
// the one entry that stopped validating is the one displaced.
|
||||
clock.advance(ManifestCache::halfLife * 300);
|
||||
for (std::size_t i = 2; i < held.size(); ++i)
|
||||
cache.noteValidation(held[i].signing);
|
||||
cache.noteValidation(newcomer.signing);
|
||||
cache.noteValidation(late.signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(late, 1), gossip) == D::accepted);
|
||||
BEAST_EXPECT(!cache.getTier(held[1].master));
|
||||
BEAST_EXPECT(cache.getTier(newcomer.master) == gossip);
|
||||
for (std::size_t i = 2; i < held.size(); ++i)
|
||||
BEAST_EXPECT(cache.getTier(held[i].master) == gossip);
|
||||
}
|
||||
|
||||
void
|
||||
testPinning()
|
||||
{
|
||||
testcase("pinning");
|
||||
|
||||
using D = ManifestDisposition;
|
||||
auto const gossip = ManifestSource::gossip;
|
||||
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
|
||||
std::vector<Validator> held;
|
||||
for (std::size_t i = 0; i < ManifestCache::gossipCapacity; ++i)
|
||||
{
|
||||
held.push_back(makeValidator());
|
||||
cache.noteValidation(held.back().signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(held.back(), 1), gossip) ==
|
||||
D::accepted);
|
||||
}
|
||||
|
||||
// Pinned, a gossip entry is retained as list and frees its slot.
|
||||
auto const seq = cache.sequence();
|
||||
cache.pin({held[0].master});
|
||||
BEAST_EXPECT(cache.sequence() > seq);
|
||||
BEAST_EXPECT(cache.getTier(held[0].master) == ManifestSource::list);
|
||||
|
||||
auto const newcomer = makeValidator();
|
||||
cache.noteValidation(newcomer.signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(newcomer, 1), gossip) ==
|
||||
D::accepted);
|
||||
for (auto const& v : held)
|
||||
BEAST_EXPECT(cache.getTier(v.master));
|
||||
|
||||
// Unpinned, it falls back into a full tier, which sheds its least
|
||||
// active entry: one of those that stopped validating.
|
||||
clock.advance(ManifestCache::halfLife);
|
||||
cache.noteValidation(held[0].signing);
|
||||
cache.noteValidation(newcomer.signing);
|
||||
cache.pin({});
|
||||
BEAST_EXPECT(cache.getTier(held[0].master) == gossip);
|
||||
BEAST_EXPECT(cache.getTier(newcomer.master) == gossip);
|
||||
std::size_t remaining = 0;
|
||||
for (std::size_t i = 1; i < held.size(); ++i)
|
||||
remaining += cache.getTier(held[i].master) ? 1 : 0;
|
||||
BEAST_EXPECT(remaining == held.size() - 2);
|
||||
BEAST_EXPECT(offered(cache) == ManifestCache::gossipCapacity);
|
||||
|
||||
// A master key a list vouched for stays in the list tier however it
|
||||
// is updated later, and is offered alongside gossip.
|
||||
auto const listed = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(listed, 1)) == D::accepted);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(rotate(listed), 2), gossip) ==
|
||||
D::accepted);
|
||||
BEAST_EXPECT(cache.getTier(listed.master) == ManifestSource::list);
|
||||
BEAST_EXPECT(offered(cache) == ManifestCache::gossipCapacity + 1);
|
||||
}
|
||||
|
||||
void
|
||||
testPrecedence()
|
||||
{
|
||||
testcase("tier precedence on key conflicts");
|
||||
|
||||
using D = ManifestDisposition;
|
||||
auto const gossip = ManifestSource::gossip;
|
||||
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
|
||||
// Gossip binds an ephemeral key to a master key...
|
||||
auto const a = makeValidator();
|
||||
cache.noteValidation(a.signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(a, 1), gossip) == D::accepted);
|
||||
|
||||
// ...and a list binding the same key to another master key wins,
|
||||
// evicting it, where before it would have been refused.
|
||||
auto const b = impostor(a);
|
||||
BEAST_EXPECT(cache.applyManifest(manifestFor(b, 1)) == D::accepted);
|
||||
BEAST_EXPECT(cache.getMasterKey(a.signing) == b.master);
|
||||
BEAST_EXPECT(!cache.getTier(a.master));
|
||||
|
||||
// Gossip cannot do the same to the list...
|
||||
auto const c = impostor(a);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(c, 1), gossip) ==
|
||||
D::badEphemeralKey);
|
||||
BEAST_EXPECT(cache.getMasterKey(a.signing) == b.master);
|
||||
|
||||
// ...and between equals the first holds, as it always has.
|
||||
auto const d = impostor(a);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(d, 1)) == D::badEphemeralKey);
|
||||
BEAST_EXPECT(cache.getMasterKey(a.signing) == b.master);
|
||||
}
|
||||
|
||||
void
|
||||
testPending()
|
||||
{
|
||||
testcase("pending");
|
||||
|
||||
using D = ManifestDisposition;
|
||||
auto const gossip = ManifestSource::gossip;
|
||||
|
||||
{
|
||||
// A newer manifest for a key already waiting replaces it.
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
|
||||
auto const v = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 2), gossip) == D::unseen);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 1), gossip) == D::unseen);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 3), gossip) == D::unseen);
|
||||
auto const released = cache.noteValidation(v.signing);
|
||||
BEAST_EXPECT(released && released->sequence == 3);
|
||||
BEAST_EXPECT(cache.getSequence(v.master) == 3);
|
||||
}
|
||||
|
||||
{
|
||||
// The ring holds pendingCapacity; the first to arrive makes way
|
||||
// for the one after.
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
|
||||
std::vector<Validator> waiting;
|
||||
for (std::size_t i = 0; i <= ManifestCache::pendingCapacity; ++i)
|
||||
{
|
||||
waiting.push_back(makeValidator());
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(
|
||||
manifestFor(waiting.back(), 1), gossip) == D::unseen);
|
||||
}
|
||||
|
||||
BEAST_EXPECT(!cache.noteValidation(waiting.front().signing));
|
||||
BEAST_EXPECT(!cache.getTier(waiting.front().master));
|
||||
|
||||
std::size_t released = 0;
|
||||
for (std::size_t i = 1; i < waiting.size(); ++i)
|
||||
released += cache.noteValidation(waiting[i].signing) ? 1 : 0;
|
||||
BEAST_EXPECT(released == ManifestCache::pendingCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testGossipAdmission();
|
||||
testGossipCapacity();
|
||||
testPendingSubstitution();
|
||||
testPinning();
|
||||
testPrecedence();
|
||||
testPending();
|
||||
|
||||
ManifestCache cache;
|
||||
{
|
||||
testcase("apply");
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <test/jtx.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/protocol/AppLoader.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class PWALoader_test : public beast::unit_test::suite
|
||||
{
|
||||
static appLoader::Result
|
||||
check(std::string const& s)
|
||||
{
|
||||
return appLoader::validate(
|
||||
reinterpret_cast<std::uint8_t const*>(s.data()), s.size());
|
||||
}
|
||||
|
||||
static std::string
|
||||
docOfSize(std::size_t len)
|
||||
{
|
||||
std::string const head = "<!DOCTYPE html><html><body>";
|
||||
std::string const tail = "</body></html>";
|
||||
if (len < head.size() + tail.size())
|
||||
return head + tail;
|
||||
return head + std::string(len - head.size() - tail.size(), 'x') + tail;
|
||||
}
|
||||
|
||||
static std::string
|
||||
doc()
|
||||
{
|
||||
return "<!DOCTYPE html><html><body>hi</body></html>";
|
||||
}
|
||||
|
||||
// AccountSet carrying sfAppLoader.
|
||||
static Json::Value
|
||||
set(jtx::Account const& a, std::string const& d)
|
||||
{
|
||||
auto jt = jtx::noop(a);
|
||||
jt[sfAppLoader.fieldName] = strHex(d);
|
||||
return jt;
|
||||
}
|
||||
|
||||
// validate() is consensus critical: these pin its edges, so a refactor
|
||||
// that moves any of them fails here rather than forking the network.
|
||||
void
|
||||
testValidator()
|
||||
{
|
||||
testcase("validator");
|
||||
using R = appLoader::Result;
|
||||
using namespace std::string_literals;
|
||||
|
||||
for (auto const& s :
|
||||
{"<!DOCTYPE html><html></html>"s,
|
||||
"<!doctype html><html></html>"s,
|
||||
"<html></html>"s,
|
||||
"<HTML></HTML>"s,
|
||||
"<html lang=\"en\" class=\"root\"></html>"s,
|
||||
" \n\t<html></html>\n\n "s,
|
||||
"<html></html >"s,
|
||||
"<html></html\n>"s,
|
||||
"\xEF\xBB\xBF<html></html>"s,
|
||||
"\xEF\xBB\xBF<!DOCTYPE html><html></html>"s,
|
||||
"<html><body>\xE6\xBC\xA2\xE5\xAD\x97</body></html>"s,
|
||||
"<html><script>var s=\"</html>\";</script></html>"s,
|
||||
"<html>\t\n\r\f</html>"s,
|
||||
"<html></html> \n"s,
|
||||
"<!DOCTYPE html>\n<!-- c --><html></html>"s,
|
||||
"<html>\xC2\x85</html>"s, // C1 controls are not rejected
|
||||
docOfSize(maxAppLoaderLength)})
|
||||
BEAST_EXPECTS(check(s) == R::ok, s);
|
||||
|
||||
std::vector<std::pair<std::string, R>> const bad{
|
||||
{""s, R::empty},
|
||||
{" \n"s, R::empty},
|
||||
{"\xEF\xBB\xBF"s, R::empty},
|
||||
{docOfSize(maxAppLoaderLength + 1), R::tooLarge},
|
||||
{"<html>\xC0\xAF</html>"s, R::badUTF8},
|
||||
{"<html>\xED\xA0\x80</html>"s, R::badUTF8},
|
||||
{"<html>\xEF\xBF\xBE</html>"s, R::badUTF8},
|
||||
{"<html>\xE6\xBC</html>"s, R::badUTF8},
|
||||
{"<html></html>\xE6"s, R::badUTF8},
|
||||
{"<html>\0</html>"s, R::badControlChar},
|
||||
{"<html>\x1B</html>"s, R::badControlChar},
|
||||
{"<html>\x7F</html>"s, R::badControlChar},
|
||||
{"hello"s, R::noDoctype},
|
||||
{"<div>frag</div>"s, R::noDoctype},
|
||||
{"<htmlish></html>"s, R::noDoctype},
|
||||
{"<!DOCTYPEhtml><html></html>"s, R::noDoctype},
|
||||
{"<!DOCTYPE"s, R::noDoctype},
|
||||
{"<!DOCTYPE html>"s, R::noHtmlElement},
|
||||
{"<!DOCTYPE html><htmlish></html>"s, R::noHtmlElement},
|
||||
{"<html>"s, R::unclosed},
|
||||
{"<html></html"s, R::unclosed},
|
||||
{"<html></htmlx>"s, R::unclosed},
|
||||
{"<html></html>trailing"s, R::trailingGarbage},
|
||||
{"<html></html><p>"s, R::trailingGarbage},
|
||||
{"<html></html>\xC2\xA0"s, R::trailingGarbage}, // NBSP
|
||||
};
|
||||
for (auto const& [s, r] : bad)
|
||||
BEAST_EXPECTS(
|
||||
check(s) == r, s + ": " + appLoader::to_string(check(s)));
|
||||
}
|
||||
|
||||
void
|
||||
testEnabled()
|
||||
{
|
||||
testcase("amendment gate");
|
||||
using namespace jtx;
|
||||
Env env{*this, supported_amendments() - featurePWALoader};
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
env(set(alice, doc()), fee(XRP(1)), ter(temDISABLED));
|
||||
env(set(alice, ""), fee(XRP(1)), ter(temDISABLED));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(keylet::appLoader(alice.id())));
|
||||
BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfAppLoaderSequence));
|
||||
}
|
||||
|
||||
void
|
||||
testSetAndRemove()
|
||||
{
|
||||
testcase("set, overwrite, remove, sequence");
|
||||
using namespace jtx;
|
||||
Env env{*this};
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
auto const kl = keylet::appLoader(alice.id());
|
||||
auto seq = [&] {
|
||||
return (*env.le(alice))[~sfAppLoaderSequence].value_or(0);
|
||||
};
|
||||
|
||||
BEAST_EXPECT(!env.le(kl));
|
||||
BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfAppLoaderSequence));
|
||||
|
||||
// Create: the object, with the owner's new sequence; nothing inline
|
||||
// on the AccountRoot, no reserve, not in the owner directory.
|
||||
env(set(alice, doc()), fee(XRP(1)));
|
||||
env.close();
|
||||
auto sle = env.le(kl);
|
||||
if (BEAST_EXPECT(sle))
|
||||
{
|
||||
BEAST_EXPECT((*sle)[sfAppLoader] == makeSlice(doc()));
|
||||
BEAST_EXPECT((*sle)[sfOwner] == alice.id());
|
||||
BEAST_EXPECT((*sle)[sfAppLoaderSequence] == 1);
|
||||
}
|
||||
BEAST_EXPECT(seq() == 1);
|
||||
BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfAppLoader));
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
|
||||
BEAST_EXPECT(
|
||||
env.rpc(
|
||||
"json",
|
||||
"account_objects",
|
||||
R"({"account":")" + alice.human() +
|
||||
R"("})")[jss::result][jss::account_objects]
|
||||
.size() == 0);
|
||||
auto const txid = (*sle)[sfPreviousTxnID];
|
||||
|
||||
// Overwrite: new content, next sequence, rethreaded.
|
||||
env(set(alice, "<html><body>v2</body></html>"), fee(XRP(1)));
|
||||
env.close();
|
||||
sle = env.le(kl);
|
||||
if (BEAST_EXPECT(sle))
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
(*sle)[sfAppLoader] ==
|
||||
makeSlice(std::string("<html><body>v2</body></html>")));
|
||||
BEAST_EXPECT((*sle)[sfAppLoaderSequence] == 2);
|
||||
BEAST_EXPECT((*sle)[sfPreviousTxnID] != txid);
|
||||
}
|
||||
BEAST_EXPECT(seq() == 2);
|
||||
|
||||
// An AccountSet without the field leaves everything alone.
|
||||
env(noop(alice));
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(kl))[sfAppLoaderSequence] == 2 && seq() == 2);
|
||||
|
||||
// Remove with an empty blob: the object goes, the sequence still
|
||||
// advances (so a copy of sequence 2 is known to be stale), and
|
||||
// removing again is a no-op that advances it again.
|
||||
env(set(alice, ""), fee(XRP(1)));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(kl) && seq() == 3);
|
||||
env(set(alice, ""), fee(XRP(1)));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(kl) && seq() == 4);
|
||||
|
||||
// Republish: the sequence carries on.
|
||||
env(set(alice, doc()), fee(XRP(1)));
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(kl))[sfAppLoaderSequence] == 5 && seq() == 5);
|
||||
|
||||
// ledger_entry reaches it by account or by index.
|
||||
for (auto const& req :
|
||||
{R"({"app_loader":")" + alice.human() + R"("})",
|
||||
R"({"index":")" + to_string(kl.key) + R"("})"})
|
||||
{
|
||||
auto const r = env.rpc("json", "ledger_entry", req)[jss::result];
|
||||
BEAST_EXPECTS(
|
||||
r[jss::node][sfLedgerEntryType.fieldName] == jss::AppLoader &&
|
||||
r[jss::node][sfAppLoaderSequence.fieldName] == 5,
|
||||
req);
|
||||
}
|
||||
BEAST_EXPECT(
|
||||
env.rpc(
|
||||
"json",
|
||||
"ledger_entry",
|
||||
R"({"app_loader":"nope"})")[jss::result][jss::error] ==
|
||||
"malformedAddress");
|
||||
}
|
||||
|
||||
void
|
||||
testFee()
|
||||
{
|
||||
testcase("fee: base plus one drop per byte");
|
||||
using namespace jtx;
|
||||
Env env{*this};
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
auto const base = env.current()->fees().base;
|
||||
auto const kl = keylet::appLoader(alice.id());
|
||||
|
||||
auto const expected = base + XRPAmount{1024};
|
||||
env(set(alice, docOfSize(1024)),
|
||||
fee(expected - XRPAmount{1}),
|
||||
ter(telINSUF_FEE_P));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(kl));
|
||||
auto const before = env.balance(alice);
|
||||
env(set(alice, docOfSize(1024)), fee(expected));
|
||||
env.close();
|
||||
BEAST_EXPECT(
|
||||
env.le(kl) && before - env.balance(alice) == drops(expected));
|
||||
|
||||
// Overwrites pay again; removal is base fee only.
|
||||
env(set(alice, docOfSize(100)),
|
||||
fee(base + XRPAmount{99}),
|
||||
ter(telINSUF_FEE_P));
|
||||
env(set(alice, docOfSize(100)), fee(base + XRPAmount{100}));
|
||||
env(set(alice, ""), fee(base));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(kl));
|
||||
|
||||
// Publishing takes no reserve: an account at its reserve floor can.
|
||||
Account const poor{"poor"};
|
||||
auto const reserve = env.current()->fees().accountReserve(0);
|
||||
env.fund(drops(reserve + base + XRPAmount{100}), noripple(poor));
|
||||
env.close();
|
||||
env(set(poor, docOfSize(100)), fee(base + XRPAmount{100}));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(keylet::appLoader(poor.id())));
|
||||
BEAST_EXPECT(env.balance(poor) == drops(reserve));
|
||||
env.require(owners(poor, 0));
|
||||
}
|
||||
|
||||
void
|
||||
testMalformed()
|
||||
{
|
||||
testcase("malformed documents are temMALFORMED");
|
||||
using namespace jtx;
|
||||
Env env{*this};
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
for (auto const& d :
|
||||
{std::string("hello"),
|
||||
std::string("<html>"),
|
||||
std::string("<html></html>x"),
|
||||
std::string("<html>\0</html>", 14),
|
||||
std::string("<html>\xC0\xAF</html>"),
|
||||
docOfSize(maxAppLoaderLength + 1)})
|
||||
{
|
||||
env(set(alice, d), fee(XRP(1)), ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
BEAST_EXPECT(!env.le(keylet::appLoader(alice.id())));
|
||||
BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfAppLoaderSequence));
|
||||
}
|
||||
|
||||
void
|
||||
testAccountDelete()
|
||||
{
|
||||
testcase("account delete erases the loader");
|
||||
using namespace jtx;
|
||||
Env env{*this};
|
||||
Account const alice{"alice"}, bob{"bob"};
|
||||
env.fund(XRP(1000), alice, bob);
|
||||
env.close();
|
||||
env(set(alice, doc()), fee(XRP(1)));
|
||||
env.close();
|
||||
for (int i = 0; i < 256; ++i)
|
||||
env.close();
|
||||
// Not in the owner directory, so it cannot block deletion; erased
|
||||
// explicitly, or AccountRootsDeletedClean would fire on the orphan.
|
||||
env(acctdelete(alice, bob),
|
||||
fee(drops(env.current()->fees().increment)));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(alice));
|
||||
BEAST_EXPECT(!env.le(keylet::appLoader(alice.id())));
|
||||
}
|
||||
|
||||
void
|
||||
testValidateRPC()
|
||||
{
|
||||
testcase("pwa_validate");
|
||||
using namespace jtx;
|
||||
Env env{*this};
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
auto validate = [&](std::string const& d, std::string const& domain) {
|
||||
Json::Value p(Json::objectValue);
|
||||
p[jss::account] = alice.human();
|
||||
p[jss::hash] = to_string(sha512Half(makeSlice(d)));
|
||||
if (!domain.empty())
|
||||
p[jss::domain] = domain;
|
||||
return env.rpc("json", "pwa_validate", to_string(p))[jss::result];
|
||||
};
|
||||
|
||||
BEAST_EXPECT(validate(doc(), "")["reason"] == "no loader");
|
||||
env(set(alice, doc()), fee(XRP(1)));
|
||||
env.close();
|
||||
auto r = validate(doc(), "");
|
||||
BEAST_EXPECT(r["valid"] == true && r["app_loader_sequence"] == 1);
|
||||
BEAST_EXPECT(r[jss::ledger_index] == env.closed()->seq());
|
||||
BEAST_EXPECT(
|
||||
validate("<html></html>", "")["reason"] == "hash mismatch");
|
||||
|
||||
// domain: the account's Domain, or a host beneath it, ignoring case.
|
||||
BEAST_EXPECT(
|
||||
validate(doc(), "pwa.test")["reason"] ==
|
||||
"domain not endorsed by account");
|
||||
auto jt = noop(alice);
|
||||
jt[sfDomain.fieldName] = strHex(std::string("Pwa.Test"));
|
||||
env(jt);
|
||||
env.close();
|
||||
for (auto const d :
|
||||
{"pwa.test", "PWA.TEST.", "pwaabcd.pwa.test", "a.b.pwa.test"})
|
||||
BEAST_EXPECTS(validate(doc(), d)["valid"] == true, d);
|
||||
for (auto const d : {"other.test", "xpwa.test", "pwa.test.evil"})
|
||||
BEAST_EXPECTS(validate(doc(), d)["valid"] == false, d);
|
||||
|
||||
// Malformed input.
|
||||
BEAST_EXPECT(
|
||||
env.rpc(
|
||||
"json",
|
||||
"pwa_validate",
|
||||
R"({"account":"x","hash":"00"})")[jss::result][jss::error] ==
|
||||
"actMalformed");
|
||||
BEAST_EXPECT(
|
||||
env.rpc(
|
||||
"pwa_validate", alice.human(), "zz")[jss::result][jss::error] ==
|
||||
"invalidParams");
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testValidator();
|
||||
testEnabled();
|
||||
testSetAndRemove();
|
||||
testFee();
|
||||
testMalformed();
|
||||
testAccountDelete();
|
||||
testValidateRPC();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(PWALoader, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,111 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2026 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 <xrpld/app/misc/RotatingBloomFilter.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class RotatingBloomFilter_test : public beast::unit_test::suite
|
||||
{
|
||||
using time_point = std::chrono::steady_clock::time_point;
|
||||
|
||||
void
|
||||
testWindow()
|
||||
{
|
||||
testcase("window");
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
RotatingBloomFilter<1024, 4, 4> filter{10s};
|
||||
time_point const t{};
|
||||
std::string const a{"a"};
|
||||
std::string const b{"b"};
|
||||
|
||||
filter.insert(a, t);
|
||||
BEAST_EXPECT(filter.contains(a, t));
|
||||
BEAST_EXPECT(!filter.contains(b, t));
|
||||
|
||||
// Remembered to the end of the third epoch after its own, and
|
||||
// forgotten at the start of the fourth.
|
||||
BEAST_EXPECT(filter.contains(a, t + 39s));
|
||||
BEAST_EXPECT(!filter.contains(a, t + 40s));
|
||||
|
||||
// Inserting again extends it.
|
||||
filter.insert(a, t + 35s);
|
||||
BEAST_EXPECT(filter.contains(a, t + 69s));
|
||||
BEAST_EXPECT(!filter.contains(a, t + 70s));
|
||||
|
||||
// A clock that jumps finds everything expired.
|
||||
filter.insert(b, t + 70s);
|
||||
BEAST_EXPECT(!filter.contains(b, t + 1h));
|
||||
|
||||
// An insertion that read the clock before another thread rotated its
|
||||
// slot lands in the newer epoch rather than being lost.
|
||||
filter.insert(b, t + 1h + 40s);
|
||||
filter.insert(a, t + 1h);
|
||||
BEAST_EXPECT(filter.contains(a, t + 1h + 40s));
|
||||
}
|
||||
|
||||
void
|
||||
testFalsePositives()
|
||||
{
|
||||
testcase("false positives");
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
using Filter = RotatingBloomFilter<std::size_t{1} << 14, 4, 4>;
|
||||
static_assert(Filter::bytes == 8192);
|
||||
|
||||
Filter filter{120s};
|
||||
time_point const t{};
|
||||
|
||||
std::uint64_t const inserted = 1000;
|
||||
for (std::uint64_t i = 0; i < inserted; ++i)
|
||||
filter.insert(i, t);
|
||||
|
||||
bool all = true;
|
||||
for (std::uint64_t i = 0; i < inserted; ++i)
|
||||
all = all && filter.contains(i, t);
|
||||
BEAST_EXPECT(all);
|
||||
|
||||
// About 0.24% in theory for a thousand elements in one generation.
|
||||
std::uint64_t const probes = 100000;
|
||||
std::uint64_t hits = 0;
|
||||
for (std::uint64_t i = inserted; i < inserted + probes; ++i)
|
||||
hits += filter.contains(i, t) ? 1 : 0;
|
||||
BEAST_EXPECT(hits < probes / 100);
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testWindow();
|
||||
testFalsePositives();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(RotatingBloomFilter, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -26,11 +26,19 @@
|
||||
#include <xrpld/app/misc/detail/OnlineDeleteRanges.h>
|
||||
#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
|
||||
#include <xrpld/core/ConfigSections.h>
|
||||
#include <xrpld/nodestore/NodeObject.h>
|
||||
#include <xrpld/nodestore/detail/DatabaseRotatingImp.h>
|
||||
#include <xrpl/beast/utility/temp_dir.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
@@ -47,6 +55,19 @@ class SHAMapStore_test : public beast::unit_test::suite
|
||||
return cfg;
|
||||
}
|
||||
|
||||
// Same as onlineDelete, but on a node store that persists nodes, so
|
||||
// SHAMapStoreImp builds DatabaseRotatingImp and really rotates.
|
||||
// envconfig defaults to type=rwdb, which is the null node store.
|
||||
static auto
|
||||
onlineDeleteStoring(std::unique_ptr<Config> cfg, std::string const& path)
|
||||
{
|
||||
cfg = onlineDelete(std::move(cfg));
|
||||
auto& section = cfg->section(ConfigSection::nodeDatabase());
|
||||
section.set("type", "memory");
|
||||
section.set("path", path);
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static auto
|
||||
advisoryDelete(std::unique_ptr<Config> cfg)
|
||||
{
|
||||
@@ -74,6 +95,27 @@ class SHAMapStore_test : public beast::unit_test::suite
|
||||
return cfg;
|
||||
}
|
||||
|
||||
// Online delete with DatabasePinned over in-memory backends. Unlike
|
||||
// pinnedPersistent, the hot store keeps nodes (type=rwdb is the null
|
||||
// node store), so ledgers closed in the test can be fetched. There is
|
||||
// no database_path, so every SHAMapStoreImp opens its own private
|
||||
// state.db: a second instance built by a test does not see (or trip
|
||||
// over) the rotation state of the Env's own.
|
||||
static auto
|
||||
pinnedInMemory(
|
||||
std::unique_ptr<Config> cfg,
|
||||
std::string const& nodePath,
|
||||
std::string const& pinnedPath)
|
||||
{
|
||||
cfg = onlineDelete(std::move(cfg));
|
||||
auto& section = cfg->section(ConfigSection::nodeDatabase());
|
||||
section.set("type", "memory");
|
||||
section.set("path", nodePath);
|
||||
section.set("pinned_type", "memory");
|
||||
section.set("pinned_path", pinnedPath);
|
||||
return cfg;
|
||||
}
|
||||
|
||||
bool
|
||||
goodLedger(
|
||||
jtx::Env& env,
|
||||
@@ -308,13 +350,23 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
testAutomatic()
|
||||
testAutomatic(bool storing)
|
||||
{
|
||||
testcase("automatic online_delete");
|
||||
testcase(
|
||||
storing ? "automatic online_delete (rotating node store)"
|
||||
: "automatic online_delete (null node store)");
|
||||
using namespace jtx;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
Env env(*this, envconfig(onlineDelete));
|
||||
beast::temp_dir nodeDir;
|
||||
Env env(
|
||||
*this,
|
||||
storing ? envconfig(onlineDeleteStoring, nodeDir.path())
|
||||
: envconfig(onlineDelete));
|
||||
BEAST_EXPECT(
|
||||
storing ==
|
||||
(dynamic_cast<NodeStore::DatabaseRotating*>(
|
||||
&env.app().getNodeStore()) != nullptr));
|
||||
auto& store = env.app().getSHAMapStore();
|
||||
|
||||
auto ledgerSeq = waitForReady(env);
|
||||
@@ -572,6 +624,412 @@ public:
|
||||
return backend;
|
||||
}
|
||||
|
||||
std::unique_ptr<NodeStore::Backend>
|
||||
makeMemoryBackend(
|
||||
jtx::Env& env,
|
||||
NodeStoreScheduler& scheduler,
|
||||
std::string const& name)
|
||||
{
|
||||
Section section;
|
||||
section.set("type", "memory");
|
||||
section.set("path", name);
|
||||
auto backend = NodeStore::Manager::instance().make_Backend(
|
||||
section,
|
||||
megabytes(env.app().config().getValueFor(
|
||||
SizedItem::burstSize, std::nullopt)),
|
||||
scheduler,
|
||||
env.app().logs().journal("NodeStoreTest"));
|
||||
backend->open();
|
||||
return backend;
|
||||
}
|
||||
|
||||
// Ordinary fetch (duplicate == false) must copy an archived object
|
||||
// into the writable backend. The following rotation deletes the
|
||||
// archive. Without that copy, a transaction node read between
|
||||
// rotations disappears while callers can still name it.
|
||||
void
|
||||
testArchiveReadSurvivesRotation()
|
||||
{
|
||||
testcase("archive read survives rotation");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this, envconfig(onlineDelete));
|
||||
NodeStoreScheduler scheduler(env.app().getJobQueue());
|
||||
|
||||
Section nscfg;
|
||||
nscfg.set("type", "memory");
|
||||
nscfg.set("path", "rotating");
|
||||
|
||||
auto dbr = std::make_unique<NodeStore::DatabaseRotatingImp>(
|
||||
env.app(),
|
||||
scheduler,
|
||||
1,
|
||||
makeMemoryBackend(env, scheduler, "writable"),
|
||||
makeMemoryBackend(env, scheduler, "archive"),
|
||||
nscfg,
|
||||
env.app().logs().journal("NodeStoreTest"));
|
||||
|
||||
Blob const expected(32, 0xab);
|
||||
auto const hash = sha512Half(makeSlice(expected));
|
||||
{
|
||||
Blob data = expected;
|
||||
dbr->store(hotTRANSACTION_NODE, std::move(data), hash, 1);
|
||||
}
|
||||
|
||||
auto const matches = [&](std::shared_ptr<NodeObject> const& object) {
|
||||
return object && object->getType() == hotTRANSACTION_NODE &&
|
||||
object->getData() == expected;
|
||||
};
|
||||
|
||||
// Public fetch defaults duplicate to false. The private override
|
||||
// is the rotation copy pass.
|
||||
NodeStore::Database& db = *dbr;
|
||||
BEAST_EXPECT(matches(db.fetchNodeObject(hash)));
|
||||
|
||||
auto const nop = [](std::string const&, std::string const&) {};
|
||||
// Writable becomes the archive. The object now lives only there.
|
||||
dbr->rotate(makeMemoryBackend(env, scheduler, "writable-2"), nop);
|
||||
|
||||
BEAST_EXPECT(matches(db.fetchNodeObject(hash)));
|
||||
|
||||
dbr->rotate(makeMemoryBackend(env, scheduler, "writable-3"), nop);
|
||||
BEAST_EXPECT(matches(db.fetchNodeObject(hash)));
|
||||
}
|
||||
|
||||
// getWriteLoad, sync, storeLedger, importDatabase and for_each changed
|
||||
// lock type. In null mode no Env builds a DatabaseRotatingImp, so
|
||||
// drive them directly.
|
||||
void
|
||||
testRotatingAccessors()
|
||||
{
|
||||
testcase("rotating database accessors");
|
||||
|
||||
using namespace jtx;
|
||||
// Source ledger must come from a node store that holds its nodes.
|
||||
Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
|
||||
auto& section = cfg->section(ConfigSection::nodeDatabase());
|
||||
section.set("type", "memory");
|
||||
section.set("path", "rotating-accessors-app");
|
||||
return cfg;
|
||||
}));
|
||||
env.fund(XRP(10000), Account{"alice"});
|
||||
env.close();
|
||||
|
||||
NodeStoreScheduler scheduler(env.app().getJobQueue());
|
||||
Section nscfg;
|
||||
nscfg.set("type", "memory");
|
||||
nscfg.set("path", "rotating-accessors");
|
||||
auto makeDb = [&](std::string const& prefix) {
|
||||
return std::make_unique<NodeStore::DatabaseRotatingImp>(
|
||||
env.app(),
|
||||
scheduler,
|
||||
1,
|
||||
makeMemoryBackend(env, scheduler, prefix + "-writable"),
|
||||
makeMemoryBackend(env, scheduler, prefix + "-archive"),
|
||||
nscfg,
|
||||
env.app().logs().journal("NodeStoreTest"));
|
||||
};
|
||||
|
||||
auto src = makeDb("rotating-accessors-src");
|
||||
BEAST_EXPECT(src->getName() == "rotating-accessors-src-writable");
|
||||
BEAST_EXPECT(src->getWriteLoad() >= 0);
|
||||
src->sync();
|
||||
|
||||
auto const ledger = env.app().getLedgerMaster().getClosedLedger();
|
||||
if (!BEAST_EXPECT(ledger))
|
||||
return;
|
||||
BEAST_EXPECT(src->storeLedger(ledger));
|
||||
|
||||
NodeStore::Database& srcDb = *src;
|
||||
auto const stateRoot = ledger->stateMap().getHash().as_uint256();
|
||||
BEAST_EXPECT(srcDb.fetchNodeObject(ledger->info().hash));
|
||||
BEAST_EXPECT(srcDb.fetchNodeObject(stateRoot));
|
||||
|
||||
// importDatabase walks the source with for_each.
|
||||
auto dst = makeDb("rotating-accessors-dst");
|
||||
dst->importDatabase(*src);
|
||||
NodeStore::Database& dstDb = *dst;
|
||||
BEAST_EXPECT(dstDb.fetchNodeObject(ledger->info().hash));
|
||||
BEAST_EXPECT(dstDb.fetchNodeObject(stateRoot));
|
||||
}
|
||||
|
||||
static auto
|
||||
nullBackend(std::unique_ptr<Config> cfg)
|
||||
{
|
||||
cfg->LEDGER_HISTORY = 8;
|
||||
auto& section = cfg->section(ConfigSection::nodeDatabase());
|
||||
section.set("type", "rwdb");
|
||||
section.set("path", "main");
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static auto
|
||||
nullBackendFullHistory(std::unique_ptr<Config> cfg)
|
||||
{
|
||||
cfg = nullBackend(std::move(cfg));
|
||||
cfg->LEDGER_HISTORY = std::numeric_limits<std::uint32_t>::max();
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static auto
|
||||
nullBackendDeleteOff(std::unique_ptr<Config> cfg)
|
||||
{
|
||||
cfg = nullBackend(std::move(cfg));
|
||||
cfg->LEDGER_HISTORY = 256;
|
||||
cfg->section(ConfigSection::nodeDatabase()).set("online_delete", "0");
|
||||
return cfg;
|
||||
}
|
||||
|
||||
// Payments keep applying after more closes than ledger_history,
|
||||
// with no node-store record of the state tree. Close time comes
|
||||
// from the resident ledger, not a node-store header walk.
|
||||
void
|
||||
testNullModeLedgerProgression()
|
||||
{
|
||||
testcase("null mode ledger progression");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this, envconfig(nullBackend));
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto const bobBefore = env.balance(bob);
|
||||
constexpr int payments = 12;
|
||||
for (int i = 0; i < payments; ++i)
|
||||
{
|
||||
env(pay(alice, bob, XRP(1)));
|
||||
env.close();
|
||||
}
|
||||
|
||||
BEAST_EXPECT(env.balance(bob) == bobBefore + XRP(payments));
|
||||
BEAST_EXPECT(env.seq(alice) > 1);
|
||||
|
||||
auto const info = env.rpc(
|
||||
"json",
|
||||
"account_info",
|
||||
std::string{"{\"account\": \""} + alice.human() + "\"}");
|
||||
BEAST_EXPECT(
|
||||
info[jss::result].isMember(jss::account_data) &&
|
||||
info[jss::result][jss::account_data][jss::Account] ==
|
||||
alice.human());
|
||||
|
||||
auto const ledger = env.app().getLedgerMaster().getClosedLedger();
|
||||
if (!BEAST_EXPECT(ledger))
|
||||
return;
|
||||
BEAST_EXPECT(ledger->read(keylet::account(alice.id())));
|
||||
|
||||
auto const closeTime = env.app().getLedgerMaster().getCloseTimeByHash(
|
||||
ledger->info().hash, ledger->info().seq);
|
||||
BEAST_EXPECT(closeTime && *closeTime == ledger->info().closeTime);
|
||||
|
||||
auto const validated = env.rpc("ledger", "validated");
|
||||
BEAST_EXPECT(goodLedger(
|
||||
env, validated, std::to_string(ledger->info().seq), true));
|
||||
}
|
||||
|
||||
// ledger_history=full would make the retained-ledger window grow
|
||||
// without bound, since nothing else can drop a null-mode tree.
|
||||
void
|
||||
testNullModeRejectsFullHistory()
|
||||
{
|
||||
testcase("RWDB null mode rejects ledger_history=full");
|
||||
|
||||
using namespace jtx;
|
||||
try
|
||||
{
|
||||
Env env(*this, envconfig(nullBackendFullHistory));
|
||||
fail("Env should throw when ledger_history is full");
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
std::string(e.what()).find("ledger_history") !=
|
||||
std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
// online_delete=0 is an explicit disable. It must not be treated
|
||||
// as a missing key and replaced with ledger_history.
|
||||
void
|
||||
testExplicitOnlineDeleteZero()
|
||||
{
|
||||
testcase("explicit online_delete zero stays off");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this, envconfig(nullBackendDeleteOff));
|
||||
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
auto const balance = env.balance(alice);
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.app().getSHAMapStore().getLastRotated() == 0);
|
||||
BEAST_EXPECT(env.balance(alice) == balance);
|
||||
}
|
||||
|
||||
void
|
||||
testNullModeRequiresHistory()
|
||||
{
|
||||
testcase("RWDB null mode requires ledger_history > 0");
|
||||
|
||||
using namespace jtx;
|
||||
try
|
||||
{
|
||||
Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->LEDGER_HISTORY = 0;
|
||||
auto& section = cfg->section(ConfigSection::nodeDatabase());
|
||||
section.set("type", "rwdb");
|
||||
section.set("path", "main");
|
||||
return cfg;
|
||||
}));
|
||||
fail("Env should throw when ledger_history is 0");
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
std::string(e.what()).find("ledger_history") !=
|
||||
std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testRetainedLedgerCloseTime()
|
||||
{
|
||||
testcase("retained ledger close time survives later closes");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this, envconfig(nullBackend));
|
||||
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
auto const first = env.app().getLedgerMaster().getClosedLedger();
|
||||
if (!BEAST_EXPECT(first))
|
||||
return;
|
||||
auto const firstHash = first->info().hash;
|
||||
auto const firstSeq = first->info().seq;
|
||||
auto const firstClose = first->info().closeTime;
|
||||
|
||||
for (int i = 0; i < 12; ++i)
|
||||
{
|
||||
env(pay(alice, env.master, XRP(1)));
|
||||
env.close();
|
||||
}
|
||||
|
||||
auto const closeTime =
|
||||
env.app().getLedgerMaster().getCloseTimeByHash(firstHash, firstSeq);
|
||||
BEAST_EXPECT(closeTime && *closeTime == firstClose);
|
||||
BEAST_EXPECT(env.balance(alice) < XRP(10000));
|
||||
}
|
||||
|
||||
void
|
||||
testNullBackendIsPerConfig()
|
||||
{
|
||||
testcase("null backend flag is per configuration");
|
||||
|
||||
Config rwdbCfg;
|
||||
Config upperCfg;
|
||||
Config nudbCfg;
|
||||
Config emptyCfg;
|
||||
rwdbCfg.section(ConfigSection::nodeDatabase()).set("type", "rwdb");
|
||||
upperCfg.section(ConfigSection::nodeDatabase()).set("type", "RWDB");
|
||||
nudbCfg.section(ConfigSection::nodeDatabase()).set("type", "NuDB");
|
||||
|
||||
// Evaluated per Config object, not latched process-wide.
|
||||
BEAST_EXPECT(rwdbCfg.nullBackend());
|
||||
BEAST_EXPECT(upperCfg.nullBackend());
|
||||
BEAST_EXPECT(!nudbCfg.nullBackend());
|
||||
BEAST_EXPECT(!emptyCfg.nullBackend());
|
||||
BEAST_EXPECT(rwdbCfg.nullBackend());
|
||||
}
|
||||
|
||||
// Resident lookups are used with peer-supplied values. They must find
|
||||
// in-memory ledgers but never load or edit the complete-ledger set.
|
||||
void
|
||||
testResidentLedgerLookup()
|
||||
{
|
||||
testcase("resident ledger lookup does not load or clear");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this, envconfig(nullBackend));
|
||||
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
for (int i = 0; i < 4; ++i)
|
||||
env.close();
|
||||
|
||||
auto& lm = env.app().getLedgerMaster();
|
||||
auto const closed = lm.getClosedLedger();
|
||||
if (!BEAST_EXPECT(closed))
|
||||
return;
|
||||
|
||||
auto const complete = lm.getCompleteLedgers();
|
||||
|
||||
auto const bySeq = lm.getResidentLedgerBySeq(closed->info().seq);
|
||||
BEAST_EXPECT(bySeq && bySeq->info().hash == closed->info().hash);
|
||||
|
||||
auto const byHash = lm.getResidentLedgerByHash(closed->info().hash);
|
||||
BEAST_EXPECT(byHash && byHash->info().seq == closed->info().seq);
|
||||
|
||||
// A recent ledger still inside the retained window.
|
||||
auto const prior = lm.getResidentLedgerBySeq(closed->info().seq - 2);
|
||||
BEAST_EXPECT(prior && prior->info().seq == closed->info().seq - 2);
|
||||
|
||||
BEAST_EXPECT(!lm.getResidentLedgerByHash(uint256{12345}));
|
||||
BEAST_EXPECT(!lm.getResidentLedgerBySeq(closed->info().seq + 1000));
|
||||
|
||||
BEAST_EXPECT(lm.getCompleteLedgers() == complete);
|
||||
|
||||
// Drop every ledger from the history cache. Null mode must still
|
||||
// reach recent ledgers through the closed/validated holders and
|
||||
// the retained window, without loading anything.
|
||||
env.app().getJobQueue().rendezvous();
|
||||
auto const older = lm.getResidentLedgerBySeq(closed->info().seq - 3);
|
||||
if (!BEAST_EXPECT(older))
|
||||
return;
|
||||
lm.clearLedgerCachePrior(closed->info().seq + 1);
|
||||
|
||||
auto const closedAgain = lm.getResidentLedgerBySeq(closed->info().seq);
|
||||
BEAST_EXPECT(
|
||||
closedAgain && closedAgain->info().hash == closed->info().hash);
|
||||
auto const closedByHash =
|
||||
lm.getResidentLedgerByHash(closed->info().hash);
|
||||
BEAST_EXPECT(
|
||||
closedByHash && closedByHash->info().seq == closed->info().seq);
|
||||
|
||||
auto const retainedBySeq = lm.getResidentLedgerBySeq(older->info().seq);
|
||||
BEAST_EXPECT(
|
||||
retainedBySeq && retainedBySeq->info().hash == older->info().hash);
|
||||
auto const retainedByHash =
|
||||
lm.getResidentLedgerByHash(older->info().hash);
|
||||
BEAST_EXPECT(
|
||||
retainedByHash && retainedByHash->info().seq == older->info().seq);
|
||||
|
||||
BEAST_EXPECT(!lm.getResidentLedgerByHash(uint256{12345}));
|
||||
BEAST_EXPECT(lm.getCompleteLedgers() == complete);
|
||||
}
|
||||
|
||||
void
|
||||
testNullFactoryDropsWrites()
|
||||
{
|
||||
testcase("type=rwdb uses NullFactory and drops node-store writes");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this, envconfig(nullBackend));
|
||||
|
||||
auto& db = env.app().getNodeStore();
|
||||
uint256 const hash{2};
|
||||
Blob data(32, 7);
|
||||
db.store(hotACCOUNT_NODE, std::move(data), hash, 1);
|
||||
BEAST_EXPECT(!db.fetchNodeObject(hash, 1));
|
||||
}
|
||||
|
||||
void
|
||||
testRotate()
|
||||
{
|
||||
@@ -718,6 +1176,127 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// catalogue_unpin of everything (or a rolled-back first load) leaves the
|
||||
// pinned set empty. That must persist as "no pins", not as a string that
|
||||
// fails to parse on the next start.
|
||||
void
|
||||
testPinnedRangeRestoreEmpty()
|
||||
{
|
||||
testcase("pinned range restore of an empty set");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
beast::temp_dir tempDir;
|
||||
auto const tempPath = boost::filesystem::path(tempDir.path());
|
||||
auto const nodePath = (tempPath / "node").string();
|
||||
auto const pinnedPath = (tempPath / "pinned").string();
|
||||
|
||||
Env env(*this, envconfig(pinnedInMemory, nodePath, pinnedPath));
|
||||
|
||||
NodeStoreScheduler scheduler(env.app().getJobQueue());
|
||||
auto const journal = env.app().journal("SHAMapStoreTest");
|
||||
try
|
||||
{
|
||||
SHAMapStoreImp restoreCheck(env.app(), scheduler, journal);
|
||||
|
||||
RangeSet<std::uint32_t> ranges;
|
||||
ranges.insert(range(100u, 200u));
|
||||
restoreCheck.setPinnedRanges(ranges);
|
||||
restoreCheck.setPinnedRanges(RangeSet<std::uint32_t>{});
|
||||
|
||||
restoreCheck.start();
|
||||
restoreCheck.stop();
|
||||
pass();
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
fail(
|
||||
std::string("restart with no pinned ranges threw: ") +
|
||||
e.what());
|
||||
}
|
||||
BEAST_EXPECT(
|
||||
env.app().getLedgerMaster().getPinnedLedgersRangeSet().empty());
|
||||
}
|
||||
|
||||
// A crash between a pinned-range checkpoint and the backend committing
|
||||
// the last ledgers leaves the recorded interval's tail missing. Startup
|
||||
// trims it back to the last ledger present instead of refusing to start.
|
||||
void
|
||||
testPinnedRangeRestoreTrimsMissingTail()
|
||||
{
|
||||
testcase("pinned range restore trims a missing tail");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
beast::temp_dir tempDir;
|
||||
auto const tempPath = boost::filesystem::path(tempDir.path());
|
||||
auto const nodePath = (tempPath / "node").string();
|
||||
auto const pinnedPath = (tempPath / "pinned").string();
|
||||
|
||||
Env env(*this, envconfig(pinnedInMemory, nodePath, pinnedPath));
|
||||
for (int i = 0; i < 4; ++i)
|
||||
env.close();
|
||||
|
||||
auto& rdb = env.app().getRelationalDatabase();
|
||||
auto const last = env.app().getLedgerMaster().getValidLedgerIndex();
|
||||
auto const first = last - 2;
|
||||
|
||||
// Ledger saves may complete asynchronously; wait for the SQL rows.
|
||||
for (int i = 0; i < 100 && rdb.getHashByIndex(last).isZero(); ++i)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
if (!BEAST_EXPECT(
|
||||
rdb.getHashByIndex(first).isNonZero() &&
|
||||
rdb.getHashByIndex(last).isNonZero()))
|
||||
return;
|
||||
|
||||
NodeStoreScheduler scheduler(env.app().getJobQueue());
|
||||
auto const journal = env.app().journal("SHAMapStoreTest");
|
||||
try
|
||||
{
|
||||
SHAMapStoreImp restoreCheck(env.app(), scheduler, journal);
|
||||
|
||||
// Recorded through last + 10, but only [first, last] exist.
|
||||
RangeSet<std::uint32_t> recorded;
|
||||
recorded.insert(range(first, last + 10));
|
||||
restoreCheck.setPinnedRanges(recorded);
|
||||
|
||||
restoreCheck.start();
|
||||
restoreCheck.stop();
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
fail(std::string("restart with a missing tail threw: ") + e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
RangeSet<std::uint32_t> expected;
|
||||
expected.insert(range(first, last));
|
||||
BEAST_EXPECT(
|
||||
env.app().getLedgerMaster().getPinnedLedgersRangeSet() == expected);
|
||||
|
||||
// A missing tail longer than the trim limit (65536 ledgers) is not
|
||||
// an interrupted load: it means the wrong pinned store, so startup
|
||||
// still refuses.
|
||||
try
|
||||
{
|
||||
SHAMapStoreImp restoreCheck(env.app(), scheduler, journal);
|
||||
|
||||
RangeSet<std::uint32_t> recorded;
|
||||
recorded.insert(range(first, last + 70000));
|
||||
restoreCheck.setPinnedRanges(recorded);
|
||||
|
||||
restoreCheck.start();
|
||||
restoreCheck.stop();
|
||||
fail("Expected startup failure for an over-long missing tail");
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
std::string(e.what()).find("Persisted pinned interval") !=
|
||||
std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: compare two RangeSet<uint32_t> for set-equality. The
|
||||
// test cares about which sequences are in the result, not the
|
||||
// particular interval representation.
|
||||
@@ -852,10 +1431,23 @@ public:
|
||||
{
|
||||
testComputeOnlineDeleteTargets();
|
||||
testClear();
|
||||
testAutomatic();
|
||||
testAutomatic(false);
|
||||
testAutomatic(true);
|
||||
testRotatingAccessors();
|
||||
testCanDelete();
|
||||
testArchiveReadSurvivesRotation();
|
||||
testNullModeLedgerProgression();
|
||||
testNullModeRejectsFullHistory();
|
||||
testExplicitOnlineDeleteZero();
|
||||
testNullModeRequiresHistory();
|
||||
testRetainedLedgerCloseTime();
|
||||
testNullBackendIsPerConfig();
|
||||
testResidentLedgerLookup();
|
||||
testNullFactoryDropsWrites();
|
||||
testRotate();
|
||||
testPinnedRangeRestoreRequiresPinnedData();
|
||||
testPinnedRangeRestoreEmpty();
|
||||
testPinnedRangeRestoreTrimsMissingTail();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6146,6 +6146,15 @@ private:
|
||||
pass();
|
||||
}
|
||||
|
||||
void
|
||||
testEntropyTSH(FeatureBitset features)
|
||||
{
|
||||
testcase("entropy tsh");
|
||||
|
||||
// pseudo transaction
|
||||
pass();
|
||||
}
|
||||
|
||||
// | otxn | tfBurnable | tsh | mint | burn | buy | sell | cancel
|
||||
// | O | false | O | N/A | S | N/A | S | S
|
||||
// | O | false | I | N/A | N | N/A | W | N/A
|
||||
@@ -8439,11 +8448,8 @@ private:
|
||||
using namespace std::literals;
|
||||
testcase("set manifest TSH");
|
||||
|
||||
if (!features[featureOnChainManifests])
|
||||
{
|
||||
pass();
|
||||
return;
|
||||
}
|
||||
// OnChainManifests is not yet Supported::yes, so enable it explicitly.
|
||||
features = features | featureOnChainManifests;
|
||||
|
||||
// otxn: master
|
||||
// tsh: ephemeral
|
||||
|
||||
+990
-25
File diff suppressed because it is too large.
Load diff
+1166
-111
File diff suppressed because it is too large.
Load diff
@@ -20,7 +20,10 @@
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/network.h>
|
||||
#include <xrpld/app/ledger/OpenLedger.h>
|
||||
#include <xrpld/app/misc/HashRouter.h>
|
||||
#include <xrpld/app/misc/Manifest.h>
|
||||
#include <xrpld/app/misc/ManifestLedger.h>
|
||||
#include <xrpld/app/misc/NetworkOPs.h>
|
||||
#include <xrpld/app/tx/apply.h>
|
||||
#include <xrpld/app/tx/detail/SetManifest.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
@@ -54,10 +57,10 @@ struct SetManifest_test : public beast::unit_test::suite
|
||||
// A manifest transaction is unsigned, so the network id is mandatory and
|
||||
// the network must be one that requires it (id > 1024).
|
||||
static std::unique_ptr<Config>
|
||||
makeConfig()
|
||||
makeConfig(std::string fee = "10")
|
||||
{
|
||||
return jtx::network::makeNetworkConfig(
|
||||
21337, "10", "1000000", "200000");
|
||||
21337, std::move(fee), "1000000", "200000");
|
||||
}
|
||||
|
||||
/** Builds a manifest signed by `master` nominating `ephemeral`.
|
||||
@@ -111,6 +114,15 @@ struct SetManifest_test : public beast::unit_test::suite
|
||||
return env.rpc("json", "submit", to_string(params))[jss::result];
|
||||
}
|
||||
|
||||
/** Submits an already formed SetManifest transaction. */
|
||||
static Json::Value
|
||||
submit(jtx::Env& env, std::shared_ptr<STTx const> const& tx)
|
||||
{
|
||||
Serializer s;
|
||||
tx->add(s);
|
||||
return env.rpc("submit", strHex(s.slice()))[jss::result];
|
||||
}
|
||||
|
||||
static std::string
|
||||
engineResult(Json::Value const& result)
|
||||
{
|
||||
@@ -166,13 +178,13 @@ struct SetManifest_test : public beast::unit_test::suite
|
||||
});
|
||||
};
|
||||
|
||||
// The same two-pass pricing makeSetManifestTx() does: encode once with
|
||||
// a placeholder purely to have something to measure, then encode at the
|
||||
// ceiling checkFee() will accept.
|
||||
auto const base = SetManifest::calculateBaseFee(
|
||||
*env.current(), *build(XRPAmount{0}, false));
|
||||
|
||||
return build(mulRatio(base, 12, 10, /*roundUp*/ true), true);
|
||||
// The canonical Fee makeSetManifestTx() uses for the open ledger.
|
||||
SerialIter mit{makeSlice(manifest)};
|
||||
STObject const manifestObject{mit, sfManifest};
|
||||
return build(
|
||||
canonicalSetManifestFee(env.current()->fees().base, manifestObject)
|
||||
.value_or(XRPAmount{0}),
|
||||
true);
|
||||
}
|
||||
|
||||
/** A mutable copy of a ledger object, suitable for the RawView interface.
|
||||
@@ -542,9 +554,99 @@ struct SetManifest_test : public beast::unit_test::suite
|
||||
submit(env, makeManifest(master, ephemeral, 1));
|
||||
env.close();
|
||||
|
||||
// Every refusal over a key in use is tef. The transaction is unsigned
|
||||
// and consumes no sequence, so anyone holding it could resubmit it in
|
||||
// every ledger; a tec would charge the master account each time.
|
||||
auto const masterBalance = env.balance(master);
|
||||
auto const otherBalance = env.balance(other);
|
||||
|
||||
BEAST_EXPECT(
|
||||
engineResult(submit(env, makeManifest(other, ephemeral, 1))) ==
|
||||
"tecDUPLICATE");
|
||||
"tefMANIFEST_KEY_IN_USE");
|
||||
|
||||
// A newer manifest that keeps the current ephemeral key. Every
|
||||
// manifest cache refuses it, so the ledger must too, or it would stay
|
||||
// ahead of what any node believes.
|
||||
BEAST_EXPECT(
|
||||
engineResult(submit(env, makeManifest(master, ephemeral, 2))) ==
|
||||
"tefMANIFEST_KEY_IN_USE");
|
||||
|
||||
// A master key that is already another account's ephemeral key: the
|
||||
// two kinds of manifest keylet share one namespace.
|
||||
auto const third = Account("third", KeyType::ed25519);
|
||||
auto const thirdEph = Account("thirdEph", KeyType::ed25519);
|
||||
env.fund(XRP(1000), third);
|
||||
env.close();
|
||||
|
||||
auto const thirdBalance = env.balance(third);
|
||||
BEAST_EXPECT(
|
||||
engineResult(submit(env, makeManifest(other, third, 1))) ==
|
||||
"tesSUCCESS");
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
engineResult(submit(env, makeManifest(third, thirdEph, 1))) ==
|
||||
"tefMANIFEST_KEY_IN_USE");
|
||||
env.close();
|
||||
|
||||
// Nothing was charged and nothing was written.
|
||||
BEAST_EXPECT(env.balance(master) == masterBalance);
|
||||
BEAST_EXPECT(env.balance(third) == thirdBalance);
|
||||
BEAST_EXPECT(env.balance(other) < otherBalance);
|
||||
if (auto const sle = env.le(keylet::manifest(master.pk()));
|
||||
BEAST_EXPECT(sle))
|
||||
BEAST_EXPECT(sle->getFieldU32(sfSequence) == 1);
|
||||
if (auto const sle = env.le(keylet::manifest(third.pk()));
|
||||
BEAST_EXPECT(sle))
|
||||
BEAST_EXPECT(sle->getAccountID(sfAccount) == other.id());
|
||||
BEAST_EXPECT(!env.le(keylet::manifest(thirdEph.pk())));
|
||||
BEAST_EXPECT(
|
||||
!env.le(keylet::account(third.id()))->isFieldPresent(sfManifestID));
|
||||
}
|
||||
|
||||
void
|
||||
testAccountDelete(FeatureBitset features)
|
||||
{
|
||||
testcase("account delete");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, makeConfig(), features};
|
||||
|
||||
auto const master = Account("master", KeyType::ed25519);
|
||||
auto const ephemeral = Account("ephemeral", KeyType::ed25519);
|
||||
auto const plain = Account("plain", KeyType::ed25519);
|
||||
auto const dest = Account("dest");
|
||||
env.fund(XRP(1000), master, plain, dest);
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
engineResult(submit(env, makeManifest(master, ephemeral, 1))) ==
|
||||
"tesSUCCESS");
|
||||
env.close();
|
||||
|
||||
// Old enough to delete, so the manifest is the only obstacle.
|
||||
incLgrSeqForAccDel(env, master);
|
||||
incLgrSeqForAccDel(env, plain);
|
||||
|
||||
// An on-ledger manifest outlives its account: neither copy is in the
|
||||
// owner directory, and the master copy stays listed in the manifest
|
||||
// directory for good.
|
||||
env(acctdelete(master, dest),
|
||||
fee(drops(env.current()->fees().increment)),
|
||||
ter(tecHAS_OBLIGATIONS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.le(keylet::account(master.id())));
|
||||
BEAST_EXPECT(env.le(keylet::manifest(master.pk())));
|
||||
BEAST_EXPECT(env.le(keylet::manifest(ephemeral.pk())));
|
||||
|
||||
// An account of the same age without one goes as usual.
|
||||
env(acctdelete(plain, dest),
|
||||
fee(drops(env.current()->fees().increment)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(!env.le(keylet::account(plain.id())));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -597,7 +699,9 @@ struct SetManifest_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(
|
||||
applyDirect(env, envelope(env, good, other.id())) == temMALFORMED);
|
||||
|
||||
// Every envelope field a relayer could otherwise choose is pinned.
|
||||
// Every envelope field a relayer could otherwise choose is pinned: the
|
||||
// envelope is one exact transaction, with Account and NetworkID derived
|
||||
// rather than mirrored.
|
||||
for (auto const& [name, tweak] : std::vector<
|
||||
std::pair<char const*, std::function<void(STObject&)>>>{
|
||||
{"Sequence",
|
||||
@@ -608,33 +712,175 @@ struct SetManifest_test : public beast::unit_test::suite
|
||||
}},
|
||||
{"TicketSequence",
|
||||
[](STObject& obj) { obj.setFieldU32(sfTicketSequence, 1); }},
|
||||
{"SigningPubKey", [&](STObject& obj) {
|
||||
{"SigningPubKey",
|
||||
[&](STObject& obj) {
|
||||
obj.setFieldVL(sfSigningPubKey, master.pk().slice());
|
||||
}}})
|
||||
}},
|
||||
{"Memos",
|
||||
[](STObject& obj) {
|
||||
obj.setFieldArray(sfMemos, STArray(sfMemos, 1));
|
||||
STObject memo{sfMemo};
|
||||
memo.setFieldVL(sfMemoData, Blob{0x01});
|
||||
obj.peekFieldArray(sfMemos).emplace_back(std::move(memo));
|
||||
}},
|
||||
{"SourceTag",
|
||||
[](STObject& obj) { obj.setFieldU32(sfSourceTag, 1); }},
|
||||
{"LastLedgerSequence",
|
||||
[](STObject& obj) {
|
||||
obj.setFieldU32(sfLastLedgerSequence, 1'000'000);
|
||||
}},
|
||||
{"NetworkID",
|
||||
[](STObject& obj) { obj.setFieldU32(sfNetworkID, 21338); }}})
|
||||
{
|
||||
auto const variant = envelope(env, good, master.id(), tweak);
|
||||
// The generic network check runs first for a foreign NetworkID.
|
||||
auto const expected = std::string(name) == "NetworkID"
|
||||
? TER{telWRONG_NETWORK}
|
||||
: TER{temMALFORMED};
|
||||
BEAST_EXPECTS(applyDirect(env, variant) == expected, name);
|
||||
|
||||
// Ingress rejects the variant as a bad envelope before any
|
||||
// manifest signature work, and never as a soft fee miss.
|
||||
auto const [validity, reason] = checkValidity(
|
||||
env.app().getHashRouter(),
|
||||
*variant,
|
||||
env.current()->rules(),
|
||||
env.app().config());
|
||||
BEAST_EXPECTS(validity == Validity::SigBad, name);
|
||||
BEAST_EXPECTS(
|
||||
applyDirect(env, envelope(env, good, master.id(), tweak)) ==
|
||||
temMALFORMED,
|
||||
checkManifestIngressFee(env.app(), *variant).first ==
|
||||
ManifestIngressFee::NotApplicable,
|
||||
name);
|
||||
}
|
||||
|
||||
// sfFee is the one envelope field preflight cannot bound, because the
|
||||
// base fee is not in scope until preclaim. checkFee() caps it instead.
|
||||
// The Fee is pinned to the one canonical value for the applying view's
|
||||
// base fee. A different value is a local miss, not a malformed
|
||||
// transaction, and ingress drops it before manifest crypto.
|
||||
auto const priced = envelope(env, good, master.id());
|
||||
auto const ceiling = priced->getFieldAmount(sfFee).xrp();
|
||||
auto const canonicalFee = priced->getFieldAmount(sfFee).xrp();
|
||||
BEAST_EXPECT(
|
||||
checkManifestIngressFee(env.app(), *priced).first ==
|
||||
ManifestIngressFee::Canonical);
|
||||
for (auto const fee :
|
||||
{canonicalFee + XRPAmount{1}, canonicalFee - XRPAmount{1}})
|
||||
{
|
||||
auto const variant =
|
||||
envelope(env, good, master.id(), [&](STObject& obj) {
|
||||
obj.setFieldAmount(sfFee, fee);
|
||||
});
|
||||
BEAST_EXPECT(
|
||||
checkManifestIngressFee(env.app(), *variant).first ==
|
||||
ManifestIngressFee::Refused);
|
||||
BEAST_EXPECT(applyDirect(env, variant) == telMANIFEST_FEE_MISMATCH);
|
||||
}
|
||||
|
||||
// At the canonical value exactly, which is what Submit sends.
|
||||
BEAST_EXPECT(applyDirect(env, priced) == tesSUCCESS);
|
||||
}
|
||||
|
||||
void
|
||||
testCanonicalFee(FeatureBitset features)
|
||||
{
|
||||
testcase("canonical fee");
|
||||
using namespace jtx;
|
||||
|
||||
auto const master = Account("master", KeyType::ed25519);
|
||||
auto const eph1 = Account("eph1", KeyType::ed25519);
|
||||
auto const units = [](STObject const& manifestObject) {
|
||||
return 100 +
|
||||
10 *
|
||||
static_cast<std::int64_t>(
|
||||
manifestObject.getSerializer().getDataLength());
|
||||
};
|
||||
auto const manifest = makeManifest(master, eph1, 1);
|
||||
|
||||
// The builder prices against the voted base fee of the open ledger.
|
||||
auto const build = [&](Env& env) -> std::shared_ptr<STTx const> {
|
||||
auto const hex = makeSetManifestTx(
|
||||
makeSlice(manifest),
|
||||
env.app().config().NETWORK_ID,
|
||||
*env.current(),
|
||||
env.app().journal("SetManifest_test"));
|
||||
if (!BEAST_EXPECT(hex))
|
||||
return nullptr;
|
||||
auto const bytes = strUnHex(*hex);
|
||||
if (!BEAST_EXPECT(bytes))
|
||||
return nullptr;
|
||||
SerialIter txIter{makeSlice(*bytes)};
|
||||
return std::make_shared<STTx const>(std::ref(txIter));
|
||||
};
|
||||
auto const manifestOf = [](STTx const& tx) -> STObject const& {
|
||||
return const_cast<STTx&>(tx)
|
||||
.getField(sfManifest)
|
||||
.downcast<STObject>();
|
||||
};
|
||||
|
||||
std::shared_ptr<STTx const> ordinaryTx;
|
||||
{
|
||||
Env ordinary{*this, makeConfig("10"), features};
|
||||
ordinaryTx = build(ordinary);
|
||||
if (!ordinaryTx)
|
||||
return;
|
||||
BEAST_EXPECT(
|
||||
(*ordinaryTx)[sfFee].xrp().drops() ==
|
||||
10 * units(manifestOf(*ordinaryTx)));
|
||||
|
||||
// A zero base would make the transaction free, so there is no
|
||||
// canonical Fee for it at all.
|
||||
BEAST_EXPECT(!canonicalSetManifestFee(
|
||||
XRPAmount{0}, manifestOf(*ordinaryTx)));
|
||||
}
|
||||
|
||||
// A different fee setting maps the same manifest to exactly one
|
||||
// different txid.
|
||||
Env expensive{*this, makeConfig("100000"), features};
|
||||
auto const expensiveTx = build(expensive);
|
||||
if (!expensiveTx)
|
||||
return;
|
||||
BEAST_EXPECT(
|
||||
(*expensiveTx)[sfFee].xrp().drops() ==
|
||||
100'000 * units(manifestOf(*expensiveTx)));
|
||||
BEAST_EXPECT(
|
||||
expensiveTx->getTransactionID() != ordinaryTx->getTransactionID());
|
||||
|
||||
expensive.fund(XRP(10000), master);
|
||||
expensive.close();
|
||||
|
||||
// A wrapper priced for another fee setting is a local miss, not a bad
|
||||
// signature: ingress drops it before manifest crypto without marking
|
||||
// it bad, and preclaim refuses it with a tel result.
|
||||
BEAST_EXPECT(
|
||||
checkManifestIngressFee(expensive.app(), *ordinaryTx).first ==
|
||||
ManifestIngressFee::Refused);
|
||||
BEAST_EXPECT(
|
||||
submit(expensive, ordinaryTx)[jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(
|
||||
(expensive.app().getHashRouter().getFlags(
|
||||
ordinaryTx->getTransactionID()) &
|
||||
SF_BAD) == 0);
|
||||
BEAST_EXPECT(
|
||||
applyDirect(expensive, ordinaryTx) == telMANIFEST_FEE_MISMATCH);
|
||||
|
||||
BEAST_EXPECT(
|
||||
applyDirect(
|
||||
env, envelope(env, good, master.id(), [&](STObject& obj) {
|
||||
obj.setFieldAmount(sfFee, ceiling + XRPAmount{1});
|
||||
})) == temBAD_FEE);
|
||||
checkManifestIngressFee(expensive.app(), *expensiveTx).first ==
|
||||
ManifestIngressFee::Canonical);
|
||||
BEAST_EXPECT(engineResult(submit(expensive, manifest)) == "tesSUCCESS");
|
||||
expensive.close();
|
||||
BEAST_EXPECT(expensive.le(keylet::manifest(master.pk())));
|
||||
|
||||
// At the ceiling exactly, which is what Submit sends.
|
||||
// An amendment-blocked server has no base fee it can trust, so ingress
|
||||
// refuses even the canonical transaction rather than verify it.
|
||||
Env blocked{*this, makeConfig("10"), features};
|
||||
auto const blockedTx = build(blocked);
|
||||
if (!blockedTx)
|
||||
return;
|
||||
BEAST_EXPECT(
|
||||
applyDirect(
|
||||
env, envelope(env, good, master.id(), [&](STObject& obj) {
|
||||
obj.setFieldAmount(sfFee, ceiling);
|
||||
})) == tesSUCCESS);
|
||||
checkManifestIngressFee(blocked.app(), *blockedTx).first ==
|
||||
ManifestIngressFee::Canonical);
|
||||
blocked.app().getOPs().setAmendmentBlocked();
|
||||
BEAST_EXPECT(
|
||||
checkManifestIngressFee(blocked.app(), *blockedTx).first ==
|
||||
ManifestIngressFee::Refused);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -725,6 +971,11 @@ struct SetManifest_test : public beast::unit_test::suite
|
||||
|
||||
BEAST_EXPECT(cache.applyLedger(*env.closed(), all) == 4);
|
||||
|
||||
// Read from the ledger and pinned by nothing, all four are held as
|
||||
// ledger lookup cache entries.
|
||||
for (auto const& m : masters)
|
||||
BEAST_EXPECT(cache.getTier(m.pk()) == ManifestSource::ledger);
|
||||
|
||||
// The raw form is what a republishing node needs: the exact bytes the
|
||||
// master key signed, plus the sequence to compare against the ledger.
|
||||
auto const raw = cache.getRawManifest(masters[0].pk());
|
||||
@@ -740,33 +991,237 @@ struct SetManifest_test : public beast::unit_test::suite
|
||||
auto const seq = cache.sequence();
|
||||
cache.pin({masters[0].pk(), masters[1].pk()});
|
||||
BEAST_EXPECT(cache.sequence() > seq);
|
||||
BEAST_EXPECT(cache.getTier(masters[0].pk()) == ManifestSource::list);
|
||||
|
||||
auto const seq2 = cache.sequence();
|
||||
cache.pin({masters[0].pk(), masters[1].pk()});
|
||||
BEAST_EXPECT(cache.sequence() == seq2);
|
||||
|
||||
// Everything is offered: two pinned plus two under the gossip limit.
|
||||
// Only the pinned two are offered. The other two are on the ledger,
|
||||
// where any peer that is not amendment blocked can read them.
|
||||
std::size_t reserved = 0;
|
||||
std::vector<PublicKey> offered;
|
||||
cache.for_each_gossip_manifest(
|
||||
[&](std::size_t n) { reserved = n; },
|
||||
[&](Manifest const& m) { offered.push_back(m.masterKey); });
|
||||
|
||||
BEAST_EXPECT(reserved == 4);
|
||||
BEAST_EXPECT(offered.size() == 4);
|
||||
BEAST_EXPECT(reserved == 2);
|
||||
BEAST_EXPECT(
|
||||
hash_set<PublicKey>(offered.begin(), offered.end()) == all);
|
||||
hash_set<PublicKey>(offered.begin(), offered.end()) ==
|
||||
hash_set<PublicKey>({masters[0].pk(), masters[1].pk()}));
|
||||
|
||||
// A pinned key with no manifest is counted in the reservation but not
|
||||
// offered, since the reservation is only an upper bound.
|
||||
// Unpinned, a master key falls back to the lookup cache. A pinned key
|
||||
// with no manifest is neither offered nor counted.
|
||||
cache.pin({masters[0].pk(), ephemeral.pk()});
|
||||
BEAST_EXPECT(cache.getTier(masters[1].pk()) == ManifestSource::ledger);
|
||||
offered.clear();
|
||||
cache.for_each_gossip_manifest(
|
||||
[&](std::size_t n) { reserved = n; },
|
||||
[&](Manifest const& m) { offered.push_back(m.masterKey); });
|
||||
|
||||
BEAST_EXPECT(reserved == 5);
|
||||
BEAST_EXPECT(offered.size() == 4);
|
||||
BEAST_EXPECT(reserved == 1);
|
||||
BEAST_EXPECT(offered.size() == 1);
|
||||
}
|
||||
|
||||
void
|
||||
testLedgerOutranksGossip(FeatureBitset features)
|
||||
{
|
||||
testcase("ledger outranks gossip");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, makeConfig(), features};
|
||||
|
||||
auto const master = Account("master", KeyType::ed25519);
|
||||
auto const eph1 = Account("eph1", KeyType::ed25519);
|
||||
auto const eph2 = Account("eph2", KeyType::ed25519);
|
||||
env.fund(XRP(1000), master);
|
||||
env.close();
|
||||
|
||||
submit(env, makeManifest(master, eph1, 1));
|
||||
env.close();
|
||||
|
||||
auto const fromPeer = [](std::string const& s) {
|
||||
auto mo = deserializeManifest(s);
|
||||
return std::move(*mo);
|
||||
};
|
||||
auto const gossip = ManifestSource::gossip;
|
||||
|
||||
{
|
||||
// Gossip repeating what the ledger holds is stale, and it is the
|
||||
// ledger's copy that is kept, as a lookup cache entry.
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{env.journal, clock};
|
||||
cache.noteValidation(eph1.pk());
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(
|
||||
fromPeer(makeManifest(master, eph1, 1)),
|
||||
gossip,
|
||||
env.closed().get()) == ManifestDisposition::stale);
|
||||
BEAST_EXPECT(cache.getTier(master.pk()) == ManifestSource::ledger);
|
||||
BEAST_EXPECT(cache.getMasterKey(eph1.pk()) == master.pk());
|
||||
}
|
||||
|
||||
submit(
|
||||
env,
|
||||
makeManifest(
|
||||
master, eph1, std::numeric_limits<std::uint32_t>::max()));
|
||||
env.close();
|
||||
|
||||
{
|
||||
// Revoked on-ledger, a master key cannot come back by gossip with
|
||||
// a fresh ephemeral key, even one seen validating, and even in a
|
||||
// cache that never held it.
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{env.journal, clock};
|
||||
cache.noteValidation(eph2.pk());
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(
|
||||
fromPeer(makeManifest(master, eph2, 5)),
|
||||
gossip,
|
||||
env.closed().get()) == ManifestDisposition::stale);
|
||||
BEAST_EXPECT(cache.revoked(master.pk()));
|
||||
BEAST_EXPECT(cache.getTier(master.pk()) == ManifestSource::ledger);
|
||||
BEAST_EXPECT(cache.getMasterKey(eph2.pk()) == eph2.pk());
|
||||
}
|
||||
|
||||
{
|
||||
// A cache that took the gossip without a ledger to check it
|
||||
// against learns of the revocation at its next refresh, though it
|
||||
// never misses on the ephemeral key.
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{env.journal, clock};
|
||||
cache.noteValidation(eph2.pk());
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(
|
||||
fromPeer(makeManifest(master, eph2, 5)), gossip) ==
|
||||
ManifestDisposition::accepted);
|
||||
BEAST_EXPECT(cache.getMasterKey(eph2.pk()) == master.pk());
|
||||
BEAST_EXPECT(cache.applyLedger(*env.closed()) == 1);
|
||||
BEAST_EXPECT(cache.revoked(master.pk()));
|
||||
BEAST_EXPECT(cache.getMasterKey(eph2.pk()) == eph2.pk());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDirectory(FeatureBitset features)
|
||||
{
|
||||
testcase("manifest directory");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, makeConfig(), features};
|
||||
auto const revoke = std::numeric_limits<std::uint32_t>::max();
|
||||
|
||||
std::vector<Account> masters;
|
||||
std::vector<Account> ephs;
|
||||
for (int i = 0; i < 40; ++i)
|
||||
{
|
||||
masters.emplace_back("dm" + std::to_string(i), KeyType::ed25519);
|
||||
ephs.emplace_back("de" + std::to_string(i), KeyType::ed25519);
|
||||
env.fund(XRP(1000), masters.back());
|
||||
}
|
||||
env.close();
|
||||
|
||||
for (int i = 0; i < 40; ++i)
|
||||
BEAST_EXPECT(
|
||||
engineResult(
|
||||
submit(env, makeManifest(masters[i], ephs[i], 1))) ==
|
||||
"tesSUCCESS");
|
||||
env.close();
|
||||
|
||||
auto const listed = [&]() {
|
||||
std::map<PublicKey, std::uint32_t> out;
|
||||
forEachLedgerManifest(
|
||||
*env.closed(), [&](std::shared_ptr<SLE const> const& sle) {
|
||||
out.emplace(
|
||||
PublicKey{makeSlice(sle->getFieldVL(sfPublicKey))},
|
||||
sle->getFieldU32(sfSequence));
|
||||
});
|
||||
return out;
|
||||
};
|
||||
|
||||
auto const pageOf = [&](Account const& a) {
|
||||
auto const sle = env.le(keylet::manifest(a.pk()));
|
||||
return sle && sle->isFieldPresent(sfOwnerNode)
|
||||
? std::optional<std::uint64_t>{sle->getFieldU64(sfOwnerNode)}
|
||||
: std::nullopt;
|
||||
};
|
||||
|
||||
// Each master object listed once, on the page it records; the
|
||||
// ephemeral copies are not listed.
|
||||
{
|
||||
auto const all = listed();
|
||||
BEAST_EXPECT(all.size() == 40);
|
||||
for (auto const& m : masters)
|
||||
BEAST_EXPECT(all.count(m.pk()) && all.at(m.pk()) == 1);
|
||||
for (auto const& e : ephs)
|
||||
{
|
||||
auto const sle = env.le(keylet::manifest(e.pk()));
|
||||
BEAST_EXPECT(sle && !sle->isFieldPresent(sfOwnerNode));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::optional<std::uint64_t>> pages;
|
||||
for (auto const& m : masters)
|
||||
{
|
||||
pages.push_back(pageOf(m));
|
||||
BEAST_EXPECT(pages.back());
|
||||
}
|
||||
|
||||
// Rotated to a new ephemeral key...
|
||||
std::vector<Account> rotated;
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
rotated.emplace_back("dr" + std::to_string(i), KeyType::ed25519);
|
||||
BEAST_EXPECT(
|
||||
engineResult(
|
||||
submit(env, makeManifest(masters[i], rotated.back(), 2))) ==
|
||||
"tesSUCCESS");
|
||||
}
|
||||
env.close();
|
||||
|
||||
// ...revoked...
|
||||
for (int i = 10; i < 15; ++i)
|
||||
BEAST_EXPECT(
|
||||
engineResult(
|
||||
submit(env, makeManifest(masters[i], ephs[i], revoke))) ==
|
||||
"tesSUCCESS");
|
||||
env.close();
|
||||
|
||||
// ...or replaced unsuccessfully, which changes nothing.
|
||||
BEAST_EXPECT(
|
||||
engineResult(
|
||||
submit(env, makeManifest(masters[0], rotated[0], 2))) ==
|
||||
"tefPAST_MANIFEST_SEQ");
|
||||
env.close();
|
||||
|
||||
// Still each master key once, at its latest, on the same page.
|
||||
auto const all = listed();
|
||||
BEAST_EXPECT(all.size() == 40);
|
||||
for (int i = 0; i < 40; ++i)
|
||||
{
|
||||
auto const expected = i < 10 ? 2u : i < 15 ? revoke : 1u;
|
||||
BEAST_EXPECT(
|
||||
all.count(masters[i].pk()) &&
|
||||
all.at(masters[i].pk()) == expected);
|
||||
BEAST_EXPECT(pageOf(masters[i]) == pages[i]);
|
||||
}
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::manifest(ephs[i].pk())));
|
||||
BEAST_EXPECT(env.le(keylet::manifest(rotated[i].pk())));
|
||||
}
|
||||
for (int i = 10; i < 15; ++i)
|
||||
BEAST_EXPECT(!env.le(keylet::manifest(ephs[i].pk())));
|
||||
|
||||
// Read from the directory alone, with no key to start from.
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{env.journal, clock};
|
||||
BEAST_EXPECT(cache.applyLedgerDirectory(*env.closed()) == 40);
|
||||
BEAST_EXPECT(cache.getMasterKey(rotated[0].pk()) == masters[0].pk());
|
||||
BEAST_EXPECT(cache.getMasterKey(ephs[0].pk()) == ephs[0].pk());
|
||||
BEAST_EXPECT(cache.getMasterKey(ephs[20].pk()) == masters[20].pk());
|
||||
BEAST_EXPECT(cache.revoked(masters[10].pk()));
|
||||
BEAST_EXPECT(cache.applyLedgerDirectory(*env.closed()) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -798,21 +1253,22 @@ public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
// RH TODO: re-add these when the amendment is ready for supported::yes
|
||||
/*
|
||||
using namespace test::jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testSubmission(sa);
|
||||
testUpdate(sa);
|
||||
testRevocation(sa);
|
||||
testRetrieval(sa);
|
||||
testSigningKeyRetrieval(sa);
|
||||
testMalformed(sa);
|
||||
testEnvelopeRejections(sa);
|
||||
testCorruptLedger(sa);
|
||||
testGossipSelection(sa);
|
||||
testDisabled(sa);
|
||||
*/
|
||||
using namespace test::jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testSubmission(sa);
|
||||
testUpdate(sa);
|
||||
testRevocation(sa);
|
||||
testRetrieval(sa);
|
||||
testSigningKeyRetrieval(sa);
|
||||
testMalformed(sa);
|
||||
testAccountDelete(sa);
|
||||
testEnvelopeRejections(sa);
|
||||
testCanonicalFee(sa);
|
||||
testCorruptLedger(sa);
|
||||
testGossipSelection(sa);
|
||||
testLedgerOutranksGossip(sa);
|
||||
testDirectory(sa);
|
||||
testDisabled(sa);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -5055,7 +5055,15 @@ public:
|
||||
run() override
|
||||
{
|
||||
using namespace test::jtx;
|
||||
FeatureBitset const all{supported_amendments() - featureXahauGenesis};
|
||||
// featureRNG is excluded: makeConfig() runs as a validator whenever
|
||||
// voting settings are given, and a validator puts a ttENTROPY with a
|
||||
// fresh random commitment into every ledger. That makes the
|
||||
// transaction set hash, and so the canonical order in which
|
||||
// different accounts' transactions apply when the ledger closes,
|
||||
// differ from run to run, which breaks tests that assert balances
|
||||
// depending on that order (e.g. unexpected balance change).
|
||||
FeatureBitset const all{
|
||||
supported_amendments() - featureXahauGenesis - featureRNG};
|
||||
testQueueSeq(all);
|
||||
testQueueTicket(all);
|
||||
testTecResult(all);
|
||||
@@ -5082,7 +5090,15 @@ public:
|
||||
runMetaInfo()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
FeatureBitset const all{supported_amendments() - featureXahauGenesis};
|
||||
// featureRNG is excluded: makeConfig() runs as a validator whenever
|
||||
// voting settings are given, and a validator puts a ttENTROPY with a
|
||||
// fresh random commitment into every ledger. That makes the
|
||||
// transaction set hash, and so the canonical order in which
|
||||
// different accounts' transactions apply when the ledger closes,
|
||||
// differ from run to run, which breaks tests that assert balances
|
||||
// depending on that order (e.g. unexpected balance change).
|
||||
FeatureBitset const all{
|
||||
supported_amendments() - featureXahauGenesis - featureRNG};
|
||||
testAcctInQueueButEmpty(all);
|
||||
testRPC(all);
|
||||
testExpirationReplacement(all);
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -4676,11 +4677,12 @@ struct XahauGenesis_test : public beast::unit_test::suite
|
||||
// validate account fields
|
||||
STAmount const postUser = preUser + netReward;
|
||||
bool const has240819 = env.current()->rules().enabled(fix240819);
|
||||
bool const has20261005 = env.current()->rules().enabled(fix20261005);
|
||||
BEAST_EXPECT(expectAccountFields(
|
||||
env,
|
||||
user,
|
||||
preLedger,
|
||||
has240819 ? preLedger : preLedger + 1,
|
||||
(has240819 && !has20261005) ? preLedger : preLedger + 1,
|
||||
has240819 ? (preUser - feesXRP) : postUser,
|
||||
preTime));
|
||||
}
|
||||
@@ -5567,8 +5569,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
|
||||
// calculate rewards
|
||||
bool const hasFix = env.current()->rules().enabled(fix240819) &&
|
||||
env.current()->rules().enabled(fix240911);
|
||||
bool const hasFix = env.current()->rules().enabled(fix240819);
|
||||
STAmount const netReward =
|
||||
rewardUserAmount(*acctSle, preLedger, rateDrops);
|
||||
BEAST_EXPECT(netReward == (hasFix ? XRP(3.329999) : XRP(3.329999)));
|
||||
@@ -5659,8 +5660,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
|
||||
// calculate rewards
|
||||
bool const hasFix = env.current()->rules().enabled(fix240819) &&
|
||||
env.current()->rules().enabled(fix240911);
|
||||
bool const hasFix = env.current()->rules().enabled(fix240819);
|
||||
STAmount const netReward =
|
||||
rewardUserAmount(*acctSle, preLedger, rateDrops);
|
||||
BEAST_EXPECT(netReward == (hasFix ? XRP(3.479999) : XRP(6.663333)));
|
||||
@@ -5678,9 +5678,196 @@ struct XahauGenesis_test : public beast::unit_test::suite
|
||||
postUser == (hasFix ? XRP(2002.479999) : XRP(2005.663333)));
|
||||
}
|
||||
|
||||
void
|
||||
test20261005(FeatureBitset features)
|
||||
{
|
||||
using namespace jtx;
|
||||
using namespace std::chrono_literals;
|
||||
testcase("test fix20261005");
|
||||
|
||||
bool const has240819 = features[fix240819];
|
||||
bool const has20261005 = features[fix20261005];
|
||||
bool const nofix = !(has240819 && has20261005);
|
||||
|
||||
Env env{
|
||||
*this, makeNetworkConfig(21337), features - featureXahauGenesis};
|
||||
|
||||
STAmount const feesXRP = XRP(1);
|
||||
|
||||
Account const user = Account("user");
|
||||
env.fund(XRP(200), user);
|
||||
env.close();
|
||||
|
||||
// setup governance
|
||||
Account const alice = Account("alice");
|
||||
Account const bob = Account("bob");
|
||||
Account const carol = Account("carol");
|
||||
Account const david = Account("david");
|
||||
Account const edward = Account("edward");
|
||||
|
||||
env.fund(XRP(10000), alice, bob, carol, david, edward);
|
||||
env.close();
|
||||
|
||||
std::vector<AccountID> initial_members_ids{
|
||||
alice.id(), bob.id(), carol.id(), david.id(), edward.id()};
|
||||
|
||||
setupGov(env, initial_members_ids);
|
||||
|
||||
{
|
||||
std::vector<uint8_t> vote_data{
|
||||
0x00U, 0x80U, 0xC6U, 0xA4U, 0x7EU, 0x8DU, 0x03U, 0x55U};
|
||||
|
||||
updateTopic(
|
||||
env, alice, bob, carol, david, edward, 'R', 'D', vote_data);
|
||||
}
|
||||
|
||||
BEAST_EXPECT(hasUNLReport(env) == false);
|
||||
|
||||
env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
env(noop(user),
|
||||
fee(env.balance(user) - drops(500'000)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(user) == drops(500'000));
|
||||
BEAST_EXPECT(env.balance(user) < XRP(1));
|
||||
|
||||
std::uint32_t const drainMarker =
|
||||
env.le(keylet::account(user))->getFieldU32(sfRewardLgrLast);
|
||||
|
||||
for (int i = 0; i < 20; ++i)
|
||||
env.close(10s);
|
||||
|
||||
BEAST_EXPECT(env.balance(user) < XRP(1));
|
||||
|
||||
env(pay(alice, user, XRP(1000)));
|
||||
env.close();
|
||||
|
||||
std::uint32_t const fundMarker =
|
||||
env.le(keylet::account(user))->getFieldU32(sfRewardLgrLast);
|
||||
|
||||
bool const markerFrozen = has240819 && !has20261005;
|
||||
if (markerFrozen)
|
||||
BEAST_EXPECT(fundMarker == drainMarker);
|
||||
else
|
||||
BEAST_EXPECT(fundMarker > drainMarker);
|
||||
|
||||
STAmount const preUser = env.balance(user);
|
||||
|
||||
env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
env.close();
|
||||
|
||||
STAmount const postUser = env.balance(user);
|
||||
|
||||
if (nofix)
|
||||
BEAST_EXPECT(postUser > preUser + XRP(1));
|
||||
else
|
||||
BEAST_EXPECT(postUser < preUser + XRP(1));
|
||||
}
|
||||
|
||||
void
|
||||
testRewardAccumulatorOverflow(FeatureBitset features)
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("reward accumulator overflow");
|
||||
|
||||
bool const has20261005 = features[fix20261005];
|
||||
|
||||
Env env{
|
||||
*this, makeNetworkConfig(21337), features - featureXahauGenesis};
|
||||
|
||||
STAmount const feesXRP = XRP(1);
|
||||
|
||||
Account const user = Account("user");
|
||||
env.fund(XRP(10000), user);
|
||||
env.close();
|
||||
|
||||
// setup governance
|
||||
Account const alice = Account("alice");
|
||||
Account const bob = Account("bob");
|
||||
Account const carol = Account("carol");
|
||||
Account const david = Account("david");
|
||||
Account const edward = Account("edward");
|
||||
|
||||
env.fund(XRP(10000), alice, bob, carol, david, edward);
|
||||
env.close();
|
||||
|
||||
std::vector<AccountID> initial_members_ids{
|
||||
alice.id(), bob.id(), carol.id(), david.id(), edward.id()};
|
||||
|
||||
setupGov(env, initial_members_ids);
|
||||
|
||||
{
|
||||
std::vector<uint8_t> vote_data{
|
||||
0x00U, 0x80U, 0xC6U, 0xA4U, 0x7EU, 0x8DU, 0x03U, 0x55U};
|
||||
|
||||
updateTopic(
|
||||
env, alice, bob, carol, david, edward, 'R', 'D', vote_data);
|
||||
}
|
||||
|
||||
// opt in so the account carries the reward fields
|
||||
env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS));
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
auto const preSle = env.le(keylet::account(user));
|
||||
BEAST_REQUIRE(preSle);
|
||||
BEAST_REQUIRE(preSle->isFieldPresent(sfRewardLgrLast));
|
||||
BEAST_REQUIRE(preSle->isFieldPresent(sfRewardAccumulator));
|
||||
|
||||
std::uint32_t const preMarker = preSle->getFieldU32(sfRewardLgrLast);
|
||||
BEAST_EXPECT(preMarker < env.current()->seq());
|
||||
|
||||
constexpr std::uint64_t maxAccum =
|
||||
std::numeric_limits<std::uint64_t>::max();
|
||||
|
||||
// Push the accumulator to the brink in the open ledger so that any
|
||||
// non-zero bal * lgrElapsed wraps around in finishApply.
|
||||
env.app().openLedger().modify(
|
||||
[&](OpenView& view, beast::Journal) -> bool {
|
||||
auto const sle = view.read(keylet::account(user));
|
||||
if (!sle)
|
||||
return false;
|
||||
auto replacement = std::make_shared<SLE>(*sle);
|
||||
replacement->setFieldU64(sfRewardAccumulator, maxAccum);
|
||||
view.rawReplace(replacement);
|
||||
return true;
|
||||
});
|
||||
|
||||
BEAST_EXPECT(
|
||||
env.le(keylet::account(user))->getFieldU64(sfRewardAccumulator) ==
|
||||
maxAccum);
|
||||
|
||||
// Any balance change on user runs the reward block in finishApply.
|
||||
env(pay(alice, user, XRP(1)), ter(tesSUCCESS));
|
||||
|
||||
// Check the open ledger before close() rebuilds it without the
|
||||
// injected accumulator.
|
||||
auto const postSle = env.le(keylet::account(user));
|
||||
BEAST_REQUIRE(postSle);
|
||||
|
||||
// the overflowing contribution is never added
|
||||
BEAST_EXPECT(postSle->getFieldU64(sfRewardAccumulator) == maxAccum);
|
||||
|
||||
// with the fix the marker advances so the span can't be reused;
|
||||
// without it the marker is left behind
|
||||
if (has20261005)
|
||||
BEAST_EXPECT(
|
||||
postSle->getFieldU32(sfRewardLgrLast) == env.current()->seq());
|
||||
else
|
||||
BEAST_EXPECT(postSle->getFieldU32(sfRewardLgrLast) == preMarker);
|
||||
|
||||
env.close();
|
||||
}
|
||||
|
||||
void
|
||||
testRewardHookWithFeats(FeatureBitset features)
|
||||
{
|
||||
test20261005(features);
|
||||
testRewardAccumulatorOverflow(features);
|
||||
testLastCloseTime(features);
|
||||
testInvalidRate0(features);
|
||||
testInvalidRateGreater1(features);
|
||||
@@ -5723,7 +5910,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
|
||||
testGovernHookWithFeats(sa);
|
||||
testRewardHookWithFeats(sa);
|
||||
testRewardHookWithFeats(sa - fix240819);
|
||||
testRewardHookWithFeats(sa - fix240819 - fix240911);
|
||||
testRewardHookWithFeats(sa - fix20261005);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -529,11 +529,10 @@ public:
|
||||
|
||||
std::vector<JobName> jobs;
|
||||
{
|
||||
auto const& jobTypes = JobTypes::instance();
|
||||
jobs.reserve(jobTypes.size());
|
||||
for (auto const& job : jobTypes)
|
||||
{
|
||||
jobs.emplace_back(job.first, job.second.name());
|
||||
jobs.emplace_back(job.type, std::string(job.name));
|
||||
}
|
||||
}
|
||||
std::shuffle(jobs.begin(), jobs.end(), default_prng());
|
||||
@@ -865,14 +864,10 @@ public:
|
||||
JobType jobType;
|
||||
std::string jobTypeName;
|
||||
{
|
||||
auto const& jobTypes = JobTypes::instance();
|
||||
auto index = rand_int<std::size_t>(0, jobTypes.size() - 1);
|
||||
|
||||
std::uniform_int_distribution<> dis(0, jobTypes.size() - 1);
|
||||
auto iter{jobTypes.begin()};
|
||||
std::advance(iter, dis(default_prng()));
|
||||
|
||||
jobType = iter->second.type();
|
||||
jobTypeName = iter->second.name();
|
||||
jobType = jobTypes[index].type;
|
||||
jobTypeName = jobTypes[index].name;
|
||||
}
|
||||
|
||||
// Say there's one worker thread.
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2026 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/ReaderPreferringSharedMutex.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class ReaderPreferringSharedMutex_test : public beast::unit_test::suite
|
||||
{
|
||||
void
|
||||
testExclusiveAndShared()
|
||||
{
|
||||
testcase("exclusive and shared locking");
|
||||
|
||||
ReaderPreferringSharedMutex mutex;
|
||||
|
||||
BEAST_EXPECT(mutex.try_lock());
|
||||
BEAST_EXPECT(!mutex.try_lock());
|
||||
BEAST_EXPECT(!mutex.try_lock_shared());
|
||||
mutex.unlock();
|
||||
|
||||
mutex.lock_shared();
|
||||
BEAST_EXPECT(mutex.try_lock_shared());
|
||||
BEAST_EXPECT(!mutex.try_lock());
|
||||
mutex.unlock_shared();
|
||||
mutex.unlock_shared();
|
||||
|
||||
{
|
||||
std::shared_lock shared{mutex};
|
||||
BEAST_EXPECT(!mutex.try_lock());
|
||||
BEAST_EXPECT(mutex.try_lock_shared());
|
||||
mutex.unlock_shared();
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock exclusive{mutex};
|
||||
BEAST_EXPECT(!mutex.try_lock());
|
||||
BEAST_EXPECT(!mutex.try_lock_shared());
|
||||
}
|
||||
|
||||
mutex.lock();
|
||||
BEAST_EXPECT(!mutex.try_lock_shared());
|
||||
mutex.unlock();
|
||||
BEAST_EXPECT(mutex.try_lock_shared());
|
||||
mutex.unlock_shared();
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testExclusiveAndShared();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ReaderPreferringSharedMutex, ripple_basics, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -59,6 +59,17 @@ public:
|
||||
testUnHexFailure("123X");
|
||||
testUnHexFailure("V");
|
||||
testUnHexFailure("XAH");
|
||||
|
||||
// Bytes >= 0x80 (negative when char is signed) must be rejected, not
|
||||
// used to index the lookup table out of bounds
|
||||
for (int b = 0x80; b <= 0xff; ++b)
|
||||
{
|
||||
std::string const c(1, static_cast<char>(b));
|
||||
testUnHexFailure(c);
|
||||
testUnHexFailure("0" + c);
|
||||
testUnHexFailure(c + "0");
|
||||
testUnHexFailure("00" + c + "00");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -462,11 +462,102 @@ class base58_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDecodeInvalidChars()
|
||||
{
|
||||
testcase("decode_invalid_chars");
|
||||
|
||||
// Every byte outside the base58 alphabet, including bytes >= 0x80
|
||||
// (negative when char is signed), must be rejected at any position.
|
||||
// The fast decoder reads the leading `size % 10` characters and the
|
||||
// remaining characters in two separate loops; varying the length and
|
||||
// the position of the bad byte exercises both.
|
||||
constexpr std::string_view alphabet =
|
||||
"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz";
|
||||
std::array<bool, 256> inAlphabet{};
|
||||
for (unsigned char const c : alphabet)
|
||||
inAlphabet[c] = true;
|
||||
|
||||
auto const invalidCode =
|
||||
make_error_code(TokenCodecErrc::invalidEncodingChar);
|
||||
|
||||
// 52 is the longest input the fast decoder accepts
|
||||
for (std::size_t len = 1; len <= 52; ++len)
|
||||
{
|
||||
std::string base;
|
||||
for (std::size_t i = 0; i < len; ++i)
|
||||
base.push_back(alphabet[(i * 7 + len) % alphabet.size()]);
|
||||
|
||||
for (std::size_t pos = 0; pos < len; ++pos)
|
||||
{
|
||||
for (int b = 0; b < 256; ++b)
|
||||
{
|
||||
if (inAlphabet[b])
|
||||
continue;
|
||||
|
||||
std::string s = base;
|
||||
s[pos] = static_cast<char>(b);
|
||||
|
||||
std::array<std::uint8_t, 64> outBuf;
|
||||
auto const r = b58_fast::detail::b58_to_b256_be(s, outBuf);
|
||||
BEAST_EXPECT(!r && r.error() == invalidCode);
|
||||
BEAST_EXPECT(b58_ref::detail::decodeBase58(s).empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Same through the token API, using a valid AccountID as the base
|
||||
constexpr TokenType tokType = TokenType::AccountID;
|
||||
std::string const addr = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
|
||||
BEAST_EXPECT(!decodeBase58Token(addr, tokType).empty());
|
||||
for (std::size_t pos = 0; pos < addr.size(); ++pos)
|
||||
{
|
||||
for (int b = 0; b < 256; ++b)
|
||||
{
|
||||
if (inAlphabet[b])
|
||||
continue;
|
||||
|
||||
std::string s = addr;
|
||||
s[pos] = static_cast<char>(b);
|
||||
|
||||
std::array<std::uint8_t, 64> outBuf;
|
||||
auto const r = b58_fast::decodeBase58Token(tokType, s, outBuf);
|
||||
BEAST_EXPECT(!r && r.error() == invalidCode);
|
||||
BEAST_EXPECT(b58_ref::decodeBase58Token(s, tokType).empty());
|
||||
BEAST_EXPECT(decodeBase58Token(s, tokType).empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDecodeMaxValue()
|
||||
{
|
||||
testcase("decode_max_value");
|
||||
|
||||
// The largest value of every accepted length ('z' is digit 57) must
|
||||
// decode identically in both implementations. At 52 chars this is the
|
||||
// widest intermediate the fast decoder's bigint ever has to hold.
|
||||
for (std::size_t len = 1; len <= 52; ++len)
|
||||
{
|
||||
std::string const s(len, 'z');
|
||||
|
||||
std::array<std::uint8_t, 64> outBuf;
|
||||
auto const r = b58_fast::detail::b58_to_b256_be(s, outBuf);
|
||||
std::string const ref = b58_ref::detail::decodeBase58(s);
|
||||
if (!BEAST_EXPECT(r))
|
||||
continue;
|
||||
BEAST_EXPECT(
|
||||
std::string(r.value().begin(), r.value().end()) == ref);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testMultiprecision();
|
||||
testFastMatchesRef();
|
||||
testDecodeInvalidChars();
|
||||
testDecodeMaxValue();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -307,11 +307,9 @@ struct base_uint_test : beast::unit_test::suite
|
||||
std::string_view sView(str.data(), str.size());
|
||||
[[maybe_unused]] test96 t96(sView);
|
||||
}
|
||||
catch (std::invalid_argument const& e)
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
e.what() ==
|
||||
std::string("invalid length for hex string"));
|
||||
BEAST_EXPECT(e.what() == std::string("invalid hex string"));
|
||||
caught = true;
|
||||
}
|
||||
BEAST_EXPECT(caught);
|
||||
@@ -327,10 +325,9 @@ struct base_uint_test : beast::unit_test::suite
|
||||
std::string_view sView(str.data(), str.size());
|
||||
[[maybe_unused]] test96 t96(sView);
|
||||
}
|
||||
catch (std::range_error const& e)
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
e.what() == std::string("invalid hex character"));
|
||||
BEAST_EXPECT(e.what() == std::string("invalid hex string"));
|
||||
caught = true;
|
||||
}
|
||||
BEAST_EXPECT(caught);
|
||||
|
||||
@@ -324,6 +324,35 @@ public:
|
||||
BEAST_EXPECT(!is_loopback(ep));
|
||||
BEAST_EXPECTS(to_string(ep) == "fd00::1", to_string(ep));
|
||||
|
||||
// a link-local IPv6 is private
|
||||
ep = Endpoint(boost::asio::ip::make_address_v6("fe80::1"));
|
||||
BEAST_EXPECT(!is_public(ep));
|
||||
BEAST_EXPECT(is_private(ep));
|
||||
BEAST_EXPECT(!is_loopback(ep));
|
||||
|
||||
// the IPv6 loopback is private
|
||||
ep = Endpoint(boost::asio::ip::make_address_v6("::1"));
|
||||
BEAST_EXPECT(!is_public(ep));
|
||||
BEAST_EXPECT(is_private(ep));
|
||||
BEAST_EXPECT(is_loopback(ep));
|
||||
|
||||
// a global unicast IPv6 is public
|
||||
ep = Endpoint(boost::asio::ip::make_address_v6("2001:db8::1"));
|
||||
BEAST_EXPECT(!is_unspecified(ep));
|
||||
BEAST_EXPECT(is_public(ep));
|
||||
BEAST_EXPECT(!is_private(ep));
|
||||
BEAST_EXPECT(!is_multicast(ep));
|
||||
|
||||
// multicast and unspecified IPv6 are neither public nor private
|
||||
ep = Endpoint(boost::asio::ip::make_address_v6("ff02::1"));
|
||||
BEAST_EXPECT(!is_public(ep));
|
||||
BEAST_EXPECT(!is_private(ep));
|
||||
BEAST_EXPECT(is_multicast(ep));
|
||||
ep = Endpoint(AddressV6{});
|
||||
BEAST_EXPECT(is_unspecified(ep));
|
||||
BEAST_EXPECT(!is_public(ep));
|
||||
BEAST_EXPECT(!is_private(ep));
|
||||
|
||||
{
|
||||
ep = Endpoint::from_string("192.0.2.112");
|
||||
BEAST_EXPECT(!is_unspecified(ep));
|
||||
|
||||
@@ -1052,6 +1052,67 @@ public:
|
||||
BEAST_EXPECT(sim.synchronized());
|
||||
}
|
||||
|
||||
void
|
||||
testForcedInclusion()
|
||||
{
|
||||
using namespace csf;
|
||||
using namespace std::chrono;
|
||||
testcase("forced inclusion");
|
||||
|
||||
// Transaction n * 1000 + k belongs to peer n - 1. Each one is placed
|
||||
// only in a single peer's open set and never relayed, so without
|
||||
// forced inclusion it has one vote in five and stays out.
|
||||
auto const owner = [](Tx const& tx) -> std::optional<PeerID> {
|
||||
if (tx.id() < 1000)
|
||||
return std::nullopt;
|
||||
return PeerID{tx.id() / 1000 - 1};
|
||||
};
|
||||
|
||||
auto const run = [&](bool withHook) {
|
||||
ConsensusParms const parms{};
|
||||
Sim sim;
|
||||
PeerGroup peers = sim.createGroup(5);
|
||||
peers.trustAndConnect(
|
||||
peers, round<milliseconds>(0.2 * parms.ledgerGRANULARITY));
|
||||
|
||||
for (Peer* p : peers)
|
||||
{
|
||||
if (withHook)
|
||||
p->txOwner = owner;
|
||||
|
||||
std::uint32_t const base =
|
||||
(static_cast<std::uint32_t>(p->id) + 1) * 1000;
|
||||
p->openTxs.insert(Tx{base + 1});
|
||||
if (p->id == PeerID{4})
|
||||
{
|
||||
// more of its own than it may force
|
||||
p->openTxs.insert(Tx{base + 2});
|
||||
p->openTxs.insert(Tx{base + 3});
|
||||
// one that names another owner, who does not hold it
|
||||
p->openTxs.insert(Tx{1999});
|
||||
}
|
||||
}
|
||||
|
||||
sim.run(1);
|
||||
BEAST_EXPECT(sim.synchronized());
|
||||
return peers[0]->lastClosedLedger.txs();
|
||||
};
|
||||
|
||||
{
|
||||
auto const txs = run(false);
|
||||
BEAST_EXPECT(txs.empty());
|
||||
}
|
||||
|
||||
{
|
||||
auto const txs = run(true);
|
||||
// every owner's own transaction, and only the lowest of the
|
||||
// several peer 4 put in its position
|
||||
TxSetType const expected{
|
||||
Tx{1001}, Tx{2001}, Tx{3001}, Tx{4001}, Tx{5001}};
|
||||
BEAST_EXPECTS(txs == expected, to_string(txs));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -1068,6 +1129,7 @@ public:
|
||||
testHubNetwork();
|
||||
testPreferredByBranch();
|
||||
testPauseForLaggards();
|
||||
testForcedInclusion();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1203,6 +1203,96 @@ class UNLReportVoteNewValidator_test : public beast::unit_test::suite
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Regression test: UNLReport proposals must not be gated on the N-UNL local
|
||||
* reliability threshold.
|
||||
*
|
||||
* The local node (validator 0) validates exactly `myVals` of the 256 scored
|
||||
* ledgers, validator 1 validates none (so it is an N-UNL disable candidate and
|
||||
* absent from the report), every other validator validates all of them.
|
||||
*
|
||||
* == myVals <= 128: not active ourselves -> no UNLReport, no UNLModify
|
||||
* == 129 .. 229: UNLReport proposed, not reliable enough to vote on N-UNL
|
||||
* == >= 230: UNLReport and one UNLModify (230 used to fall into the
|
||||
* "Too many!" branch and fail)
|
||||
*/
|
||||
class UNLReportVoteLocalReliability_test : public beast::unit_test::suite
|
||||
{
|
||||
void
|
||||
testDoVoting()
|
||||
{
|
||||
testcase("Do Voting with local reliability below N-UNL threshold");
|
||||
|
||||
struct Case
|
||||
{
|
||||
std::uint32_t myVals;
|
||||
bool expectReport;
|
||||
bool expectModify;
|
||||
};
|
||||
|
||||
std::array<Case, 6> const cases = {{
|
||||
{128, false, false},
|
||||
{129, true, false},
|
||||
{229, true, false},
|
||||
{230, true, true},
|
||||
{231, true, true},
|
||||
{256, true, true},
|
||||
}};
|
||||
|
||||
for (bool const withVLImport : {true, false})
|
||||
{
|
||||
for (auto const& c : cases)
|
||||
{
|
||||
std::uint32_t const numNodes = 20;
|
||||
URNetworkHistory history = {
|
||||
*this, {numNodes, 0, false, false, withVLImport, {}}};
|
||||
BEAST_EXPECT(history.goodHistory);
|
||||
if (!history.goodHistory)
|
||||
continue;
|
||||
|
||||
// scored ledgers are [lastSeq - 256, lastSeq - 1]
|
||||
auto const lastSeq = history.lastLedger()->seq();
|
||||
history.walkHistoryAndAddValidations(
|
||||
[&](std::shared_ptr<Ledger const> const& l,
|
||||
std::size_t idx) -> bool {
|
||||
if (idx == 1)
|
||||
return false;
|
||||
if (idx != 0)
|
||||
return true;
|
||||
return l->seq() + 256 < lastSeq + c.myVals;
|
||||
});
|
||||
|
||||
NegativeUNLVote vote(
|
||||
history.UNLNodeIDs[0],
|
||||
history.env.journal,
|
||||
history.env.app());
|
||||
auto txSet = std::make_shared<SHAMap>(
|
||||
SHAMapType::TRANSACTION, history.env.app().getNodeFamily());
|
||||
vote.doVoting(
|
||||
history.lastLedger(),
|
||||
history.UNLKeySet,
|
||||
history.validations,
|
||||
txSet);
|
||||
|
||||
std::size_t const expectReport =
|
||||
c.expectReport ? numNodes - 1 + (withVLImport ? 1 : 0) : 0;
|
||||
std::size_t const expectModify = c.expectModify ? 1 : 0;
|
||||
std::string const ctx = "myVals=" + std::to_string(c.myVals) +
|
||||
" withVLImport=" + std::to_string(withVLImport);
|
||||
|
||||
BEAST_EXPECTS(countUNLRTx(txSet) == expectReport, ctx);
|
||||
BEAST_EXPECTS(unl::countTx(txSet) == expectModify, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testDoVoting();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(UNLReport, ledger, ripple);
|
||||
BEAST_DEFINE_TESTSUITE(UNLReportNoAmendment, ledger, ripple);
|
||||
BEAST_DEFINE_TESTSUITE(UNLReportFork, consensus, ripple);
|
||||
@@ -1215,6 +1305,7 @@ BEAST_DEFINE_TESTSUITE_PRIO(
|
||||
ripple,
|
||||
1);
|
||||
BEAST_DEFINE_TESTSUITE(UNLReportVoteNewValidator, consensus, ripple);
|
||||
BEAST_DEFINE_TESTSUITE(UNLReportVoteLocalReliability, consensus, ripple);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
+152
-29
@@ -421,6 +421,72 @@ port_wss_admin
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testEarlyManifestSync()
|
||||
{
|
||||
testcase("early_manifest_sync");
|
||||
|
||||
// On unless turned off.
|
||||
{
|
||||
Config c;
|
||||
c.loadFromString("");
|
||||
BEAST_EXPECT(c.EARLY_MANIFEST_SYNC);
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, bool>> const values{
|
||||
{"0", false}, {"false", false}, {"1", true}, {"TRUE", true}};
|
||||
for (auto const& [value, expected] : values)
|
||||
{
|
||||
Config c;
|
||||
c.loadFromString("[early_manifest_sync]\n" + value + "\n");
|
||||
BEAST_EXPECT(c.EARLY_MANIFEST_SYNC == expected);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Config c;
|
||||
c.loadFromString("[early_manifest_sync]\nsometimes\n");
|
||||
fail();
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testUdpHighway()
|
||||
{
|
||||
testcase("udp_highway");
|
||||
|
||||
// The UDP Superhighway is opt-in: off unless turned on.
|
||||
{
|
||||
Config c;
|
||||
c.loadFromString("");
|
||||
BEAST_EXPECT(!c.UDP_HIGHWAY);
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, bool>> const values{
|
||||
{"0", false}, {"false", false}, {"1", true}, {"TRUE", true}};
|
||||
for (auto const& [value, expected] : values)
|
||||
{
|
||||
Config c;
|
||||
c.loadFromString("[udp_highway]\n" + value + "\n");
|
||||
BEAST_EXPECT(c.UDP_HIGHWAY == expected);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Config c;
|
||||
c.loadFromString("[udp_highway]\nsometimes\n");
|
||||
fail();
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testNetworkID()
|
||||
{
|
||||
@@ -1056,6 +1122,36 @@ trustthesevalidators.gov
|
||||
}
|
||||
}
|
||||
|
||||
// Standalone forces ledger_history to zero, but [node_db] type=rwdb
|
||||
// (the null node store) sizes its retained-ledger window from it and
|
||||
// SHAMapStoreImp throws on zero. cfg/rippled.cfg ships exactly this
|
||||
// configuration, and jtx never calls Config::setup, so pin it here.
|
||||
void
|
||||
testSetupStandaloneRwdb()
|
||||
{
|
||||
testcase("setup standalone rwdb keeps ledger_history");
|
||||
|
||||
for (std::string const type : {"rwdb", "NuDB"})
|
||||
{
|
||||
detail::RippledCfgGuard const cfg(
|
||||
*this,
|
||||
"testSetupStandaloneRwdb_" + type,
|
||||
"",
|
||||
"",
|
||||
true,
|
||||
"[node_db]\ntype=" + type + "\npath=main\n");
|
||||
Config config;
|
||||
config.setup(
|
||||
cfg.configFile(),
|
||||
/*bQuiet*/ true,
|
||||
/* bSilent */ true,
|
||||
/* bStandalone */ true);
|
||||
BEAST_EXPECT(config.standalone());
|
||||
BEAST_EXPECT(config.nullBackend() == (type == "rwdb"));
|
||||
BEAST_EXPECT(config.LEDGER_HISTORY == (type == "rwdb" ? 256 : 0));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testPort()
|
||||
{
|
||||
@@ -1102,23 +1198,24 @@ trustthesevalidators.gov
|
||||
Config cfg;
|
||||
/* NOTE: this string includes some explicit
|
||||
* space chars in order to verify proper trimming */
|
||||
std::string toLoad(R"(
|
||||
std::string toLoad(
|
||||
R"(
|
||||
[port_rpc])"
|
||||
"\x20"
|
||||
R"(
|
||||
"\x20"
|
||||
R"(
|
||||
# comment
|
||||
# indented comment
|
||||
)"
|
||||
"\x20\x20"
|
||||
R"(
|
||||
"\x20\x20"
|
||||
R"(
|
||||
[ips])"
|
||||
"\x20"
|
||||
R"(
|
||||
"\x20"
|
||||
R"(
|
||||
r.ripple.com 51235
|
||||
|
||||
[ips_fixed])"
|
||||
"\x20\x20"
|
||||
R"(
|
||||
"\x20\x20"
|
||||
R"(
|
||||
# COMMENT
|
||||
s1.ripple.com 51235
|
||||
s2.ripple.com 51235
|
||||
@@ -1144,23 +1241,24 @@ r.ripple.com 51235
|
||||
Config cfg;
|
||||
/* NOTE: this string includes some explicit
|
||||
* space chars in order to verify proper trimming */
|
||||
std::string toLoad(R"(
|
||||
std::string toLoad(
|
||||
R"(
|
||||
[port_rpc])"
|
||||
"\x20"
|
||||
R"(
|
||||
"\x20"
|
||||
R"(
|
||||
# comment
|
||||
# indented comment
|
||||
)"
|
||||
"\x20\x20"
|
||||
R"(
|
||||
"\x20\x20"
|
||||
R"(
|
||||
[ips])"
|
||||
"\x20"
|
||||
R"(
|
||||
"\x20"
|
||||
R"(
|
||||
r.ripple.com:51235
|
||||
|
||||
[ips_fixed])"
|
||||
"\x20\x20"
|
||||
R"(
|
||||
"\x20\x20"
|
||||
R"(
|
||||
# COMMENT
|
||||
s1.ripple.com:51235
|
||||
s2.ripple.com 51235
|
||||
@@ -1447,8 +1545,8 @@ r.ripple.com:51235
|
||||
}
|
||||
}
|
||||
|
||||
// Test 2: RWDB without online_delete NOT in standalone mode (should
|
||||
// throw)
|
||||
// Test 2: RWDB without online_delete NOT in standalone mode
|
||||
// (now allowed — SHAMapStoreImp defaults it to ledger_history)
|
||||
{
|
||||
Config c;
|
||||
std::string toLoad =
|
||||
@@ -1459,16 +1557,12 @@ r.ripple.com:51235
|
||||
try
|
||||
{
|
||||
c.loadFromString(toLoad);
|
||||
fail("Expected exception for RWDB without online_delete");
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
std::string(e.what()).find(
|
||||
"RWDB (in-memory backend) requires online_delete") !=
|
||||
std::string::npos);
|
||||
pass();
|
||||
}
|
||||
catch (std::runtime_error const&)
|
||||
{
|
||||
fail("Should not throw for RWDB without online_delete");
|
||||
}
|
||||
}
|
||||
|
||||
// Test 3: RWDB with online_delete NOT in standalone mode (should
|
||||
@@ -1613,7 +1707,33 @@ r.ripple.com:51235
|
||||
}
|
||||
}
|
||||
|
||||
// Test 5: pinned_type without online_delete (should throw)
|
||||
// Test 5: type=rwdb with pinned_type (should throw, even in
|
||||
// standalone). The rwdb node store is tree-only, so pinned data
|
||||
// would be accepted and then dropped.
|
||||
for (bool const standalone : {false, true})
|
||||
{
|
||||
Config c;
|
||||
std::string toLoad =
|
||||
"[node_db]\n"
|
||||
"type=rwdb\n"
|
||||
"online_delete=256\n"
|
||||
"pinned_type=NuDB\n"
|
||||
"pinned_path=pinned\n";
|
||||
c.setupControl(true, true, standalone);
|
||||
try
|
||||
{
|
||||
c.loadFromString(toLoad);
|
||||
fail("Expected exception for pinned_type with type=rwdb");
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
std::string(e.what()).find("type=rwdb") !=
|
||||
std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
// Test 6: pinned_type without online_delete (should throw)
|
||||
{
|
||||
Config c;
|
||||
std::string toLoad =
|
||||
@@ -1723,6 +1843,7 @@ r.ripple.com:51235
|
||||
testValidatorsFile();
|
||||
testSetup(false);
|
||||
testSetup(true);
|
||||
testSetupStandaloneRwdb();
|
||||
testPort();
|
||||
testZeroPort();
|
||||
testWhitespace();
|
||||
@@ -1734,6 +1855,8 @@ r.ripple.com:51235
|
||||
testDatabasePinnedValidation();
|
||||
testOverlay();
|
||||
testNetworkID();
|
||||
testEarlyManifestSync();
|
||||
testUdpHighway();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -120,23 +120,28 @@ public:
|
||||
|
||||
auto& jq = env.app().getJobQueue();
|
||||
|
||||
static int const N = 4;
|
||||
std::array<std::shared_ptr<JobQueue::Coro>, N> a;
|
||||
// The test assumes that all the threads and coroutines it launches
|
||||
// all run in a single thread.
|
||||
BEAST_EXPECT(jq.thread_count() == 1);
|
||||
|
||||
std::array<std::shared_ptr<JobQueue::Coro>, 4> a;
|
||||
|
||||
LocalValue<int> lv(-1);
|
||||
BEAST_EXPECT(*lv == -1);
|
||||
|
||||
gate g;
|
||||
|
||||
jq.addJob(jtCLIENT, "LocalValue-Test", [&]() {
|
||||
this->BEAST_EXPECT(*lv == -1);
|
||||
*lv = -2;
|
||||
this->BEAST_EXPECT(*lv == -2);
|
||||
g.signal();
|
||||
});
|
||||
|
||||
BEAST_EXPECT(g.wait_for(5s));
|
||||
BEAST_EXPECT(*lv == -1);
|
||||
|
||||
for (int i = 0; i < N; ++i)
|
||||
for (int i = 0; i < a.size(); ++i)
|
||||
{
|
||||
jq.postCoro(jtCLIENT, "Coroutine-Test", [&, id = i](auto const& c) {
|
||||
a[id] = c;
|
||||
@@ -154,12 +159,14 @@ public:
|
||||
BEAST_EXPECT(g.wait_for(5s));
|
||||
a[i]->join();
|
||||
}
|
||||
|
||||
for (auto const& c : a)
|
||||
{
|
||||
c->post();
|
||||
BEAST_EXPECT(g.wait_for(5s));
|
||||
c->join();
|
||||
}
|
||||
|
||||
for (auto const& c : a)
|
||||
{
|
||||
c->post();
|
||||
@@ -170,6 +177,7 @@ public:
|
||||
this->BEAST_EXPECT(*lv == -2);
|
||||
g.signal();
|
||||
});
|
||||
|
||||
BEAST_EXPECT(g.wait_for(5s));
|
||||
BEAST_EXPECT(*lv == -1);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
#include <xrpld/core/JobQueue.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
@@ -45,22 +50,19 @@ class JobQueue_test : public beast::unit_test::suite
|
||||
while (jobRan == false)
|
||||
;
|
||||
}
|
||||
{
|
||||
// If the JobQueue is stopped, we should no
|
||||
// longer be able to add Jobs (and calling addJob() should
|
||||
// return false).
|
||||
using namespace std::chrono_literals;
|
||||
jQueue.stop();
|
||||
|
||||
// The Job should never run, so having the Job access this
|
||||
// unprotected variable on the stack should be completely safe.
|
||||
// Not recommended for the faint of heart...
|
||||
bool unprotected;
|
||||
BEAST_EXPECT(
|
||||
jQueue.addJob(jtCLIENT, "JobAddTest2", [&unprotected]() {
|
||||
unprotected = false;
|
||||
}) == false);
|
||||
}
|
||||
// If the JobQueue is stopped or in the process of stopping, attempting
|
||||
// to schedule a coroutine should fail.
|
||||
jQueue.stop();
|
||||
BEAST_EXPECT(jQueue.isStopping());
|
||||
|
||||
auto const coro = jQueue.postCoro(
|
||||
jtCLIENT,
|
||||
"JobAddTest2",
|
||||
[](std::shared_ptr<JobQueue::Coro> const&) {
|
||||
LogicError("Unscheduled coroutine improperly invoked!");
|
||||
});
|
||||
BEAST_EXPECT(coro == nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -128,31 +130,77 @@ class JobQueue_test : public beast::unit_test::suite
|
||||
}
|
||||
BEAST_EXPECT(yieldCount == 4);
|
||||
}
|
||||
{
|
||||
// If the JobQueue is stopped, we should no
|
||||
// longer be able to add a Coro (and calling postCoro() should
|
||||
// return false).
|
||||
using namespace std::chrono_literals;
|
||||
jQueue.stop();
|
||||
|
||||
// The Coro should never run, so having the Coro access this
|
||||
// unprotected variable on the stack should be completely safe.
|
||||
// Not recommended for the faint of heart...
|
||||
bool unprotected;
|
||||
auto const coro = jQueue.postCoro(
|
||||
jtCLIENT,
|
||||
"PostCoroTest3",
|
||||
[&unprotected](std::shared_ptr<JobQueue::Coro> const&) {
|
||||
unprotected = false;
|
||||
});
|
||||
BEAST_EXPECT(coro == nullptr);
|
||||
}
|
||||
// If the JobQueue is stopped or in the process of stopping, attempting
|
||||
// to schedule a coroutine should fail.
|
||||
jQueue.stop();
|
||||
BEAST_EXPECT(jQueue.isStopping());
|
||||
|
||||
auto const coro = jQueue.postCoro(
|
||||
jtCLIENT,
|
||||
"PostCoroTest3",
|
||||
[](std::shared_ptr<JobQueue::Coro> const&) {
|
||||
LogicError("Unscheduled coroutine improperly invoked!");
|
||||
});
|
||||
BEAST_EXPECT(coro == nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
testPriority()
|
||||
{
|
||||
testcase("priority order");
|
||||
|
||||
jtx::Env env{*this};
|
||||
JobQueue& jQueue = env.app().getJobQueue();
|
||||
|
||||
// A standalone Env runs the JobQueue on a single worker, which makes
|
||||
// dispatch order deterministic.
|
||||
if (!BEAST_EXPECT(jQueue.thread_count() == 1))
|
||||
return;
|
||||
|
||||
std::promise<void> started;
|
||||
auto startedFuture = started.get_future();
|
||||
std::promise<void> release;
|
||||
std::shared_future<void> const releaseFuture =
|
||||
release.get_future().share();
|
||||
|
||||
// Occupy the only worker so the jobs below queue up behind it.
|
||||
BEAST_EXPECT(
|
||||
jQueue.addJob(jtCLIENT, "blocker", [&started, releaseFuture]() {
|
||||
started.set_value();
|
||||
releaseFuture.wait();
|
||||
}));
|
||||
startedFuture.wait();
|
||||
|
||||
std::mutex m;
|
||||
std::vector<std::string> order;
|
||||
auto record = [&](std::string tag) {
|
||||
return [&m, &order, tag]() {
|
||||
std::lock_guard lock(m);
|
||||
order.push_back(tag);
|
||||
};
|
||||
};
|
||||
|
||||
BEAST_EXPECT(jQueue.addJob(jtCLIENT, "low1", record("low1")));
|
||||
BEAST_EXPECT(jQueue.addJob(jtPUBLEDGER, "high", record("high")));
|
||||
BEAST_EXPECT(jQueue.addJob(jtCLIENT, "low2", record("low2")));
|
||||
BEAST_EXPECT(jQueue.addJob(jtADMIN, "highest", record("highest")));
|
||||
|
||||
release.set_value();
|
||||
jQueue.rendezvous();
|
||||
|
||||
// Higher job types run first; equal types run in FIFO order.
|
||||
std::vector<std::string> const expected{
|
||||
"highest", "high", "low1", "low2"};
|
||||
std::lock_guard lock(m);
|
||||
BEAST_EXPECT(order == expected);
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testPriority();
|
||||
testAddJob();
|
||||
testPostCoro();
|
||||
}
|
||||
|
||||
@@ -1,179 +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 <xrpld/core/JobTypes.h>
|
||||
#include <xrpld/core/detail/Workers.h>
|
||||
#include <xrpld/perflog/PerfLog.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/**
|
||||
* Dummy class for unit tests.
|
||||
*/
|
||||
|
||||
namespace perf {
|
||||
|
||||
class PerfLogTest : public PerfLog
|
||||
{
|
||||
void
|
||||
rpcStart(std::string const& method, std::uint64_t requestId) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
rpcFinish(std::string const& method, std::uint64_t requestId) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
rpcError(std::string const& method, std::uint64_t dur) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
jobQueue(JobType const type) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
jobStart(
|
||||
JobType const type,
|
||||
std::chrono::microseconds dur,
|
||||
std::chrono::time_point<std::chrono::steady_clock> startTime,
|
||||
int instance) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
jobFinish(JobType const type, std::chrono::microseconds dur, int instance)
|
||||
override
|
||||
{
|
||||
}
|
||||
|
||||
Json::Value
|
||||
countersJson() const override
|
||||
{
|
||||
return Json::Value();
|
||||
}
|
||||
|
||||
Json::Value
|
||||
currentJson() const override
|
||||
{
|
||||
return Json::Value();
|
||||
}
|
||||
|
||||
void
|
||||
resizeJobs(int const resize) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
rotate() override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace perf
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class Workers_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
struct TestCallback : Workers::Callback
|
||||
{
|
||||
void
|
||||
processTask(int instance) override
|
||||
{
|
||||
std::lock_guard lk{mut};
|
||||
if (--count == 0)
|
||||
cv.notify_all();
|
||||
}
|
||||
|
||||
std::condition_variable cv;
|
||||
std::mutex mut;
|
||||
int count = 0;
|
||||
};
|
||||
|
||||
void
|
||||
testThreads(int const tc1, int const tc2, int const tc3)
|
||||
{
|
||||
testcase(
|
||||
"threadCounts: " + std::to_string(tc1) + " -> " +
|
||||
std::to_string(tc2) + " -> " + std::to_string(tc3));
|
||||
|
||||
TestCallback cb;
|
||||
std::unique_ptr<perf::PerfLog> perfLog =
|
||||
std::make_unique<perf::PerfLogTest>();
|
||||
|
||||
Workers w(cb, perfLog.get(), "Test", tc1);
|
||||
BEAST_EXPECT(w.getNumberOfThreads() == tc1);
|
||||
|
||||
auto testForThreadCount = [this, &cb, &w](int const threadCount) {
|
||||
// Prepare the callback.
|
||||
cb.count = threadCount;
|
||||
|
||||
// Execute the test.
|
||||
w.setNumberOfThreads(threadCount);
|
||||
BEAST_EXPECT(w.getNumberOfThreads() == threadCount);
|
||||
|
||||
for (int i = 0; i < threadCount; ++i)
|
||||
w.addTask();
|
||||
|
||||
// 10 seconds should be enough to finish on any system
|
||||
//
|
||||
using namespace std::chrono_literals;
|
||||
std::unique_lock<std::mutex> lk{cb.mut};
|
||||
bool const signaled =
|
||||
cb.cv.wait_for(lk, 10s, [&cb] { return cb.count == 0; });
|
||||
BEAST_EXPECT(signaled);
|
||||
BEAST_EXPECT(cb.count == 0);
|
||||
};
|
||||
testForThreadCount(tc1);
|
||||
testForThreadCount(tc2);
|
||||
testForThreadCount(tc3);
|
||||
w.stop();
|
||||
|
||||
// We had better finished all our work!
|
||||
BEAST_EXPECT(cb.count == 0);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testThreads(0, 0, 0);
|
||||
testThreads(1, 0, 1);
|
||||
testThreads(2, 1, 2);
|
||||
testThreads(4, 3, 5);
|
||||
testThreads(16, 4, 15);
|
||||
testThreads(64, 3, 65);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Workers, core, ripple);
|
||||
|
||||
} // namespace ripple
|
||||
@@ -33,6 +33,8 @@
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -257,6 +259,11 @@ struct Peer
|
||||
// Simulation parameters
|
||||
ConsensusParms consensusParms;
|
||||
|
||||
//! Names the peer a transaction belongs to, for Consensus forced
|
||||
//! inclusion (as featureRNG uses for entropy contributions). Unset, it
|
||||
//! behaves exactly like an adaptor without the forcedTxOwner hook.
|
||||
std::function<std::optional<PeerID>(Tx const&)> txOwner;
|
||||
|
||||
//! The collectors to report events to
|
||||
CollectorRefs& collectors;
|
||||
|
||||
@@ -655,6 +662,14 @@ struct Peer
|
||||
return consensusParms;
|
||||
}
|
||||
|
||||
std::optional<PeerID>
|
||||
forcedTxOwner(Tx const& tx, Ledger const&) const
|
||||
{
|
||||
if (!txOwner)
|
||||
return std::nullopt;
|
||||
return txOwner(tx);
|
||||
}
|
||||
|
||||
// Not interested in tracking consensus mode changes for now
|
||||
void
|
||||
onModeChange(ConsensusMode, ConsensusMode)
|
||||
|
||||
@@ -44,10 +44,10 @@ bridge(
|
||||
Issue const& issuingChainIssue)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[sfLockingChainDoor.getJsonName()] = lockingChainDoor.human();
|
||||
jv[sfLockingChainIssue.getJsonName()] = to_json(lockingChainIssue);
|
||||
jv[sfIssuingChainDoor.getJsonName()] = issuingChainDoor.human();
|
||||
jv[sfIssuingChainIssue.getJsonName()] = to_json(issuingChainIssue);
|
||||
jv[jss::LockingChainDoor] = lockingChainDoor.human();
|
||||
jv[jss::LockingChainIssue] = to_json(lockingChainIssue);
|
||||
jv[jss::IssuingChainDoor] = issuingChainDoor.human();
|
||||
jv[jss::IssuingChainIssue] = to_json(issuingChainIssue);
|
||||
return jv;
|
||||
}
|
||||
|
||||
@@ -60,10 +60,10 @@ bridge_rpc(
|
||||
Issue const& issuingChainIssue)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[sfLockingChainDoor.getJsonName()] = lockingChainDoor.human();
|
||||
jv[sfLockingChainIssue.getJsonName()] = to_json(lockingChainIssue);
|
||||
jv[sfIssuingChainDoor.getJsonName()] = issuingChainDoor.human();
|
||||
jv[sfIssuingChainIssue.getJsonName()] = to_json(issuingChainIssue);
|
||||
jv[jss::LockingChainDoor] = lockingChainDoor.human();
|
||||
jv[jss::LockingChainIssue] = to_json(lockingChainIssue);
|
||||
jv[jss::IssuingChainDoor] = issuingChainDoor.human();
|
||||
jv[jss::IssuingChainIssue] = to_json(issuingChainIssue);
|
||||
return jv;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/InnerObjectFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
@@ -1234,6 +1235,90 @@ class Invariants_test : public beast::unit_test::suite
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||
}
|
||||
|
||||
void
|
||||
testAppLoader()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
static std::string const good = "<html></html>";
|
||||
|
||||
auto insert = [](ApplyContext& ac,
|
||||
AccountID const& owner,
|
||||
uint256 const& key,
|
||||
std::string const& blob,
|
||||
std::uint32_t seq) {
|
||||
auto sle = std::make_shared<SLE>(Keylet{ltAPP_LOADER, key});
|
||||
sle->setAccountID(sfOwner, owner);
|
||||
sle->setFieldVL(sfAppLoader, Slice(blob.data(), blob.size()));
|
||||
sle->setFieldU32(sfAppLoaderSequence, seq);
|
||||
ac.view().insert(sle);
|
||||
auto root = ac.view().peek(keylet::account(owner));
|
||||
root->setFieldU32(sfAppLoaderSequence, seq);
|
||||
ac.view().update(root);
|
||||
return true;
|
||||
};
|
||||
Preclose publish = [](Account const& A1, Account const&, Env& env) {
|
||||
auto jt = noop(A1);
|
||||
jt[sfAppLoader.fieldName] = strHex(good);
|
||||
env(jt, fee(XRP(1)));
|
||||
return bool(env.le(keylet::appLoader(A1.id())));
|
||||
};
|
||||
auto bad = [&](char const* log,
|
||||
Precheck const& pre,
|
||||
std::uint16_t txType = ttACCOUNT_SET,
|
||||
Preclose const& preclose = {}) {
|
||||
doInvariantCheck(
|
||||
{{log}},
|
||||
pre,
|
||||
XRPAmount{},
|
||||
STTx{TxType(txType), [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
preclose);
|
||||
};
|
||||
|
||||
testcase << "AppLoader at the wrong key";
|
||||
bad("AppLoader is not keyed at its owner's keylet",
|
||||
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
return insert(ac, A1, keylet::appLoader(A2).key, good, 1);
|
||||
});
|
||||
testcase << "AppLoader with invalid blob";
|
||||
bad("AppLoader blob does not validate",
|
||||
[&](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
return insert(ac, A1, keylet::appLoader(A1).key, "nope", 1);
|
||||
});
|
||||
testcase << "AppLoader blob over the size limit";
|
||||
bad("AppLoader blob does not validate",
|
||||
[&](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
return insert(
|
||||
ac,
|
||||
A1,
|
||||
keylet::appLoader(A1).key,
|
||||
"<html>" + std::string(maxAppLoaderLength, 'x') + "</html>",
|
||||
1);
|
||||
});
|
||||
testcase << "AppLoader sequence differs from its owner's";
|
||||
bad(
|
||||
"AppLoader sequence does not match its owner's",
|
||||
[&](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
auto sle = ac.view().peek(keylet::appLoader(A1));
|
||||
sle->setFieldU32(sfAppLoaderSequence, 7);
|
||||
ac.view().update(sle);
|
||||
return true;
|
||||
},
|
||||
ttACCOUNT_SET,
|
||||
publish);
|
||||
testcase << "AppLoader changed by the wrong transaction type";
|
||||
bad(
|
||||
"AppLoader changed by a transaction other than AccountSet or "
|
||||
"AccountDelete",
|
||||
[&](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
auto sle = ac.view().peek(keylet::appLoader(A1));
|
||||
ac.view().erase(sle);
|
||||
return true;
|
||||
},
|
||||
ttPAYMENT,
|
||||
publish);
|
||||
}
|
||||
|
||||
void
|
||||
testLockedBalance()
|
||||
{
|
||||
@@ -1289,6 +1374,7 @@ public:
|
||||
testNFTokenPageInvariants();
|
||||
testPermissionedDomainInvariants();
|
||||
testLockedBalance();
|
||||
testAppLoader();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Loaded 100 of 257 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user