mirror of
https://github.com/Xahau/xahaud.git
synced 2026-10-22 19:57:09 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcd2b0a57d | ||
|
|
1440940636 | ||
|
|
ebaaf2132b | ||
|
|
ed10ffb935 |
No files matched your search
@@ -97,6 +97,8 @@ jobs:
|
||||
contents: read
|
||||
container:
|
||||
image: ubuntu:24.04
|
||||
# [DO NOT MERGE] diagnostic: allow gdb/eu-stack to attach to hung test children
|
||||
options: --cap-add=SYS_PTRACE
|
||||
volumes:
|
||||
- /home/runner/.conan-cache:/.conan-cache
|
||||
- /home/runner/.ccache-cache:/github/home/.ccache-cache
|
||||
@@ -303,9 +305,98 @@ jobs:
|
||||
# Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness.
|
||||
# Use *_FLAGS_DEBUG so the build action's stdlib flag (e.g. -stdlib=libc++) in CMAKE_CXX_FLAGS isn't clobbered.
|
||||
cmake-args: '-Dcoverage=ON -Dcoverage_tool=${{ matrix.coverage_tool }} -Dcoverage_format=${{ matrix.coverage_format }} -Dcoverage_test_parallelism=$(($(nproc)/2)) -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_CXX_FLAGS_DEBUG="-g -O0" -DCMAKE_C_FLAGS_DEBUG="-g -O0"'
|
||||
cmake-target: 'coverage'
|
||||
cmake-target: 'rippled'
|
||||
ccache_max_size: '100G'
|
||||
|
||||
# [DO NOT MERGE] Diagnostic: run the coverage test pass directly (not via
|
||||
# the ninja `coverage` target, which buffers all output until exit) so
|
||||
# every line gets a live timestamp, sample host/process state every 60s,
|
||||
# and dump thread stacks of every rippled process at 12/25 min and on a
|
||||
# 90 min deadline instead of sitting for the 6h job timeout.
|
||||
- name: Run tests (coverage, diagnostic)
|
||||
if: matrix.job_type == 'coverage'
|
||||
shell: bash
|
||||
run: |
|
||||
apt-get install -y procps elfutils gdb > /dev/null
|
||||
cd ${{ env.build_dir }}
|
||||
rm -rf coverage-profraw && mkdir coverage-profraw
|
||||
JOBS=$(($(nproc)/2))
|
||||
echo "nproc=$(nproc) jobs=$JOBS loadavg=$(cat /proc/loadavg)"
|
||||
free -m; df -h . /dev/shm /tmp; ulimit -a; cat /proc/pressure/cpu /proc/pressure/memory /proc/pressure/io 2>/dev/null || true
|
||||
|
||||
cat > /tmp/flag.py <<'EOF'
|
||||
import gdb
|
||||
for t in gdb.selected_inferior().threads():
|
||||
t.switch()
|
||||
f = gdb.newest_frame()
|
||||
while f:
|
||||
if f.name() and 'ApplicationImp::run' in f.name():
|
||||
f.select()
|
||||
print("thread", t.num, "in ApplicationImp::run, isTimeToStop =", gdb.parse_and_eval("this->isTimeToStop"))
|
||||
break
|
||||
f = f.older()
|
||||
EOF
|
||||
dump_stacks() {
|
||||
echo "===== STACK DUMP ($1) $(date -u +%T)"
|
||||
for p in $(pgrep -x rippled); do
|
||||
echo "--- pid $p state=$(awk '/^State/{print $2}' /proc/$p/status) wchan=$(cat /proc/$p/wchan 2>/dev/null) cmd=$(tr '\0' ' ' < /proc/$p/cmdline | cut -c1-100)"
|
||||
timeout 120 eu-stack -p $p 2>&1 | head -300 || true
|
||||
st=$(awk '/^State/{print $2}' /proc/$p/status 2>/dev/null)
|
||||
if [ "$st" = "S" ] && [ "$p" != "$RP" ]; then
|
||||
timeout 900 gdb -p $p -batch -ex 'set pagination off' -ex 'source /tmp/flag.py' -ex 'thread apply all bt 30' 2>&1 \
|
||||
| grep -vE '^\[New LWP|^\[Thread debugging|^Using host|^warning:|^Reading symbols|^Download' | head -800 || true
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Stress the suite whose Env lifetimes are shortest: the hang sits in
|
||||
# Env::~AppBundle (thread.join on ApplicationImp::run) and shows up
|
||||
# within a few iterations when it is present.
|
||||
for i in 1 2 3 4 5 6; do
|
||||
st=$SECONDS
|
||||
if LLVM_PROFILE_FILE=/tmp/stress-%p.profraw timeout -s KILL 240 ./rippled --unittest=ripple.app.HookAPI --unittest-jobs 1 > /tmp/stress_$i.log 2>&1; then
|
||||
echo "stress iter $i ok $((SECONDS-st))s: $(grep -oE '[0-9]+ cases, [0-9]+ tests total, [0-9]+ failures' /tmp/stress_$i.log)"
|
||||
else
|
||||
echo "stress iter $i FAILED/HUNG rc=$? after $((SECONDS-st))s; last lines:"; tail -3 /tmp/stress_$i.log
|
||||
fi
|
||||
done
|
||||
rm -f /tmp/stress-*.profraw
|
||||
|
||||
( while true; do
|
||||
echo "===== sample $(date -u +%T) loadavg=$(cut -d' ' -f1-3 /proc/loadavg) psi_cpu=$(grep -o 'avg60=[0-9.]*' /proc/pressure/cpu 2>/dev/null | head -1) psi_mem=$(grep -o 'avg60=[0-9.]*' /proc/pressure/memory 2>/dev/null | head -1) psi_io=$(grep -o 'avg60=[0-9.]*' /proc/pressure/io 2>/dev/null | head -1)"
|
||||
free -m | sed -n 2,3p
|
||||
ps -eo pid,ppid,stat,etimes,pcpu,rss,wchan:28,args --sort=-pcpu | grep -E 'PID|rippled' | grep -v grep | head -14 | cut -c1-150
|
||||
sleep 60
|
||||
done ) &
|
||||
MON=$!
|
||||
|
||||
LLVM_PROFILE_FILE="$PWD/coverage-profraw/rippled-%m-%p.profraw" \
|
||||
stdbuf -oL -eL ./rippled --unittest --unittest-jobs $JOBS --unittest-log &
|
||||
RP=$!
|
||||
START=$SECONDS; D20=0; D40=0; rc=0
|
||||
while kill -0 $RP 2>/dev/null; do
|
||||
el=$((SECONDS-START))
|
||||
if [ $D20 = 0 ] && [ $el -ge 720 ]; then D20=1; dump_stacks "12min"; fi
|
||||
if [ $D40 = 0 ] && [ $el -ge 1500 ]; then D40=1; dump_stacks "25min"; fi
|
||||
if [ $el -ge 3600 ]; then
|
||||
dump_stacks "60min deadline"
|
||||
echo "===== DEADLINE: killing rippled processes"
|
||||
pkill -KILL -x rippled || true
|
||||
break
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
wait $RP || rc=$?
|
||||
kill $MON 2>/dev/null || true
|
||||
echo "rippled unittest exit code: $rc after $((SECONDS-START))s"
|
||||
ls -la coverage-profraw | head -20
|
||||
|
||||
llvm-profdata-${{ matrix.compiler_version }} merge -sparse -o coverage.profdata coverage-profraw/*.profraw
|
||||
llvm-cov-${{ matrix.compiler_version }} export -instr-profile=coverage.profdata -format=lcov \
|
||||
-ignore-filename-regex="src/test|include/xrpl/beast/test|include/xrpl/beast/unit_test|$PWD/pb-xrpl.libpb" \
|
||||
./rippled > coverage.lcov
|
||||
exit $rc
|
||||
|
||||
- name: Set artifact name
|
||||
if: matrix.job_type == 'build'
|
||||
id: set-artifact-name
|
||||
|
||||
@@ -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,7 +12,6 @@ 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
|
||||
@@ -22,7 +21,6 @@ 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
|
||||
@@ -52,6 +50,8 @@ 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
|
||||
@@ -101,7 +101,6 @@ 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
|
||||
@@ -125,7 +124,6 @@ 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
|
||||
|
||||
+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 || exit 1
|
||||
git log -n 20 >> release.info;
|
||||
|
||||
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 -p /data/builds/candidate
|
||||
mkdir /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,13 +166,10 @@
|
||||
# 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.
|
||||
@@ -267,119 +264,6 @@
|
||||
# "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>
|
||||
@@ -564,30 +448,6 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
# [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).
|
||||
@@ -814,38 +674,6 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
# [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]
|
||||
@@ -1588,19 +1416,6 @@
|
||||
# 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,3 +151,4 @@ D686F2538F410C9D0D856788E98E3579595DAF7B38D38887F81ECAC934B06040 HooksUpdate1
|
||||
4B8466415FAB32FFA89D9DCBE166A42340115771DF611A7160F8D7439C87ECD8 fixNSDelete
|
||||
EDB4EE4C524E16BDD91D9A529332DED08DCAAA51CC6DC897ACFA1A0ED131C5B6 fix240819
|
||||
8063140E9260799D6716756B891CEC3E7006C4E4F277AB84670663A88F94B9C4 fixPageCap
|
||||
88693F108C3CD8A967F3F4253A32DEF5E35F9406ACD2A11B88B11D90865763A9 fix240911
|
||||
@@ -97,7 +97,6 @@ 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,6 +47,5 @@
|
||||
#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,12 +339,6 @@ 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
|
||||
|
||||
+1
-1
@@ -46,8 +46,8 @@
|
||||
|
||||
#include "error.h"
|
||||
#include "extern.h"
|
||||
#include "macro.h"
|
||||
#include "sfcodes.h"
|
||||
#include "macro.h"
|
||||
#include "tts.h"
|
||||
|
||||
#include "ls_flags.h"
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
#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)
|
||||
@@ -82,7 +80,6 @@
|
||||
#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)
|
||||
@@ -155,8 +152,6 @@
|
||||
#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)
|
||||
@@ -227,7 +222,6 @@
|
||||
#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)
|
||||
@@ -295,7 +289,6 @@
|
||||
#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)
|
||||
@@ -328,7 +321,6 @@
|
||||
#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,7 +61,6 @@
|
||||
#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,33 +1,75 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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;
|
||||
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;
|
||||
|
||||
int
|
||||
increment() noexcept
|
||||
{
|
||||
auto const newCount = ++count_;
|
||||
|
||||
if (auto maxCount = maxCount_.load(); newCount > maxCount)
|
||||
maxCount_.compare_exchange_strong(maxCount, newCount);
|
||||
|
||||
return newCount;
|
||||
return ++count_;
|
||||
}
|
||||
|
||||
int
|
||||
@@ -37,161 +79,78 @@ public:
|
||||
}
|
||||
|
||||
int
|
||||
count() const noexcept
|
||||
getCount() const noexcept
|
||||
{
|
||||
return count_.load();
|
||||
}
|
||||
|
||||
int
|
||||
max() const noexcept
|
||||
Counter*
|
||||
getNext() const noexcept
|
||||
{
|
||||
return std::max(count_.load(), maxCount_.load());
|
||||
return next_;
|
||||
}
|
||||
|
||||
std::string const&
|
||||
name() const noexcept
|
||||
getName() const noexcept
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class CountedObjects;
|
||||
|
||||
Counter* next_;
|
||||
std::atomic<std::uint32_t> count_ = 0;
|
||||
std::atomic<std::uint32_t> maxCount_ = 0;
|
||||
std::string const name_;
|
||||
std::atomic<int> count_;
|
||||
Counter* next_;
|
||||
};
|
||||
|
||||
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:
|
||||
friend class Counter;
|
||||
CountedObjects() noexcept;
|
||||
~CountedObjects() noexcept = default;
|
||||
|
||||
std::atomic<Counter*> head_ = nullptr;
|
||||
private:
|
||||
std::atomic<int> m_count;
|
||||
std::atomic<Counter*> m_head;
|
||||
};
|
||||
|
||||
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
|
||||
{
|
||||
static CountedObjects::Counter counter_;
|
||||
private:
|
||||
static auto&
|
||||
getCounter() noexcept
|
||||
{
|
||||
static CountedObjects::Counter c{beast::type_name<Object>()};
|
||||
return c;
|
||||
}
|
||||
|
||||
public:
|
||||
CountedObject() noexcept
|
||||
{
|
||||
counter_.increment();
|
||||
getCounter().increment();
|
||||
}
|
||||
|
||||
CountedObject(CountedObject const&) noexcept
|
||||
{
|
||||
counter_.increment();
|
||||
getCounter().increment();
|
||||
}
|
||||
|
||||
CountedObject&
|
||||
operator=(CountedObject const&) noexcept = default;
|
||||
|
||||
~CountedObject() noexcept
|
||||
{
|
||||
counter_.decrement();
|
||||
getCounter().decrement();
|
||||
}
|
||||
};
|
||||
|
||||
// Instantiation of the static CountedObject<T>::counter_
|
||||
template <class Object>
|
||||
CountedObjects::Counter CountedObject<Object>::counter_{
|
||||
beast::type_name<Object>()};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -113,7 +113,7 @@ template <>
|
||||
inline std::size_t
|
||||
extract(SHAMapHash const& key)
|
||||
{
|
||||
return extract(key.as_uint256());
|
||||
return *reinterpret_cast<std::size_t const*>(key.as_uint256().data());
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
+300
-523
@@ -21,443 +21,195 @@
|
||||
#define RIPPLE_BASICS_SLABALLOCATOR_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
#include <xrpl/basics/spinlock.h>
|
||||
#include <xrpl/beast/type_name.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <boost/align.hpp>
|
||||
#include <boost/predef/os.h>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <boost/predef.h>
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include <limits>
|
||||
#include <new>
|
||||
|
||||
#ifdef BOOST_OS_LINUX
|
||||
#if BOOST_OS_LINUX
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
namespace ripple {
|
||||
namespace slab {
|
||||
namespace detail {
|
||||
|
||||
/// 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
|
||||
template <typename Type>
|
||||
class SlabAllocator
|
||||
{
|
||||
auto ptr = reinterpret_cast<std::uint8_t*>(
|
||||
boost::alignment::aligned_alloc(pageSize, size));
|
||||
static_assert(
|
||||
sizeof(Type) >= sizeof(std::uint8_t*),
|
||||
"SlabAllocator: the requested object must be larger than a pointer.");
|
||||
|
||||
#if BOOST_OS_LINUX
|
||||
if (ptr != nullptr) [[likely]]
|
||||
madvise(ptr, size, MADV_HUGEPAGE);
|
||||
#endif
|
||||
static_assert(alignof(Type) == 8 || alignof(Type) == 4);
|
||||
|
||||
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 block of memory that is owned by a slab allocator */
|
||||
struct SlabBlock
|
||||
{
|
||||
item_t* const next;
|
||||
// A mutex to protect the freelist for this block:
|
||||
std::mutex m_;
|
||||
|
||||
constexpr explicit item_t(item_t* n) noexcept : next(n)
|
||||
// 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)
|
||||
{
|
||||
}
|
||||
};
|
||||
// We don't need to grab the mutex here, since we're the only
|
||||
// ones with access at this moment.
|
||||
|
||||
// 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>);
|
||||
|
||||
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;
|
||||
|
||||
/** Construct a block that is unlinked. */
|
||||
constexpr slab_t() noexcept = default;
|
||||
|
||||
/** 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))
|
||||
while (data + item <= p_ + size_)
|
||||
{
|
||||
return std::construct_at(&blocks_[idx], next);
|
||||
// Use memcpy to avoid unaligned UB
|
||||
// (will optimize to equivalent code)
|
||||
std::memcpy(data, &l_, sizeof(std::uint8_t*));
|
||||
l_ = data;
|
||||
data += item;
|
||||
}
|
||||
}
|
||||
|
||||
// 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_)
|
||||
~SlabBlock()
|
||||
{
|
||||
if (auto ret = slab->try_allocate())
|
||||
return ret;
|
||||
// Calling this destructor will release the allocated memory but
|
||||
// will not properly destroy any objects that are constructed in
|
||||
// the block itself.
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
SlabBlock(SlabBlock const& other) = delete;
|
||||
SlabBlock&
|
||||
operator=(SlabBlock const& other) = delete;
|
||||
|
||||
/** Allocate a memory block, potentially creating new capacity.
|
||||
SlabBlock(SlabBlock&& other) = delete;
|
||||
SlabBlock&
|
||||
operator=(SlabBlock&& other) = delete;
|
||||
|
||||
Slow path allocation that may allocate a new buffer or slab.
|
||||
Must be called with blockLock_ held.
|
||||
/** 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 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));
|
||||
/** Return an item to this allocator's freelist.
|
||||
|
||||
if (!slab) [[unlikely]]
|
||||
@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
|
||||
{
|
||||
detail::deallocateBuffer(buf);
|
||||
return nullptr;
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
auto ret = slab->try_assign(buf, slabSize_, itemSize_);
|
||||
assert(ret);
|
||||
private:
|
||||
// A linked list of slabs
|
||||
std::atomic<SlabBlock*> slabs_ = nullptr;
|
||||
|
||||
// Link the new slab. Since we hold the lock, we can just do a
|
||||
// direct assignment here.
|
||||
slabs_.store(slab, std::memory_order::release);
|
||||
// The alignment requirements of the item we're allocating:
|
||||
std::size_t const itemAlignment_;
|
||||
|
||||
return ret;
|
||||
}
|
||||
// The size of an item, including the extra bytes requested and
|
||||
// any padding needed for alignment purposes:
|
||||
std::size_t const itemSize_;
|
||||
|
||||
// The size of each individual slab:
|
||||
std::size_t const slabSize_;
|
||||
|
||||
public:
|
||||
/** Constructs a slab allocator able to allocate objects of a fixed size
|
||||
|
||||
@param extra Extra bytes per item beyond sizeof(Type).
|
||||
@param minItems Minimum number of items per slab (rounded up to 2MB).
|
||||
@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.
|
||||
*/
|
||||
constexpr explicit sized_allocator_t(
|
||||
constexpr explicit SlabAllocator(
|
||||
std::size_t extra,
|
||||
std::size_t minItems)
|
||||
: itemSize_(boost::alignment::align_up(sizeof(Type) + extra, Align))
|
||||
, slabSize_(boost::alignment::align_up(
|
||||
itemSize_ * minItems,
|
||||
detail::pageSize))
|
||||
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)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(itemAlignment_ & (itemAlignment_ - 1)) == 0,
|
||||
"ripple::SlabAllocator::SlabAllocator : valid alignment");
|
||||
}
|
||||
|
||||
// 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 const& other) = delete;
|
||||
SlabAllocator&
|
||||
operator=(SlabAllocator const& 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(SlabAllocator&& other) = delete;
|
||||
SlabAllocator&
|
||||
operator=(SlabAllocator&& other) = delete;
|
||||
|
||||
/** Returns the size of memory blocks returned by this allocator.
|
||||
~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.
|
||||
}
|
||||
|
||||
@return Size of each allocated block in bytes.
|
||||
*/
|
||||
[[nodiscard]] constexpr std::size_t
|
||||
/** Returns the size of the memory block this allocator returns. */
|
||||
constexpr std::size_t
|
||||
size() const noexcept
|
||||
{
|
||||
return itemSize_;
|
||||
@@ -465,36 +217,87 @@ public:
|
||||
|
||||
/** Returns a suitably aligned pointer, if one is available.
|
||||
|
||||
@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.
|
||||
@return a pointer to a block of memory from the allocator, or
|
||||
nullptr if the allocator can't satisfy this request.
|
||||
*/
|
||||
[[nodiscard, gnu::malloc]] std::uint8_t*
|
||||
std::uint8_t*
|
||||
allocate() noexcept
|
||||
{
|
||||
// Fast path: try existing slabs
|
||||
auto ret = fast_allocate();
|
||||
auto slab = slabs_.load();
|
||||
|
||||
if (ret == nullptr)
|
||||
while (slab != nullptr)
|
||||
{
|
||||
// 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_);
|
||||
if (auto ret = slab->allocate())
|
||||
return ret;
|
||||
|
||||
slab = slab->next_;
|
||||
}
|
||||
|
||||
return ret;
|
||||
// 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();
|
||||
}
|
||||
|
||||
/** Returns the memory block to the allocator.
|
||||
|
||||
@param ptr Pointer to memory block.
|
||||
@return true if the block belonged to this allocator and was freed.
|
||||
@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.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
bool
|
||||
deallocate(std::uint8_t* ptr) noexcept
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
@@ -502,135 +305,112 @@ public:
|
||||
"ripple::SlabAllocator::SlabAllocator::deallocate : non-null "
|
||||
"input");
|
||||
|
||||
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_)
|
||||
for (auto slab = slabs_.load(); slab != nullptr; slab = slab->next_)
|
||||
{
|
||||
if (slab->try_deallocate(ptr, slabSize_, release))
|
||||
if (slab->own(ptr))
|
||||
{
|
||||
slab->deallocate(ptr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** 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
|
||||
/** A collection of slab allocators of various sizes for a given type. */
|
||||
template <typename Type>
|
||||
class SlabAllocatorSet
|
||||
{
|
||||
static constexpr std::size_t minItems = MinItems;
|
||||
static constexpr std::size_t extra = Extra;
|
||||
};
|
||||
private:
|
||||
// The list of allocators that belong to this set
|
||||
boost::container::static_vector<SlabAllocator<Type>, 64> allocators_;
|
||||
|
||||
/** 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;
|
||||
};
|
||||
|
||||
/** Validate slab configurations at compile time.
|
||||
|
||||
Checks that configurations produce strictly increasing sizes after
|
||||
alignment. This catches both unsorted configs and configs that
|
||||
collapse to the same size after alignment.
|
||||
|
||||
@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 std::array<std::size_t, sizeof...(Configs)> sizes{
|
||||
align_up(sizeof(Type) + Configs::extra, Align)...};
|
||||
|
||||
for (std::size_t i = 1; i < sizes.size(); ++i)
|
||||
if (sizes[i - 1] >= sizes[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** A collection of slab allocators for different sizes.
|
||||
|
||||
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.
|
||||
|
||||
@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_;
|
||||
std::size_t maxSize_ = 0;
|
||||
|
||||
public:
|
||||
/** Construct an allocator set. */
|
||||
constexpr aligned_allocator_t()
|
||||
: allocators_{sized_allocator_t<Type, Align>(
|
||||
Configs::extra,
|
||||
Configs::minItems)...}
|
||||
class SlabConfig
|
||||
{
|
||||
friend class SlabAllocatorSet;
|
||||
|
||||
private:
|
||||
std::size_t extra;
|
||||
std::size_t alloc;
|
||||
std::size_t align;
|
||||
|
||||
public:
|
||||
constexpr SlabConfig(
|
||||
std::size_t extra_,
|
||||
std::size_t alloc_ = 0,
|
||||
std::size_t align_ = alignof(Type))
|
||||
: extra(extra_), alloc(alloc_), align(align_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
// 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 (auto const& c : cfg)
|
||||
{
|
||||
auto& a = allocators_.emplace_back(c.extra, c.alloc, c.align);
|
||||
|
||||
if (a.size() > maxSize_)
|
||||
maxSize_ = a.size();
|
||||
}
|
||||
}
|
||||
|
||||
SlabAllocatorSet(SlabAllocatorSet const& other) = delete;
|
||||
SlabAllocatorSet&
|
||||
operator=(SlabAllocatorSet const& other) = delete;
|
||||
|
||||
SlabAllocatorSet(SlabAllocatorSet&& other) = delete;
|
||||
SlabAllocatorSet&
|
||||
operator=(SlabAllocatorSet&& other) = delete;
|
||||
|
||||
~SlabAllocatorSet()
|
||||
{
|
||||
}
|
||||
|
||||
~aligned_allocator_t() = default;
|
||||
/** Returns a suitably aligned pointer, if one is available.
|
||||
|
||||
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;
|
||||
@param extra The number of extra bytes, above and beyond the size of
|
||||
the object, that should be returned by the allocator.
|
||||
|
||||
/** 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.
|
||||
@return a pointer to a block of memory, or nullptr if the allocator
|
||||
can't satisfy this request.
|
||||
*/
|
||||
[[nodiscard, gnu::malloc]] std::uint8_t*
|
||||
std::uint8_t*
|
||||
allocate(std::size_t extra) noexcept
|
||||
{
|
||||
auto const size = sizeof(Type) + extra;
|
||||
|
||||
for (auto& a : allocators_)
|
||||
if (auto const size = sizeof(Type) + extra; size <= maxSize_)
|
||||
{
|
||||
if (a.size() >= size)
|
||||
return a.allocate();
|
||||
for (auto& a : allocators_)
|
||||
{
|
||||
if (a.size() >= size)
|
||||
return a.allocate();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
@@ -638,10 +418,12 @@ public:
|
||||
|
||||
/** Returns the memory block to the allocator.
|
||||
|
||||
@param ptr Pointer to memory block.
|
||||
@return true if memory belonged to this set and was freed.
|
||||
@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.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
bool
|
||||
deallocate(std::uint8_t* ptr) noexcept
|
||||
{
|
||||
for (auto& a : allocators_)
|
||||
@@ -649,16 +431,11 @@ 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
|
||||
+343
-418
File diff suppressed because it is too large.
Load diff
@@ -23,7 +23,6 @@
|
||||
#include <xrpl/beast/type_name.h>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
|
||||
@@ -53,7 +52,7 @@ Rethrow()
|
||||
}
|
||||
|
||||
template <class E, class... Args>
|
||||
[[noreturn]] constexpr void
|
||||
[[noreturn]] inline void
|
||||
Throw(Args&&... args)
|
||||
{
|
||||
static_assert(
|
||||
@@ -61,15 +60,10 @@ Throw(Args&&... args)
|
||||
"Exception must derive from std::exception.");
|
||||
|
||||
E e(std::forward<Args>(args)...);
|
||||
|
||||
// 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());
|
||||
|
||||
LogThrow(
|
||||
std::string(
|
||||
"Throwing exception of type " + beast::type_name<E>() + ": ") +
|
||||
e.what());
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
+96
-139
@@ -20,7 +20,6 @@
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
@@ -31,7 +30,6 @@
|
||||
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
|
||||
@@ -54,130 +52,11 @@ spin_pause() noexcept
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/** Attempt to acquire a spinlock without blocking.
|
||||
/** @{ */
|
||||
/** Classes to handle arrays of spinlocks packed into a single atomic integer:
|
||||
|
||||
@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.
|
||||
Packed spinlocks allow for tremendously 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
|
||||
@@ -199,19 +78,26 @@ public:
|
||||
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 <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>;
|
||||
})
|
||||
template <class 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_;
|
||||
|
||||
@@ -228,7 +114,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) noexcept
|
||||
packed_spinlock(std::atomic<T>& lock, int index)
|
||||
: bits_(lock), mask_(static_cast<T>(1) << index)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
@@ -237,13 +123,13 @@ public:
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
try_lock() noexcept
|
||||
try_lock()
|
||||
{
|
||||
return (bits_.fetch_or(mask_, std::memory_order::acquire) & mask_) == 0;
|
||||
return (bits_.fetch_or(mask_, std::memory_order_acquire) & mask_) == 0;
|
||||
}
|
||||
|
||||
void
|
||||
lock() noexcept
|
||||
lock()
|
||||
{
|
||||
while (!try_lock())
|
||||
{
|
||||
@@ -251,18 +137,89 @@ 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() noexcept
|
||||
unlock()
|
||||
{
|
||||
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
|
||||
@@ -0,0 +1,307 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
@@ -15,7 +15,6 @@
|
||||
#define uint256 std::string
|
||||
#define featureHooksUpdate1 "1"
|
||||
#define featureHooksUpdate2 "1"
|
||||
#define featureRNG "1"
|
||||
#define fix20250131 "1"
|
||||
#define fixGuardDepth32 "1"
|
||||
namespace hook_api {
|
||||
@@ -386,9 +385,7 @@ 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_LITTLE_ENTROPY = -46 // featureRNG: no seed for this ledger, or too few
|
||||
// validators contributed to it
|
||||
TOO_MANY_NAMESPACES = -45
|
||||
};
|
||||
|
||||
enum class ExitType : uint8_t {
|
||||
@@ -401,7 +398,6 @@ 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;
|
||||
|
||||
@@ -1096,11 +1096,7 @@ validateGuards(
|
||||
{
|
||||
int name_len = parseLeb128(wasm, i, &i);
|
||||
CHECK_SHORT_HOOK();
|
||||
// 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 (name_len == 4)
|
||||
{
|
||||
if (wasm[i] == 'h' && wasm[i + 1] == 'o' &&
|
||||
wasm[i + 2] == 'o' && wasm[i + 3] == 'k')
|
||||
|
||||
+516
-24
@@ -1,31 +1,523 @@
|
||||
|
||||
#ifndef HOOKMISC_INCLUDED
|
||||
#define HOOKMISC_INCLUDED 1
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
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.
|
||||
|
||||
/** 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;
|
||||
}();
|
||||
|
||||
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")};
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -298,12 +298,4 @@ HOOK_API_DEFINITION(
|
||||
HOOK_API_DEFINITION(
|
||||
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
|
||||
@@ -1,66 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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,12 +157,7 @@ enum error_code_i {
|
||||
// Simulate
|
||||
rpcTX_SIGNED = 97,
|
||||
|
||||
// ledger_entry
|
||||
rpcENTRY_NOT_FOUND = 98,
|
||||
rpcUNEXPECTED_LEDGER_TYPE = 99,
|
||||
|
||||
rpcLAST =
|
||||
rpcUNEXPECTED_LEDGER_TYPE // rpcLAST should always equal the last code.
|
||||
rpcLAST = rpcTX_SIGNED // rpcLAST should always equal the last code.
|
||||
};
|
||||
|
||||
/** Codes returned in the `warnings` array of certain RPC commands.
|
||||
|
||||
@@ -118,10 +118,6 @@ 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
|
||||
{
|
||||
@@ -332,14 +328,6 @@ 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;
|
||||
|
||||
@@ -388,16 +376,6 @@ 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:
|
||||
@@ -428,7 +406,7 @@ struct keyletDesc
|
||||
|
||||
// This list should include all of the keylet functions that take a single
|
||||
// AccountID parameter.
|
||||
std::array<keyletDesc<AccountID const&>, 7> const directAccountKeylets{
|
||||
std::array<keyletDesc<AccountID const&>, 6> const directAccountKeylets{
|
||||
{{&keylet::account, jss::AccountRoot, false},
|
||||
{&keylet::ownerDir, jss::DirectoryNode, true},
|
||||
{&keylet::signers, jss::SignerList, true},
|
||||
@@ -436,8 +414,7 @@ std::array<keyletDesc<AccountID const&>, 7> 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::appLoader, jss::AppLoader, true}}};
|
||||
{&keylet::did, jss::DID, true}}};
|
||||
|
||||
MPTID
|
||||
makeMptID(std::uint32_t sequence, AccountID const& account);
|
||||
|
||||
@@ -84,9 +84,6 @@ 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;
|
||||
|
||||
@@ -99,25 +96,6 @@ 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,7 +24,6 @@
|
||||
#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>
|
||||
|
||||
@@ -37,26 +36,6 @@
|
||||
|
||||
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
|
||||
@@ -79,47 +58,77 @@ publicKeyType(Slice const& slice)
|
||||
prefix constant 0xED, followed by 32 bytes of
|
||||
public key data.
|
||||
*/
|
||||
class PublicKey : public detail::KeyBase<PublicKey, 33>
|
||||
class PublicKey
|
||||
{
|
||||
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&) = default;
|
||||
PublicKey(PublicKey const& other);
|
||||
PublicKey&
|
||||
operator=(PublicKey const&) = default;
|
||||
operator=(PublicKey const& other);
|
||||
|
||||
explicit PublicKey(Slice const& slice) noexcept
|
||||
/** Create a public key.
|
||||
|
||||
Preconditions:
|
||||
publicKeyType(slice) != std::nullopt
|
||||
*/
|
||||
explicit PublicKey(Slice const& slice);
|
||||
|
||||
std::uint8_t const*
|
||||
data() const noexcept
|
||||
{
|
||||
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());
|
||||
return buf_;
|
||||
}
|
||||
|
||||
explicit PublicKey(value_t const& data) noexcept
|
||||
: PublicKey(makeSlice(data))
|
||||
std::size_t
|
||||
size() const noexcept
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
[[nodiscard]] Slice
|
||||
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
|
||||
slice() const noexcept
|
||||
{
|
||||
return {buf_.data(), buf_.size()};
|
||||
return {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.
|
||||
@@ -127,6 +136,22 @@ 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)
|
||||
@@ -196,6 +221,22 @@ 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,39 +25,90 @@
|
||||
#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 : public detail::KeyBase<SecretKey, 32>
|
||||
class SecretKey
|
||||
{
|
||||
public:
|
||||
SecretKey() = delete;
|
||||
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(Slice const& slice)
|
||||
{
|
||||
if (slice.size() != buf_.size())
|
||||
LogicError("SecretKey::SecretKey: invalid size");
|
||||
SecretKey(std::array<std::uint8_t, 32> const& data);
|
||||
SecretKey(Slice const& slice);
|
||||
|
||||
std::copy_n(slice.data(), buf_.size(), buf_.data());
|
||||
std::uint8_t const*
|
||||
data() const
|
||||
{
|
||||
return buf_;
|
||||
}
|
||||
|
||||
explicit SecretKey(span_t const& data) noexcept : KeyBase(data)
|
||||
std::size_t
|
||||
size() const
|
||||
{
|
||||
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,37 +22,72 @@
|
||||
|
||||
#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 : public detail::KeyBase<Seed, 16>
|
||||
class Seed
|
||||
{
|
||||
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. This will attempt to securely erase the buffer. */
|
||||
/** Destroy the seed.
|
||||
The buffer will first be securely erased.
|
||||
*/
|
||||
~Seed();
|
||||
|
||||
explicit Seed(Slice const& slice)
|
||||
{
|
||||
if (slice.size() != buf_.size())
|
||||
LogicError("Seed::Seed: invalid size");
|
||||
/** Construct a seed */
|
||||
/** @{ */
|
||||
explicit Seed(Slice const& slice);
|
||||
explicit Seed(uint128 const& seed);
|
||||
/** @} */
|
||||
|
||||
std::copy_n(slice.data(), buf_.size(), buf_.data());
|
||||
std::uint8_t const*
|
||||
data() const
|
||||
{
|
||||
return buf_.data();
|
||||
}
|
||||
|
||||
explicit Seed(span_t seed) noexcept : KeyBase(seed)
|
||||
std::size_t
|
||||
size() const
|
||||
{
|
||||
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,14 +24,18 @@
|
||||
#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. */
|
||||
inline constexpr std::string_view systemName = "xahau";
|
||||
static inline std::string const&
|
||||
systemName()
|
||||
{
|
||||
static std::string const name = "xahau";
|
||||
return name;
|
||||
}
|
||||
|
||||
/** Configure the native currency. */
|
||||
|
||||
@@ -54,7 +58,12 @@ isLegalAmountSigned(XRPAmount const& amount)
|
||||
}
|
||||
|
||||
/* The currency code for the native currency. */
|
||||
inline constexpr std::string_view systemCurrencyCode = "XAH";
|
||||
static inline std::string const&
|
||||
systemCurrencyCode()
|
||||
{
|
||||
static std::string const code = "XAH";
|
||||
return code;
|
||||
}
|
||||
|
||||
/** The XRP ledger network's earliest allowed sequence */
|
||||
static constexpr std::uint32_t XRP_LEDGER_EARLIEST_SEQ{1U};
|
||||
|
||||
@@ -197,7 +197,6 @@ enum TEFcodes : TERUnderlyingType {
|
||||
tefINVALID_LEDGER_FIX_TYPE,
|
||||
tefPAST_MANIFEST_SEQ,
|
||||
tefREVOKED_MANIFEST,
|
||||
tefMANIFEST_KEY_IN_USE,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -152,26 +152,6 @@ 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
|
||||
{
|
||||
@@ -196,7 +176,6 @@ private:
|
||||
std::optional<STAmount> mDelivered;
|
||||
std::optional<STArray> mHookExecutions;
|
||||
std::optional<STArray> mHookEmissions;
|
||||
std::optional<uint256> mRandomData;
|
||||
|
||||
STArray mNodes;
|
||||
};
|
||||
|
||||
@@ -24,166 +24,84 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/SystemParameters.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
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, struct DirectoryTag>;
|
||||
|
||||
/** Currency is a hash representing a specific currency. */
|
||||
using Currency = base_uint<160, struct CurrencyTag>;
|
||||
|
||||
/** NodeID is a 160-bit hash representing one node. */
|
||||
using NodeID = base_uint<160, struct NodeIDTag>;
|
||||
|
||||
/** 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
|
||||
class CurrencyTag
|
||||
{
|
||||
return std::all_of(code.begin(), code.end(), [](char c) {
|
||||
return validIsoChars[static_cast<unsigned char>(c)];
|
||||
});
|
||||
}
|
||||
public:
|
||||
explicit CurrencyTag() = default;
|
||||
};
|
||||
|
||||
// The location (in bytes) of the 3 digit currency inside a 160-bit value
|
||||
inline constexpr std::size_t isoCodeOffset = 12;
|
||||
class DirectoryTag
|
||||
{
|
||||
public:
|
||||
explicit DirectoryTag() = default;
|
||||
};
|
||||
|
||||
// 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};
|
||||
class NodeIDTag
|
||||
{
|
||||
public:
|
||||
explicit NodeIDTag() = default;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/** XRP currency. */
|
||||
[[nodiscard]] constexpr Currency const&
|
||||
xrpCurrency() noexcept
|
||||
{
|
||||
return detail::xrpCurrency;
|
||||
}
|
||||
/** 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>;
|
||||
|
||||
[[nodiscard]] constexpr bool
|
||||
isXRP(Currency const& c) noexcept
|
||||
{
|
||||
return c == detail::xrpCurrency;
|
||||
}
|
||||
/** Currency is a hash representing a specific currency. */
|
||||
using Currency = base_uint<160, detail::CurrencyTag>;
|
||||
|
||||
/** NodeID is a 160-bit hash representing one node. */
|
||||
using NodeID = base_uint<160, detail::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 */
|
||||
using MPTID = base_uint<192>;
|
||||
|
||||
/** XRP currency. */
|
||||
Currency const&
|
||||
xrpCurrency();
|
||||
|
||||
/** A placeholder for empty currencies. */
|
||||
[[nodiscard]] constexpr Currency const&
|
||||
noCurrency() noexcept
|
||||
{
|
||||
return detail::noCurrency;
|
||||
}
|
||||
Currency const&
|
||||
noCurrency();
|
||||
|
||||
/** We deliberately disallow the currency that looks like "XAH" because too
|
||||
many people were using it instead of the correct XAH currency. */
|
||||
[[nodiscard]] constexpr Currency const&
|
||||
badCurrency() noexcept
|
||||
Currency const&
|
||||
badCurrency();
|
||||
|
||||
inline bool
|
||||
isXRP(Currency const& c)
|
||||
{
|
||||
return detail::badCurrency;
|
||||
return c == beast::zero;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool
|
||||
isBadCurrency(Currency const& c) noexcept
|
||||
inline bool
|
||||
isBadCurrency(Currency const& c)
|
||||
{
|
||||
// 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();
|
||||
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();
|
||||
}
|
||||
|
||||
/** 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);
|
||||
}
|
||||
/** Returns "", "XAH", or three letter ISO code. */
|
||||
std::string
|
||||
to_string(Currency const& c);
|
||||
|
||||
/** Tries to convert a string to a Currency, returns true on success.
|
||||
|
||||
@@ -192,29 +110,8 @@ to_string(Currency const& currency)
|
||||
will require very careful checking everywhere and may mean having
|
||||
to rewrite some unit test code.
|
||||
*/
|
||||
[[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;
|
||||
}
|
||||
bool
|
||||
to_currency(Currency&, std::string const&);
|
||||
|
||||
/** Tries to convert a string to a Currency, returns noCurrency() on failure.
|
||||
|
||||
@@ -222,14 +119,8 @@ to_currency(Currency& currency, std::string_view code) noexcept
|
||||
unfortunate; changing this will require very careful checking
|
||||
everywhere and may mean having to rewrite some unit test code.
|
||||
*/
|
||||
[[nodiscard]] constexpr Currency
|
||||
to_currency(std::string_view code) noexcept
|
||||
{
|
||||
if (Currency currency; to_currency(currency, code))
|
||||
return currency;
|
||||
|
||||
return noCurrency();
|
||||
}
|
||||
Currency
|
||||
to_currency(std::string const&);
|
||||
|
||||
inline std::ostream&
|
||||
operator<<(std::ostream& os, Currency const& x)
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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,11 +34,9 @@
|
||||
// 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::DefaultYes)
|
||||
XRPL_FIX (20260929, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(HookOnV2_1, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(OnChainManifests, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (HookMap, Supported::yes, VoteBehavior::DefaultYes)
|
||||
@@ -90,6 +88,7 @@ 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)
|
||||
@@ -165,7 +164,6 @@ 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,22 +142,6 @@ 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.
|
||||
@@ -196,26 +180,6 @@ 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
|
||||
@@ -298,7 +262,6 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
|
||||
{sfHookStateScale, soeOPTIONAL},
|
||||
{sfCron, soeOPTIONAL},
|
||||
{sfAMMID, soeOPTIONAL},
|
||||
{sfAppLoaderSequence, soeOPTIONAL},
|
||||
{sfManifestID, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
@@ -646,7 +609,6 @@ 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,8 +59,6 @@ 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)
|
||||
@@ -125,7 +123,6 @@ 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)
|
||||
@@ -213,9 +210,6 @@ 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)
|
||||
@@ -300,7 +294,6 @@ 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)
|
||||
@@ -390,7 +383,6 @@ 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)
|
||||
@@ -431,8 +423,6 @@ 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,7 +74,6 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet, ({
|
||||
{sfTickSize, soeOPTIONAL},
|
||||
{sfNFTokenMinter, soeOPTIONAL},
|
||||
{sfHookStateScale, soeOPTIONAL},
|
||||
{sfAppLoader, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type cancels an existing escrow. */
|
||||
@@ -501,22 +500,6 @@ 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, ({
|
||||
|
||||
@@ -100,15 +100,11 @@ 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
|
||||
@@ -175,7 +171,6 @@ 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
|
||||
@@ -243,7 +238,7 @@ JSS(closed); // out: NetworkOPs, LedgerToJson,
|
||||
// handlers/Ledger
|
||||
JSS(closed_ledger); // out: NetworkOPs
|
||||
JSS(cluster); // out: PeerImp
|
||||
JSS(code); // in: HookValidate; out: errors
|
||||
JSS(code); // out: errors
|
||||
JSS(command); // in: RPCHandler
|
||||
JSS(complete); // out: NetworkOPs, InboundLedger
|
||||
JSS(complete_ledgers); // out: NetworkOPs, PeerImp
|
||||
@@ -383,8 +378,6 @@ 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
|
||||
@@ -458,7 +451,6 @@ 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
|
||||
@@ -597,6 +589,7 @@ 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
|
||||
@@ -614,7 +607,6 @@ 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
|
||||
@@ -749,7 +741,6 @@ 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
|
||||
@@ -757,7 +748,6 @@ 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
|
||||
@@ -779,7 +769,6 @@ 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,7 +22,6 @@
|
||||
|
||||
#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>
|
||||
@@ -58,12 +57,6 @@ 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;
|
||||
@@ -100,18 +93,8 @@ 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&
|
||||
@@ -143,9 +126,6 @@ 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("pwa"))
|
||||
port_.protocol.count("ws2"))
|
||||
{
|
||||
reOpen();
|
||||
}
|
||||
|
||||
@@ -208,16 +208,6 @@ 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,9 +78,10 @@ public:
|
||||
error_code ec;
|
||||
|
||||
// Create UDP endpoint from port configuration
|
||||
boost::asio::ip::udp::endpoint udp_endpoint(port_.ip, port_.port);
|
||||
auto const addr = port_.ip.to_v4();
|
||||
boost::asio::ip::udp::endpoint udp_endpoint(addr, port_.port);
|
||||
|
||||
socket_.open(udp_endpoint.protocol(), ec);
|
||||
socket_.open(boost::asio::ip::udp::v4(), ec);
|
||||
if (ec)
|
||||
{
|
||||
JLOG(j_.error()) << "UDP socket open failed: " << ec.message();
|
||||
@@ -93,22 +94,17 @@ public:
|
||||
{
|
||||
JLOG(j_.error())
|
||||
<< "UDP set reuse_address failed: " << ec.message();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
socket_.bind(udp_endpoint, ec);
|
||||
if (ec)
|
||||
{
|
||||
// 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();
|
||||
JLOG(j_.error()) << "UDP socket bind failed: " << ec.message();
|
||||
return;
|
||||
}
|
||||
|
||||
JLOG(j_.info()) << "UDP listening on " << udp_endpoint;
|
||||
JLOG(j_.info()) << "UDP-RPC listening on " << udp_endpoint;
|
||||
}
|
||||
|
||||
endpoint_type
|
||||
@@ -173,7 +169,6 @@ 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);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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,6 +25,19 @@
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -42,7 +55,7 @@ LogicError(std::string const& s) noexcept
|
||||
// here.
|
||||
// For the above reasons, we want this contract to stand out.
|
||||
UNREACHABLE("LogicError", {{"message", s}});
|
||||
std::abort();
|
||||
detail::accessViolation();
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -33,7 +33,7 @@
|
||||
namespace beast::detail {
|
||||
|
||||
inline void
|
||||
setCurrentThreadNameImpl(char const* name)
|
||||
setCurrentThreadNameImpl(std::string_view name)
|
||||
{
|
||||
#if DEBUG && BOOST_COMP_MSVC
|
||||
// This technique is documented by Microsoft and works for all versions
|
||||
@@ -54,7 +54,7 @@ setCurrentThreadNameImpl(char const* name)
|
||||
THREADNAME_INFO ni;
|
||||
|
||||
ni.dwType = 0x1000;
|
||||
ni.szName = name;
|
||||
ni.szName = name.data();
|
||||
ni.dwThreadID = GetCurrentThreadId();
|
||||
ni.dwFlags = 0;
|
||||
|
||||
@@ -81,9 +81,9 @@ setCurrentThreadNameImpl(char const* name)
|
||||
namespace beast::detail {
|
||||
|
||||
inline void
|
||||
setCurrentThreadNameImpl(char const* name)
|
||||
setCurrentThreadNameImpl(std::string_view name)
|
||||
{
|
||||
pthread_setname_np(name);
|
||||
pthread_setname_np(name.data());
|
||||
}
|
||||
|
||||
} // namespace beast::detail
|
||||
@@ -95,9 +95,9 @@ setCurrentThreadNameImpl(char const* name)
|
||||
namespace beast::detail {
|
||||
|
||||
inline void
|
||||
setCurrentThreadNameImpl(char const* name)
|
||||
setCurrentThreadNameImpl(std::string_view name)
|
||||
{
|
||||
pthread_setname_np(pthread_self(), name);
|
||||
pthread_setname_np(pthread_self(), name.data());
|
||||
}
|
||||
|
||||
} // namespace beast::detail
|
||||
@@ -119,7 +119,7 @@ void
|
||||
setCurrentThreadName(std::string_view name)
|
||||
{
|
||||
detail::threadName = name;
|
||||
detail::setCurrentThreadNameImpl(detail::threadName.c_str());
|
||||
detail::setCurrentThreadNameImpl(name);
|
||||
}
|
||||
|
||||
} // namespace beast
|
||||
@@ -29,9 +29,7 @@ bool
|
||||
is_private(AddressV6 const& addr)
|
||||
{
|
||||
return (
|
||||
addr.is_link_local() || // fe80::/10
|
||||
addr.is_loopback() || // ::1
|
||||
((addr.to_bytes()[0] & 0xfe) == 0xfc) || // fc00::/7 (all ULA)
|
||||
(addr.to_bytes()[0] & 0xfd) || // TODO fc00::/8 too ?
|
||||
(addr.is_v4_mapped() &&
|
||||
is_private(boost::asio::ip::make_address_v4(
|
||||
boost::asio::ip::v4_mapped, addr))));
|
||||
@@ -40,7 +38,8 @@ is_private(AddressV6 const& addr)
|
||||
bool
|
||||
is_public(AddressV6 const& addr)
|
||||
{
|
||||
return !is_private(addr) && !addr.is_multicast() && !addr.is_unspecified();
|
||||
// TODO is this correct?
|
||||
return !is_private(addr) && !addr.is_multicast();
|
||||
}
|
||||
|
||||
} // namespace IP
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
#include <xrpl/json/json_writer.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace Json {
|
||||
|
||||
const Value Value::null;
|
||||
@@ -674,9 +672,7 @@ Value::isConvertibleTo(ValueType other) const
|
||||
(other == intValue && value_.real_ >= minInt &&
|
||||
value_.real_ <= maxInt) ||
|
||||
(other == uintValue && value_.real_ >= 0 &&
|
||||
value_.real_ <= maxUInt &&
|
||||
std::fabs(round(value_.real_) - value_.real_) <
|
||||
std::numeric_limits<double>::epsilon()) ||
|
||||
value_.real_ <= maxUInt) ||
|
||||
other == realValue || other == stringValue ||
|
||||
other == booleanValue;
|
||||
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
@@ -113,10 +113,7 @@ 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},
|
||||
{rpcENTRY_NOT_FOUND, "entryNotFound", "Entry not found.", 400},
|
||||
{rpcUNEXPECTED_LEDGER_TYPE, "unexpectedLedgerType", "Unexpected ledger type.", 400},
|
||||
};
|
||||
{rpcTX_SIGNED, "transactionSigned", "Transaction should not be signed.", 400}};
|
||||
// clang-format on
|
||||
|
||||
// Sort and validate unorderedErrorInfos at compile time. Should be
|
||||
|
||||
@@ -81,7 +81,6 @@ 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',
|
||||
@@ -91,10 +90,8 @@ 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.
|
||||
@@ -279,13 +276,6 @@ 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
|
||||
{
|
||||
@@ -623,12 +613,6 @@ 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
|
||||
{
|
||||
@@ -693,14 +677,6 @@ 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,14 +143,6 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfAccount, soeOPTIONAL},
|
||||
});
|
||||
|
||||
add(sfRandomDigestEntry.jsonName,
|
||||
sfRandomDigestEntry.getCode(),
|
||||
{
|
||||
{sfPublicKey, soeREQUIRED},
|
||||
{sfNextRandomDigest, soeREQUIRED},
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
});
|
||||
|
||||
add(sfAmountEntry.jsonName,
|
||||
sfAmountEntry.getCode(),
|
||||
{
|
||||
|
||||
@@ -171,6 +171,53 @@ 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,8 +684,7 @@ 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 == ttENTROPY;
|
||||
tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -95,10 +95,12 @@ STXChainBridge::STXChainBridge(SField const& name, Json::Value const& v)
|
||||
};
|
||||
checkExtra(v);
|
||||
|
||||
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];
|
||||
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()];
|
||||
|
||||
if (!lockingChainDoorStr.isString())
|
||||
{
|
||||
@@ -156,10 +158,10 @@ Json::Value
|
||||
STXChainBridge::getJson(JsonOptions jo) const
|
||||
{
|
||||
Json::Value v;
|
||||
v[jss::LockingChainDoor] = lockingChainDoor_.getJson(jo);
|
||||
v[jss::LockingChainIssue] = lockingChainIssue_.getJson(jo);
|
||||
v[jss::IssuingChainDoor] = issuingChainDoor_.getJson(jo);
|
||||
v[jss::IssuingChainIssue] = issuingChainIssue_.getJson(jo);
|
||||
v[sfLockingChainDoor.getJsonName()] = lockingChainDoor_.getJson(jo);
|
||||
v[sfLockingChainIssue.getJsonName()] = lockingChainIssue_.getJson(jo);
|
||||
v[sfIssuingChainDoor.getJsonName()] = issuingChainDoor_.getJson(jo);
|
||||
v[sfIssuingChainIssue.getJsonName()] = issuingChainIssue_.getJson(jo);
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/detail/secp256k1.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <ed25519.h>
|
||||
|
||||
@@ -33,7 +32,25 @@ namespace ripple {
|
||||
|
||||
SecretKey::~SecretKey()
|
||||
{
|
||||
secure_erase(buf_.data(), buf_.size());
|
||||
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);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
@@ -197,6 +214,7 @@ 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(),
|
||||
@@ -208,7 +226,7 @@ signDigest(PublicKey const& pk, SecretKey const& sk, uint256 const& digest)
|
||||
LogicError("sign: secp256k1_ecdsa_sign failed");
|
||||
|
||||
unsigned char sig[72];
|
||||
auto len = sizeof(sig);
|
||||
size_t len = sizeof(sig);
|
||||
if (secp256k1_ecdsa_signature_serialize_der(
|
||||
secp256k1Context(), sig, &len, &sig_imp) != 1)
|
||||
LogicError("sign: secp256k1_ecdsa_signature_serialize_der failed");
|
||||
|
||||
@@ -39,12 +39,28 @@ 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, crypto_prng());
|
||||
Seed seed(buffer);
|
||||
beast::rngfill(buffer.data(), buffer.size(), crypto_prng());
|
||||
Seed seed(makeSlice(buffer));
|
||||
secure_erase(buffer.data(), buffer.size());
|
||||
return seed;
|
||||
}
|
||||
@@ -101,7 +117,7 @@ parseGenericSeed(std::string const& str, bool rfc1751)
|
||||
if (RFC1751::getKeyFromEnglish(key, str) == 1)
|
||||
{
|
||||
Blob const blob(key.rbegin(), key.rend());
|
||||
return Seed{makeSlice(blob)};
|
||||
return Seed{uint128{blob}};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,7 +153,6 @@ 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."),
|
||||
|
||||
@@ -49,9 +49,6 @@ 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)
|
||||
@@ -78,9 +75,6 @@ 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)
|
||||
@@ -251,9 +245,6 @@ TxMeta::getAsObject() const
|
||||
if (hasHookEmissions())
|
||||
metaData.setFieldArray(sfHookEmissions, getHookEmissions());
|
||||
|
||||
if (hasRandomData())
|
||||
metaData.setFieldH256(sfRandomData, getRandomData());
|
||||
|
||||
return metaData;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
@@ -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: " + std::string(systemName) + "-json-rpc/v1");
|
||||
output("Server: " + 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: " + std::string(systemName) + "-json-rpc/");
|
||||
output("Server: " + systemName() + "-json-rpc/");
|
||||
output(BuildInfo::getFullVersionString());
|
||||
output(
|
||||
"\r\n"
|
||||
|
||||
@@ -21,10 +21,7 @@
|
||||
#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>
|
||||
@@ -69,14 +66,6 @@ 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:";
|
||||
@@ -322,64 +311,6 @@ 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,79 +1271,6 @@ 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)
|
||||
{
|
||||
@@ -1367,7 +1294,6 @@ public:
|
||||
testBadConfig();
|
||||
testEnableVeto();
|
||||
testHasUnsupported();
|
||||
testIgnoredUnsupported();
|
||||
testFeature(all - fixMajorityCalc);
|
||||
testFeature(all);
|
||||
}
|
||||
|
||||
@@ -578,25 +578,6 @@ 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);
|
||||
|
||||
@@ -3043,91 +3043,6 @@ 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)
|
||||
{
|
||||
@@ -3826,174 +3741,6 @@ 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)
|
||||
{
|
||||
@@ -5211,7 +4958,6 @@ public:
|
||||
test_fee_base(features);
|
||||
|
||||
test_otxn_field(features);
|
||||
test_otxn_field_emit_failure(features);
|
||||
|
||||
test_ledger_keylet(features);
|
||||
|
||||
@@ -5266,10 +5012,6 @@ 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);
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -24,7 +24,6 @@
|
||||
#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>
|
||||
@@ -976,482 +975,9 @@ 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");
|
||||
|
||||
@@ -1,400 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
@@ -1,111 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
@@ -33,11 +33,9 @@
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -95,27 +93,6 @@ 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,
|
||||
@@ -1176,127 +1153,6 @@ 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.
|
||||
@@ -1446,8 +1302,6 @@ public:
|
||||
testNullFactoryDropsWrites();
|
||||
testRotate();
|
||||
testPinnedRangeRestoreRequiresPinnedData();
|
||||
testPinnedRangeRestoreEmpty();
|
||||
testPinnedRangeRestoreTrimsMissingTail();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6146,15 +6146,6 @@ 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
|
||||
|
||||
@@ -2134,118 +2134,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
/// Rename and remove the HookName with HookName-only updates
|
||||
Env env{*this, features};
|
||||
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
// Create with hook name
|
||||
auto jvh = hso(accept_wasm);
|
||||
jvh[jss::HookName] = "41424344";
|
||||
env(ripple::test::jtx::hook(alice, {{jvh}}, 0),
|
||||
M("Create named hook"),
|
||||
HSFEE);
|
||||
env.close();
|
||||
|
||||
auto const hookNameOnlyUpdate = [&](std::string const& name) {
|
||||
Json::Value jv;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[jss::TransactionType] = jss::SetHook;
|
||||
jv[jss::Flags] = 0;
|
||||
jv[jss::Hooks] = Json::Value{Json::arrayValue};
|
||||
Json::Value iv;
|
||||
iv[jss::HookName] = name;
|
||||
jv[jss::Hooks][0U][jss::Hook] = iv;
|
||||
return jv;
|
||||
};
|
||||
|
||||
// Rename: update carrying only a non-empty sfHookName
|
||||
env(hookNameOnlyUpdate("4142434445"),
|
||||
M("Rename hook with a HookName-only update"),
|
||||
HSFEE);
|
||||
env.close();
|
||||
{
|
||||
auto const hooks = env.le(keylet::hook(alice));
|
||||
BEAST_EXPECT(hooks && hooks->isFieldPresent(sfHooks));
|
||||
auto const& hooksArray = hooks->getFieldArray(sfHooks);
|
||||
BEAST_EXPECT(hooksArray.size() == 1);
|
||||
BEAST_EXPECT(hooksArray[0].isFieldPresent(sfHookName));
|
||||
BEAST_EXPECT(
|
||||
strHex(hooksArray[0].getFieldVL(sfHookName)) ==
|
||||
"4142434445");
|
||||
}
|
||||
|
||||
// Remove: update carrying only an empty sfHookName
|
||||
env(hookNameOnlyUpdate(""),
|
||||
M("Remove hook name with a HookName-only update"),
|
||||
HSFEE);
|
||||
|
||||
// In the same open ledger as the removal: the open view hands
|
||||
// the next transaction the very sfHooks entry the SetHook just
|
||||
// modified, so the fee calculation and the hook chain must
|
||||
// tolerate the removed name. This is the path that crashed the
|
||||
// node at submit, and crashed every validator replaying the
|
||||
// pair while building the ledger.
|
||||
{
|
||||
auto jv = invoke::invoke(alice);
|
||||
auto const expectedFee =
|
||||
calculateBaseFee(*env.current(), *env.jt(jv).stx);
|
||||
BEAST_EXPECT(expectedFee == drops(19));
|
||||
env(jv,
|
||||
M("Invoke in the same open ledger as the removal"),
|
||||
HSFEE);
|
||||
}
|
||||
env.close();
|
||||
{
|
||||
auto const hookExecutions =
|
||||
env.meta()->getFieldArray(sfHookExecutions);
|
||||
BEAST_EXPECT(hookExecutions.size() == 1);
|
||||
BEAST_EXPECT(
|
||||
hookExecutions[0].getFieldH256(sfHookHash) == accept_hash);
|
||||
}
|
||||
{
|
||||
auto const hooks = env.le(keylet::hook(alice));
|
||||
BEAST_EXPECT(hooks && hooks->isFieldPresent(sfHooks));
|
||||
auto const& hooksArray = hooks->getFieldArray(sfHooks);
|
||||
BEAST_EXPECT(hooksArray.size() == 1);
|
||||
BEAST_EXPECT(!hooksArray[0].isFieldPresent(sfHookName));
|
||||
}
|
||||
|
||||
// The now non-named hook must still be charged for and
|
||||
// executed by transactions in later ledgers, where the entry
|
||||
// is re-read from its serialized form.
|
||||
{
|
||||
auto jv = invoke::invoke(alice);
|
||||
auto const expectedFee =
|
||||
calculateBaseFee(*env.current(), *env.jt(jv).stx);
|
||||
BEAST_EXPECT(expectedFee == drops(19));
|
||||
env(jv, M("Invoke after removing the hook name"), HSFEE);
|
||||
env.close();
|
||||
auto const hookExecutions =
|
||||
env.meta()->getFieldArray(sfHookExecutions);
|
||||
BEAST_EXPECT(hookExecutions.size() == 1);
|
||||
BEAST_EXPECT(
|
||||
hookExecutions[0].getFieldH256(sfHookHash) == accept_hash);
|
||||
}
|
||||
|
||||
// Removing the name of a hook that has none must be a no-op,
|
||||
// not a thrown FieldErr (-> tefEXCEPTION): makeFieldAbsent on
|
||||
// the untemplated entry throws when the field is missing.
|
||||
env(hookNameOnlyUpdate(""),
|
||||
M("Remove hook name again (no-op)"),
|
||||
HSFEE);
|
||||
env.close();
|
||||
{
|
||||
auto const hooks = env.le(keylet::hook(alice));
|
||||
BEAST_EXPECT(hooks && hooks->isFieldPresent(sfHooks));
|
||||
auto const& hooksArray = hooks->getFieldArray(sfHooks);
|
||||
BEAST_EXPECT(hooksArray.size() == 1);
|
||||
BEAST_EXPECT(!hooksArray[0].isFieldPresent(sfHookName));
|
||||
}
|
||||
}
|
||||
|
||||
// Install named hook
|
||||
auto jvh = hso(accept_wasm);
|
||||
jvh[jss::HookName] = "41424344";
|
||||
@@ -4787,218 +4675,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
test_emit_failure_callback(FeatureBitset features)
|
||||
{
|
||||
testcase("Test emit failure callback");
|
||||
using namespace jtx;
|
||||
|
||||
// An emitted txn that is never applied expires into a ttEMIT_FAILURE
|
||||
// cleanup pseudo, which removes its ltEMITTED_TXN entry and then
|
||||
// runs the emitting Hook's cbak. Before fix20261005 the callback
|
||||
// re-read that (already erased) entry and dereferenced null, taking
|
||||
// the process down, so only the fixed behaviour can be run here.
|
||||
Env env{*this, features | fix20261005};
|
||||
|
||||
auto const alice = Account{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
TestHook hook_wasm = wasm[R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g (uint32_t id, uint32_t maxiter);
|
||||
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t emit (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
extern int64_t hook_account (uint32_t write_ptr, uint32_t write_len);
|
||||
extern int64_t etxn_reserve (uint32_t);
|
||||
extern int64_t etxn_fee_base (uint32_t read_ptr, uint32_t read_len);
|
||||
extern int64_t etxn_details (uint32_t write_ptr, uint32_t write_len);
|
||||
extern int64_t ledger_seq (void);
|
||||
extern int64_t otxn_id (uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t otxn_type (void);
|
||||
extern int64_t otxn_field (uint32_t write_ptr, uint32_t write_len, uint32_t field_id);
|
||||
extern int64_t state_set (uint32_t, uint32_t, uint32_t, uint32_t);
|
||||
#define SBUF(x) (uint32_t)(x), sizeof(x)
|
||||
#define ASSERT(x)\
|
||||
if (!(x))\
|
||||
rollback((uint32_t)#x, sizeof(#x), __LINE__);
|
||||
#define DOESNT_EXIST (-5)
|
||||
#define ttINVOKE 99
|
||||
#define sfAccount ((8U << 16U) + 1U)
|
||||
#define sfLedgerSequence ((2U << 16U) + 6U)
|
||||
|
||||
uint8_t key[32] = { [31] = 1U };
|
||||
|
||||
// an Invoke from the hook account, emitted with a callback
|
||||
uint8_t txn[229] =
|
||||
{
|
||||
/* size, upto, field name */
|
||||
/* 3, 0, tt = Invoke */ 0x12U, 0x00U, 0x63U,
|
||||
/* 5, 3, flags */ 0x22U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
/* 5, 8, sequence */ 0x24U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
/* 6, 13, firstledgersequence */ 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
/* 6, 19, lastledgersequence */ 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
/* 9, 25, fee */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
/* 35, 34, signingpubkey */ 0x73U, 0x21U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
/* 22, 69, account */ 0x81U, 0x14U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
/* 138, 91, emit details */
|
||||
/* 0, 229, */
|
||||
};
|
||||
#define FLS_OUT (txn + 15U)
|
||||
#define LLS_OUT (txn + 21U)
|
||||
#define FEE_OUT (txn + 26U)
|
||||
#define ACCOUNT_OUT (txn + 71U)
|
||||
#define EMIT_OUT (txn + 91U)
|
||||
#define SET_UINT32(ptr, value)\
|
||||
{\
|
||||
uint32_t v = (value);\
|
||||
(ptr)[0] = v >> 24U; (ptr)[1] = v >> 16U; (ptr)[2] = v >> 8U; (ptr)[3] = v;\
|
||||
}
|
||||
#define SET_NATIVE_AMOUNT(ptr, amount)\
|
||||
{\
|
||||
uint64_t a = (amount);\
|
||||
(ptr)[0] = 0x40U + ((a >> 56U) & 0x3FU);\
|
||||
(ptr)[1] = a >> 48U; (ptr)[2] = a >> 40U; (ptr)[3] = a >> 32U;\
|
||||
(ptr)[4] = a >> 24U; (ptr)[5] = a >> 16U; (ptr)[6] = a >> 8U; (ptr)[7] = a;\
|
||||
}
|
||||
#define ACC_EQ(a, b)\
|
||||
(*(uint64_t*)(a) == *(uint64_t*)(b) &&\
|
||||
*(uint64_t*)((a) + 8U) == *(uint64_t*)((b) + 8U) &&\
|
||||
*(uint32_t*)((a) + 16U) == *(uint32_t*)((b) + 16U))
|
||||
|
||||
int64_t cbak(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
// 1 = the emitted txn failed (ttEMIT_FAILURE)
|
||||
ASSERT(r == 1);
|
||||
|
||||
// the otxn is the original emitted txn, not the pseudo
|
||||
ASSERT(otxn_type() == ttINVOKE);
|
||||
|
||||
uint8_t me[20];
|
||||
ASSERT(hook_account(SBUF(me)) == 20);
|
||||
uint8_t acc[20];
|
||||
ASSERT(otxn_field(SBUF(acc), sfAccount) == 20);
|
||||
ASSERT(ACC_EQ(acc, me));
|
||||
|
||||
// carried by the pseudo but not by the emitted txn
|
||||
ASSERT(otxn_field(0, 0, sfLedgerSequence) == DOESNT_EXIST);
|
||||
|
||||
// record the id of the emitted txn that failed
|
||||
uint8_t id[32];
|
||||
ASSERT(otxn_id(SBUF(id), 0) == 32);
|
||||
ASSERT(state_set(SBUF(id), SBUF(key)) == 32);
|
||||
|
||||
return accept(0,0,0);
|
||||
}
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
ASSERT(etxn_reserve(1) == 1);
|
||||
|
||||
// FirstLedgerSequence is the current ledger, so by the time
|
||||
// the emission directory is processed for the next ledger it
|
||||
// has already passed: the txn is never applied and expires
|
||||
// once LastLedgerSequence is behind the ledger
|
||||
uint32_t seq = (uint32_t)ledger_seq();
|
||||
SET_UINT32(FLS_OUT, seq);
|
||||
SET_UINT32(LLS_OUT, seq + 1);
|
||||
ASSERT(hook_account(ACCOUNT_OUT, 20) == 20);
|
||||
ASSERT(etxn_details(EMIT_OUT, 138U) == 138);
|
||||
int64_t fee = etxn_fee_base(SBUF(txn));
|
||||
ASSERT(fee > 0);
|
||||
SET_NATIVE_AMOUNT(FEE_OUT, fee);
|
||||
|
||||
uint8_t emithash[32];
|
||||
ASSERT(emit(SBUF(emithash), SBUF(txn)) == 32);
|
||||
|
||||
return accept(0,0,0);
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
HASH_WASM(hook);
|
||||
|
||||
env(ripple::test::jtx::hook(alice, {{hso(hook_wasm, overrideFlag)}}, 0),
|
||||
M("set emit failure callback"),
|
||||
HSFEE);
|
||||
env.close();
|
||||
|
||||
env(invoke::invoke(alice),
|
||||
M("emit failure callback: emit"),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
// the emitted txn, from the ltEMITTED_TXN this created
|
||||
std::optional<uint256> emittedID;
|
||||
{
|
||||
auto const meta = env.meta(); // closes the ledger
|
||||
BEAST_REQUIRE(meta);
|
||||
for (auto const& node : meta->getFieldArray(sfAffectedNodes))
|
||||
{
|
||||
if (node.getFName() != sfCreatedNode ||
|
||||
node.getFieldU16(sfLedgerEntryType) != ltEMITTED_TXN)
|
||||
continue;
|
||||
auto const& nf = const_cast<ripple::STObject&>(node)
|
||||
.getField(sfNewFields)
|
||||
.downcast<STObject>();
|
||||
auto const& et = const_cast<ripple::STObject&>(nf)
|
||||
.getField(sfEmittedTxn)
|
||||
.downcast<STObject>();
|
||||
Serializer s;
|
||||
et.add(s);
|
||||
SerialIter sit(s.slice());
|
||||
emittedID = STTx(sit).getTransactionID();
|
||||
}
|
||||
}
|
||||
BEAST_REQUIRE(emittedID);
|
||||
auto const emittedKeylet = keylet::emittedTxn(*emittedID);
|
||||
BEAST_REQUIRE(env.le(emittedKeylet));
|
||||
|
||||
// close until the cleanup pseudo has been applied
|
||||
std::shared_ptr<STObject const> failureMeta;
|
||||
for (int i = 0; i < 5 && !failureMeta; ++i)
|
||||
{
|
||||
env.close();
|
||||
for (auto const& [tx, meta] : env.closed()->txs)
|
||||
{
|
||||
// the emitted txn itself never makes it into a ledger
|
||||
BEAST_EXPECT(tx->getTransactionID() != *emittedID);
|
||||
if (tx->getTxnType() == ttEMIT_FAILURE &&
|
||||
tx->getFieldH256(sfTransactionHash) == *emittedID)
|
||||
failureMeta = meta;
|
||||
}
|
||||
}
|
||||
BEAST_REQUIRE(failureMeta);
|
||||
|
||||
// the cleanup removed the entry and its directory slot
|
||||
BEAST_EXPECT(!env.le(emittedKeylet));
|
||||
BEAST_EXPECT(!env.le(keylet::emittedDir()));
|
||||
|
||||
// the callback ran to accept() with the original emitted txn
|
||||
BEAST_REQUIRE(failureMeta->isFieldPresent(sfHookExecutions));
|
||||
auto const hookExecutions =
|
||||
failureMeta->getFieldArray(sfHookExecutions);
|
||||
BEAST_REQUIRE(hookExecutions.size() == 1);
|
||||
BEAST_EXPECT(hookExecutions[0].getFieldH256(sfHookHash) == hook_hash);
|
||||
BEAST_EXPECT(
|
||||
hookExecutions[0].getAccountID(sfHookAccount) == alice.id());
|
||||
BEAST_EXPECT(hookExecutions[0].getFieldU64(sfHookReturnCode) == 0);
|
||||
BEAST_EXPECT(hookExecutions[0].getFieldU8(sfHookResult) == 3);
|
||||
|
||||
uint256 key;
|
||||
key.data()[31] = 1;
|
||||
auto const state =
|
||||
env.le(keylet::hookState(alice.id(), key, uint256{beast::zero}));
|
||||
BEAST_REQUIRE(state);
|
||||
BEAST_EXPECT(
|
||||
state->getFieldVL(sfHookStateData) ==
|
||||
Blob(emittedID->begin(), emittedID->end()));
|
||||
}
|
||||
|
||||
void
|
||||
test_etxn_details(FeatureBitset features)
|
||||
{
|
||||
@@ -16516,7 +16192,6 @@ public:
|
||||
testGuards(features);
|
||||
|
||||
test_emit(features); //
|
||||
test_emit_failure_callback(features);
|
||||
test_prepare(features);
|
||||
// test_etxn_burden(features); // tested above
|
||||
// test_etxn_generation(features); // tested above
|
||||
|
||||
@@ -35007,310 +35007,6 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
|
||||
0x65U, 0x64U, 0x2EU, 0x00U,
|
||||
}},
|
||||
|
||||
{R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g (uint32_t id, uint32_t maxiter);
|
||||
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t emit (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
extern int64_t hook_account (uint32_t write_ptr, uint32_t write_len);
|
||||
extern int64_t etxn_reserve (uint32_t);
|
||||
extern int64_t etxn_fee_base (uint32_t read_ptr, uint32_t read_len);
|
||||
extern int64_t etxn_details (uint32_t write_ptr, uint32_t write_len);
|
||||
extern int64_t ledger_seq (void);
|
||||
extern int64_t otxn_id (uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t otxn_type (void);
|
||||
extern int64_t otxn_field (uint32_t write_ptr, uint32_t write_len, uint32_t field_id);
|
||||
extern int64_t state_set (uint32_t, uint32_t, uint32_t, uint32_t);
|
||||
#define SBUF(x) (uint32_t)(x), sizeof(x)
|
||||
#define ASSERT(x)\
|
||||
if (!(x))\
|
||||
rollback((uint32_t)#x, sizeof(#x), __LINE__);
|
||||
#define DOESNT_EXIST (-5)
|
||||
#define ttINVOKE 99
|
||||
#define sfAccount ((8U << 16U) + 1U)
|
||||
#define sfLedgerSequence ((2U << 16U) + 6U)
|
||||
|
||||
uint8_t key[32] = { [31] = 1U };
|
||||
|
||||
// an Invoke from the hook account, emitted with a callback
|
||||
uint8_t txn[229] =
|
||||
{
|
||||
/* size, upto, field name */
|
||||
/* 3, 0, tt = Invoke */ 0x12U, 0x00U, 0x63U,
|
||||
/* 5, 3, flags */ 0x22U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
/* 5, 8, sequence */ 0x24U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
/* 6, 13, firstledgersequence */ 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
/* 6, 19, lastledgersequence */ 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
/* 9, 25, fee */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
/* 35, 34, signingpubkey */ 0x73U, 0x21U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
/* 22, 69, account */ 0x81U, 0x14U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
/* 138, 91, emit details */
|
||||
/* 0, 229, */
|
||||
};
|
||||
#define FLS_OUT (txn + 15U)
|
||||
#define LLS_OUT (txn + 21U)
|
||||
#define FEE_OUT (txn + 26U)
|
||||
#define ACCOUNT_OUT (txn + 71U)
|
||||
#define EMIT_OUT (txn + 91U)
|
||||
#define SET_UINT32(ptr, value)\
|
||||
{\
|
||||
uint32_t v = (value);\
|
||||
(ptr)[0] = v >> 24U; (ptr)[1] = v >> 16U; (ptr)[2] = v >> 8U; (ptr)[3] = v;\
|
||||
}
|
||||
#define SET_NATIVE_AMOUNT(ptr, amount)\
|
||||
{\
|
||||
uint64_t a = (amount);\
|
||||
(ptr)[0] = 0x40U + ((a >> 56U) & 0x3FU);\
|
||||
(ptr)[1] = a >> 48U; (ptr)[2] = a >> 40U; (ptr)[3] = a >> 32U;\
|
||||
(ptr)[4] = a >> 24U; (ptr)[5] = a >> 16U; (ptr)[6] = a >> 8U; (ptr)[7] = a;\
|
||||
}
|
||||
#define ACC_EQ(a, b)\
|
||||
(*(uint64_t*)(a) == *(uint64_t*)(b) &&\
|
||||
*(uint64_t*)((a) + 8U) == *(uint64_t*)((b) + 8U) &&\
|
||||
*(uint32_t*)((a) + 16U) == *(uint32_t*)((b) + 16U))
|
||||
|
||||
int64_t cbak(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
// 1 = the emitted txn failed (ttEMIT_FAILURE)
|
||||
ASSERT(r == 1);
|
||||
|
||||
// the otxn is the original emitted txn, not the pseudo
|
||||
ASSERT(otxn_type() == ttINVOKE);
|
||||
|
||||
uint8_t me[20];
|
||||
ASSERT(hook_account(SBUF(me)) == 20);
|
||||
uint8_t acc[20];
|
||||
ASSERT(otxn_field(SBUF(acc), sfAccount) == 20);
|
||||
ASSERT(ACC_EQ(acc, me));
|
||||
|
||||
// carried by the pseudo but not by the emitted txn
|
||||
ASSERT(otxn_field(0, 0, sfLedgerSequence) == DOESNT_EXIST);
|
||||
|
||||
// record the id of the emitted txn that failed
|
||||
uint8_t id[32];
|
||||
ASSERT(otxn_id(SBUF(id), 0) == 32);
|
||||
ASSERT(state_set(SBUF(id), SBUF(key)) == 32);
|
||||
|
||||
return accept(0,0,0);
|
||||
}
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
ASSERT(etxn_reserve(1) == 1);
|
||||
|
||||
// FirstLedgerSequence is the current ledger, so by the time
|
||||
// the emission directory is processed for the next ledger it
|
||||
// has already passed: the txn is never applied and expires
|
||||
// once LastLedgerSequence is behind the ledger
|
||||
uint32_t seq = (uint32_t)ledger_seq();
|
||||
SET_UINT32(FLS_OUT, seq);
|
||||
SET_UINT32(LLS_OUT, seq + 1);
|
||||
ASSERT(hook_account(ACCOUNT_OUT, 20) == 20);
|
||||
ASSERT(etxn_details(EMIT_OUT, 138U) == 138);
|
||||
int64_t fee = etxn_fee_base(SBUF(txn));
|
||||
ASSERT(fee > 0);
|
||||
SET_NATIVE_AMOUNT(FEE_OUT, fee);
|
||||
|
||||
uint8_t emithash[32];
|
||||
ASSERT(emit(SBUF(emithash), SBUF(txn)) == 32);
|
||||
|
||||
return accept(0,0,0);
|
||||
}
|
||||
)[test.hook]",
|
||||
{
|
||||
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x2CU,
|
||||
0x07U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU,
|
||||
0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x00U, 0x01U, 0x7EU, 0x60U, 0x02U,
|
||||
0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U,
|
||||
0x7EU, 0x60U, 0x04U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U,
|
||||
0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, 0xCEU, 0x01U, 0x0DU, 0x03U, 0x65U,
|
||||
0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU,
|
||||
0x76U, 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU,
|
||||
0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x09U, 0x6FU, 0x74U, 0x78U,
|
||||
0x6EU, 0x5FU, 0x74U, 0x79U, 0x70U, 0x65U, 0x00U, 0x02U, 0x03U, 0x65U,
|
||||
0x6EU, 0x76U, 0x0CU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U, 0x63U,
|
||||
0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x00U, 0x03U, 0x03U, 0x65U, 0x6EU,
|
||||
0x76U, 0x0AU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x66U, 0x69U, 0x65U,
|
||||
0x6CU, 0x64U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x07U, 0x6FU,
|
||||
0x74U, 0x78U, 0x6EU, 0x5FU, 0x69U, 0x64U, 0x00U, 0x04U, 0x03U, 0x65U,
|
||||
0x6EU, 0x76U, 0x09U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U,
|
||||
0x65U, 0x74U, 0x00U, 0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U,
|
||||
0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU,
|
||||
0x76U, 0x0CU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x72U, 0x65U, 0x73U,
|
||||
0x65U, 0x72U, 0x76U, 0x65U, 0x00U, 0x06U, 0x03U, 0x65U, 0x6EU, 0x76U,
|
||||
0x0AU, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x5FU, 0x73U, 0x65U,
|
||||
0x71U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU, 0x65U, 0x74U,
|
||||
0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U, 0x74U, 0x61U, 0x69U, 0x6CU, 0x73U,
|
||||
0x00U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0DU, 0x65U, 0x74U, 0x78U,
|
||||
0x6EU, 0x5FU, 0x66U, 0x65U, 0x65U, 0x5FU, 0x62U, 0x61U, 0x73U, 0x65U,
|
||||
0x00U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x65U, 0x6DU, 0x69U,
|
||||
0x74U, 0x00U, 0x05U, 0x03U, 0x03U, 0x02U, 0x06U, 0x06U, 0x05U, 0x03U,
|
||||
0x01U, 0x00U, 0x02U, 0x06U, 0x08U, 0x01U, 0x7FU, 0x01U, 0x41U, 0x90U,
|
||||
0x8DU, 0x04U, 0x0BU, 0x07U, 0x0FU, 0x02U, 0x04U, 0x63U, 0x62U, 0x61U,
|
||||
0x6BU, 0x00U, 0x0DU, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x0EU,
|
||||
0x0AU, 0x85U, 0x87U, 0x00U, 0x02U, 0x88U, 0x83U, 0x00U, 0x02U, 0x01U,
|
||||
0x7FU, 0x01U, 0x7EU, 0x23U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U,
|
||||
0xE0U, 0x00U, 0x6BU, 0x22U, 0x01U, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x1AU, 0x02U, 0x40U, 0x20U, 0x00U, 0x41U, 0x01U, 0x46U, 0x0DU,
|
||||
0x00U, 0x41U, 0xD5U, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x07U, 0x42U,
|
||||
0xC5U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU,
|
||||
0x02U, 0x40U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0xE3U,
|
||||
0x00U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xB3U, 0x88U, 0x80U, 0x80U, 0x00U,
|
||||
0x41U, 0x18U, 0x42U, 0xC8U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0xC0U, 0x00U,
|
||||
0x6AU, 0x41U, 0x14U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U,
|
||||
0x14U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xBCU, 0x8AU, 0x80U, 0x80U, 0x00U,
|
||||
0x41U, 0x1DU, 0x42U, 0xCBU, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x20U, 0x6AU,
|
||||
0x41U, 0x14U, 0x41U, 0x81U, 0x80U, 0x20U, 0x10U, 0x84U, 0x80U, 0x80U,
|
||||
0x80U, 0x00U, 0x42U, 0x14U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xF1U, 0x89U,
|
||||
0x80U, 0x80U, 0x00U, 0x41U, 0x27U, 0x42U, 0xCDU, 0x00U, 0x10U, 0x81U,
|
||||
0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x02U, 0x40U,
|
||||
0x20U, 0x01U, 0x29U, 0x03U, 0x20U, 0x20U, 0x01U, 0x29U, 0x03U, 0x40U,
|
||||
0x52U, 0x0DU, 0x00U, 0x20U, 0x01U, 0x29U, 0x03U, 0x28U, 0x20U, 0x01U,
|
||||
0x29U, 0x03U, 0x48U, 0x52U, 0x0DU, 0x00U, 0x20U, 0x01U, 0x28U, 0x02U,
|
||||
0x30U, 0x20U, 0x01U, 0x28U, 0x02U, 0x50U, 0x46U, 0x0DU, 0x01U, 0x0BU,
|
||||
0x41U, 0xE1U, 0x8AU, 0x80U, 0x80U, 0x00U, 0x41U, 0x10U, 0x42U, 0xCEU,
|
||||
0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U,
|
||||
0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x86U, 0x80U, 0x08U, 0x10U,
|
||||
0x84U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x7BU, 0x51U, 0x0DU, 0x00U,
|
||||
0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x33U, 0x42U, 0xD1U,
|
||||
0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U,
|
||||
0x40U, 0x20U, 0x01U, 0x41U, 0x20U, 0x41U, 0x00U, 0x10U, 0x85U, 0x80U,
|
||||
0x80U, 0x80U, 0x00U, 0x42U, 0x20U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xEFU,
|
||||
0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x1BU, 0x42U, 0xD5U, 0x00U, 0x10U,
|
||||
0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U,
|
||||
0x01U, 0x41U, 0x20U, 0x41U, 0x80U, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U,
|
||||
0x20U, 0x10U, 0x86U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x20U, 0x51U,
|
||||
0x0DU, 0x00U, 0x41U, 0x8AU, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x25U,
|
||||
0x42U, 0xD6U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
|
||||
0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0x87U, 0x80U,
|
||||
0x80U, 0x80U, 0x00U, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, 0xE0U, 0x00U,
|
||||
0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x20U, 0x02U, 0x0BU,
|
||||
0xF6U, 0x83U, 0x00U, 0x03U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x01U, 0x7FU,
|
||||
0x23U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0x20U, 0x6BU, 0x22U,
|
||||
0x01U, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0x01U, 0x41U,
|
||||
0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x02U, 0x40U,
|
||||
0x41U, 0x01U, 0x10U, 0x88U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x01U,
|
||||
0x51U, 0x0DU, 0x00U, 0x41U, 0xDCU, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U,
|
||||
0x15U, 0x42U, 0xDFU, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x1AU, 0x0BU, 0x41U, 0x00U, 0x10U, 0x89U, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x22U, 0x02U, 0x3CU, 0x00U, 0xB2U, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U,
|
||||
0x00U, 0x20U, 0x02U, 0x42U, 0x08U, 0x88U, 0x3CU, 0x00U, 0xB1U, 0x8BU,
|
||||
0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0x42U, 0x10U, 0x88U,
|
||||
0x3CU, 0x00U, 0xB0U, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U,
|
||||
0x02U, 0x42U, 0x18U, 0x88U, 0x3CU, 0x00U, 0xAFU, 0x8BU, 0x80U, 0x80U,
|
||||
0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0xA7U, 0x41U, 0x01U, 0x6AU, 0x22U,
|
||||
0x03U, 0x3AU, 0x00U, 0xB8U, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U,
|
||||
0x20U, 0x03U, 0x41U, 0x08U, 0x76U, 0x3AU, 0x00U, 0xB7U, 0x8BU, 0x80U,
|
||||
0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x03U, 0x41U, 0x10U, 0x76U, 0x3AU,
|
||||
0x00U, 0xB6U, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x03U,
|
||||
0x41U, 0x18U, 0x76U, 0x3AU, 0x00U, 0xB5U, 0x8BU, 0x80U, 0x80U, 0x00U,
|
||||
0x02U, 0x40U, 0x41U, 0xE7U, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x14U,
|
||||
0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x14U, 0x51U, 0x0DU,
|
||||
0x00U, 0x41U, 0x98U, 0x8AU, 0x80U, 0x80U, 0x00U, 0x41U, 0x24U, 0x42U,
|
||||
0xE8U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU,
|
||||
0x02U, 0x40U, 0x41U, 0xFBU, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x8AU,
|
||||
0x01U, 0x10U, 0x8AU, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x8AU, 0x01U,
|
||||
0x51U, 0x0DU, 0x00U, 0x41U, 0xCBU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U,
|
||||
0x24U, 0x42U, 0xE9U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0xA0U, 0x8BU, 0x80U, 0x80U, 0x00U,
|
||||
0x41U, 0xE5U, 0x01U, 0x10U, 0x8BU, 0x80U, 0x80U, 0x80U, 0x00U, 0x22U,
|
||||
0x02U, 0x42U, 0x00U, 0x55U, 0x0DU, 0x00U, 0x41U, 0xD9U, 0x8AU, 0x80U,
|
||||
0x80U, 0x00U, 0x41U, 0x08U, 0x42U, 0xEBU, 0x00U, 0x10U, 0x81U, 0x80U,
|
||||
0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x20U, 0x02U, 0x3CU,
|
||||
0x00U, 0xC1U, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x02U,
|
||||
0x42U, 0x08U, 0x88U, 0x3CU, 0x00U, 0xC0U, 0x8BU, 0x80U, 0x80U, 0x00U,
|
||||
0x41U, 0x00U, 0x20U, 0x02U, 0x42U, 0x10U, 0x88U, 0x3CU, 0x00U, 0xBFU,
|
||||
0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0x42U, 0x18U,
|
||||
0x88U, 0x3CU, 0x00U, 0xBEU, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U,
|
||||
0x20U, 0x02U, 0x42U, 0x20U, 0x88U, 0x3CU, 0x00U, 0xBDU, 0x8BU, 0x80U,
|
||||
0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0x42U, 0x28U, 0x88U, 0x3CU,
|
||||
0x00U, 0xBCU, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x02U,
|
||||
0x42U, 0x30U, 0x88U, 0x3CU, 0x00U, 0xBBU, 0x8BU, 0x80U, 0x80U, 0x00U,
|
||||
0x41U, 0x00U, 0x20U, 0x02U, 0x42U, 0x38U, 0x88U, 0xA7U, 0x41U, 0x3FU,
|
||||
0x71U, 0x41U, 0xC0U, 0x00U, 0x72U, 0x3AU, 0x00U, 0xBAU, 0x8BU, 0x80U,
|
||||
0x80U, 0x00U, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x20U, 0x41U, 0xA0U,
|
||||
0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0xE5U, 0x01U, 0x10U, 0x8CU, 0x80U,
|
||||
0x80U, 0x80U, 0x00U, 0x42U, 0x20U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xAFU,
|
||||
0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x26U, 0x42U, 0xEFU, 0x00U, 0x10U,
|
||||
0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U,
|
||||
0x00U, 0x42U, 0x00U, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x21U,
|
||||
0x02U, 0x20U, 0x01U, 0x41U, 0x20U, 0x6AU, 0x24U, 0x80U, 0x80U, 0x80U,
|
||||
0x80U, 0x00U, 0x20U, 0x02U, 0x0BU, 0x0BU, 0x85U, 0x05U, 0x02U, 0x00U,
|
||||
0x41U, 0x80U, 0x08U, 0x0BU, 0xF1U, 0x02U, 0x6FU, 0x74U, 0x78U, 0x6EU,
|
||||
0x5FU, 0x66U, 0x69U, 0x65U, 0x6CU, 0x64U, 0x28U, 0x30U, 0x2CU, 0x20U,
|
||||
0x30U, 0x2CU, 0x20U, 0x73U, 0x66U, 0x4CU, 0x65U, 0x64U, 0x67U, 0x65U,
|
||||
0x72U, 0x53U, 0x65U, 0x71U, 0x75U, 0x65U, 0x6EU, 0x63U, 0x65U, 0x29U,
|
||||
0x20U, 0x3DU, 0x3DU, 0x20U, 0x44U, 0x4FU, 0x45U, 0x53U, 0x4EU, 0x54U,
|
||||
0x5FU, 0x45U, 0x58U, 0x49U, 0x53U, 0x54U, 0x00U, 0x6FU, 0x74U, 0x78U,
|
||||
0x6EU, 0x5FU, 0x74U, 0x79U, 0x70U, 0x65U, 0x28U, 0x29U, 0x20U, 0x3DU,
|
||||
0x3DU, 0x20U, 0x74U, 0x74U, 0x49U, 0x4EU, 0x56U, 0x4FU, 0x4BU, 0x45U,
|
||||
0x00U, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U, 0x74U, 0x61U,
|
||||
0x69U, 0x6CU, 0x73U, 0x28U, 0x45U, 0x4DU, 0x49U, 0x54U, 0x5FU, 0x4FU,
|
||||
0x55U, 0x54U, 0x2CU, 0x20U, 0x31U, 0x33U, 0x38U, 0x55U, 0x29U, 0x20U,
|
||||
0x3DU, 0x3DU, 0x20U, 0x31U, 0x33U, 0x38U, 0x00U, 0x6FU, 0x74U, 0x78U,
|
||||
0x6EU, 0x5FU, 0x69U, 0x64U, 0x28U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U,
|
||||
0x69U, 0x64U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x29U, 0x20U, 0x3DU, 0x3DU,
|
||||
0x20U, 0x33U, 0x32U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU,
|
||||
0x73U, 0x65U, 0x74U, 0x28U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x69U,
|
||||
0x64U, 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x6BU,
|
||||
0x65U, 0x79U, 0x29U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U,
|
||||
0x00U, 0x65U, 0x6DU, 0x69U, 0x74U, 0x28U, 0x53U, 0x42U, 0x55U, 0x46U,
|
||||
0x28U, 0x65U, 0x6DU, 0x69U, 0x74U, 0x68U, 0x61U, 0x73U, 0x68U, 0x29U,
|
||||
0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x74U, 0x78U, 0x6EU,
|
||||
0x29U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, 0x72U,
|
||||
0x20U, 0x3DU, 0x3DU, 0x20U, 0x31U, 0x00U, 0x65U, 0x74U, 0x78U, 0x6EU,
|
||||
0x5FU, 0x72U, 0x65U, 0x73U, 0x65U, 0x72U, 0x76U, 0x65U, 0x28U, 0x31U,
|
||||
0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x31U, 0x00U, 0x6FU, 0x74U, 0x78U,
|
||||
0x6EU, 0x5FU, 0x66U, 0x69U, 0x65U, 0x6CU, 0x64U, 0x28U, 0x53U, 0x42U,
|
||||
0x55U, 0x46U, 0x28U, 0x61U, 0x63U, 0x63U, 0x29U, 0x2CU, 0x20U, 0x73U,
|
||||
0x66U, 0x41U, 0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x29U, 0x20U,
|
||||
0x3DU, 0x3DU, 0x20U, 0x32U, 0x30U, 0x00U, 0x68U, 0x6FU, 0x6FU, 0x6BU,
|
||||
0x5FU, 0x61U, 0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x28U, 0x41U,
|
||||
0x43U, 0x43U, 0x4FU, 0x55U, 0x4EU, 0x54U, 0x5FU, 0x4FU, 0x55U, 0x54U,
|
||||
0x2CU, 0x20U, 0x32U, 0x30U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x32U,
|
||||
0x30U, 0x00U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U, 0x63U, 0x63U,
|
||||
0x6FU, 0x75U, 0x6EU, 0x74U, 0x28U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U,
|
||||
0x6DU, 0x65U, 0x29U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x32U, 0x30U,
|
||||
0x00U, 0x66U, 0x65U, 0x65U, 0x20U, 0x3EU, 0x20U, 0x30U, 0x00U, 0x41U,
|
||||
0x43U, 0x43U, 0x5FU, 0x45U, 0x51U, 0x28U, 0x61U, 0x63U, 0x63U, 0x2CU,
|
||||
0x20U, 0x6DU, 0x65U, 0x29U, 0x00U, 0x00U, 0x41U, 0x80U, 0x0BU, 0x0BU,
|
||||
0x85U, 0x02U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x01U, 0x12U, 0x00U, 0x63U, 0x22U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x24U, 0x00U, 0x00U, 0x00U, 0x00U, 0x20U, 0x1AU, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U, 0x68U,
|
||||
0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x73U, 0x21U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x81U, 0x14U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U,
|
||||
}},
|
||||
|
||||
};
|
||||
}
|
||||
} // namespace ripple
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#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>
|
||||
@@ -554,99 +553,9 @@ 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))) ==
|
||||
"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())));
|
||||
"tecDUPLICATE");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -971,11 +880,6 @@ 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());
|
||||
@@ -991,237 +895,33 @@ 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);
|
||||
|
||||
// Only the pinned two are offered. The other two are on the ledger,
|
||||
// where any peer that is not amendment blocked can read them.
|
||||
// Everything is offered: two pinned plus two under the gossip limit.
|
||||
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 == 2);
|
||||
BEAST_EXPECT(reserved == 4);
|
||||
BEAST_EXPECT(offered.size() == 4);
|
||||
BEAST_EXPECT(
|
||||
hash_set<PublicKey>(offered.begin(), offered.end()) ==
|
||||
hash_set<PublicKey>({masters[0].pk(), masters[1].pk()}));
|
||||
hash_set<PublicKey>(offered.begin(), offered.end()) == all);
|
||||
|
||||
// Unpinned, a master key falls back to the lookup cache. A pinned key
|
||||
// with no manifest is neither offered nor counted.
|
||||
// A pinned key with no manifest is counted in the reservation but not
|
||||
// offered, since the reservation is only an upper bound.
|
||||
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 == 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);
|
||||
BEAST_EXPECT(reserved == 5);
|
||||
BEAST_EXPECT(offered.size() == 4);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1261,13 +961,10 @@ public:
|
||||
testRetrieval(sa);
|
||||
testSigningKeyRetrieval(sa);
|
||||
testMalformed(sa);
|
||||
testAccountDelete(sa);
|
||||
testEnvelopeRejections(sa);
|
||||
testCanonicalFee(sa);
|
||||
testCorruptLedger(sa);
|
||||
testGossipSelection(sa);
|
||||
testLedgerOutranksGossip(sa);
|
||||
testDirectory(sa);
|
||||
testDisabled(sa);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5055,15 +5055,7 @@ public:
|
||||
run() override
|
||||
{
|
||||
using namespace test::jtx;
|
||||
// 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};
|
||||
FeatureBitset const all{supported_amendments() - featureXahauGenesis};
|
||||
testQueueSeq(all);
|
||||
testQueueTicket(all);
|
||||
testTecResult(all);
|
||||
@@ -5090,15 +5082,7 @@ public:
|
||||
runMetaInfo()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
// 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};
|
||||
FeatureBitset const all{supported_amendments() - featureXahauGenesis};
|
||||
testAcctInQueueButEmpty(all);
|
||||
testRPC(all);
|
||||
testExpirationReplacement(all);
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -4677,12 +4676,11 @@ 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 && !has20261005) ? preLedger : preLedger + 1,
|
||||
has240819 ? preLedger : preLedger + 1,
|
||||
has240819 ? (preUser - feesXRP) : postUser,
|
||||
preTime));
|
||||
}
|
||||
@@ -5569,7 +5567,8 @@ struct XahauGenesis_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
|
||||
// calculate rewards
|
||||
bool const hasFix = env.current()->rules().enabled(fix240819);
|
||||
bool const hasFix = env.current()->rules().enabled(fix240819) &&
|
||||
env.current()->rules().enabled(fix240911);
|
||||
STAmount const netReward =
|
||||
rewardUserAmount(*acctSle, preLedger, rateDrops);
|
||||
BEAST_EXPECT(netReward == (hasFix ? XRP(3.329999) : XRP(3.329999)));
|
||||
@@ -5660,7 +5659,8 @@ struct XahauGenesis_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
|
||||
// calculate rewards
|
||||
bool const hasFix = env.current()->rules().enabled(fix240819);
|
||||
bool const hasFix = env.current()->rules().enabled(fix240819) &&
|
||||
env.current()->rules().enabled(fix240911);
|
||||
STAmount const netReward =
|
||||
rewardUserAmount(*acctSle, preLedger, rateDrops);
|
||||
BEAST_EXPECT(netReward == (hasFix ? XRP(3.479999) : XRP(6.663333)));
|
||||
@@ -5678,196 +5678,9 @@ 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);
|
||||
@@ -5910,7 +5723,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
|
||||
testGovernHookWithFeats(sa);
|
||||
testRewardHookWithFeats(sa);
|
||||
testRewardHookWithFeats(sa - fix240819);
|
||||
testRewardHookWithFeats(sa - fix20261005);
|
||||
testRewardHookWithFeats(sa - fix240819 - fix240911);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -529,10 +529,11 @@ public:
|
||||
|
||||
std::vector<JobName> jobs;
|
||||
{
|
||||
auto const& jobTypes = JobTypes::instance();
|
||||
jobs.reserve(jobTypes.size());
|
||||
for (auto const& job : jobTypes)
|
||||
{
|
||||
jobs.emplace_back(job.type, std::string(job.name));
|
||||
jobs.emplace_back(job.first, job.second.name());
|
||||
}
|
||||
}
|
||||
std::shuffle(jobs.begin(), jobs.end(), default_prng());
|
||||
@@ -864,10 +865,14 @@ public:
|
||||
JobType jobType;
|
||||
std::string jobTypeName;
|
||||
{
|
||||
auto index = rand_int<std::size_t>(0, jobTypes.size() - 1);
|
||||
auto const& jobTypes = JobTypes::instance();
|
||||
|
||||
jobType = jobTypes[index].type;
|
||||
jobTypeName = jobTypes[index].name;
|
||||
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();
|
||||
}
|
||||
|
||||
// Say there's one worker thread.
|
||||
|
||||
@@ -307,9 +307,11 @@ struct base_uint_test : beast::unit_test::suite
|
||||
std::string_view sView(str.data(), str.size());
|
||||
[[maybe_unused]] test96 t96(sView);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
catch (std::invalid_argument const& e)
|
||||
{
|
||||
BEAST_EXPECT(e.what() == std::string("invalid hex string"));
|
||||
BEAST_EXPECT(
|
||||
e.what() ==
|
||||
std::string("invalid length for hex string"));
|
||||
caught = true;
|
||||
}
|
||||
BEAST_EXPECT(caught);
|
||||
@@ -325,9 +327,10 @@ struct base_uint_test : beast::unit_test::suite
|
||||
std::string_view sView(str.data(), str.size());
|
||||
[[maybe_unused]] test96 t96(sView);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
catch (std::range_error const& e)
|
||||
{
|
||||
BEAST_EXPECT(e.what() == std::string("invalid hex string"));
|
||||
BEAST_EXPECT(
|
||||
e.what() == std::string("invalid hex character"));
|
||||
caught = true;
|
||||
}
|
||||
BEAST_EXPECT(caught);
|
||||
|
||||
@@ -324,35 +324,6 @@ 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,67 +1052,6 @@ 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
|
||||
{
|
||||
@@ -1129,7 +1068,6 @@ public:
|
||||
testHubNetwork();
|
||||
testPreferredByBranch();
|
||||
testPauseForLaggards();
|
||||
testForcedInclusion();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -421,72 +421,6 @@ 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()
|
||||
{
|
||||
@@ -1122,36 +1056,6 @@ 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()
|
||||
{
|
||||
@@ -1843,7 +1747,6 @@ r.ripple.com:51235
|
||||
testValidatorsFile();
|
||||
testSetup(false);
|
||||
testSetup(true);
|
||||
testSetupStandaloneRwdb();
|
||||
testPort();
|
||||
testZeroPort();
|
||||
testWhitespace();
|
||||
@@ -1855,8 +1758,6 @@ r.ripple.com:51235
|
||||
testDatabasePinnedValidation();
|
||||
testOverlay();
|
||||
testNetworkID();
|
||||
testEarlyManifestSync();
|
||||
testUdpHighway();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -120,28 +120,23 @@ public:
|
||||
|
||||
auto& jq = env.app().getJobQueue();
|
||||
|
||||
// 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;
|
||||
static int const N = 4;
|
||||
std::array<std::shared_ptr<JobQueue::Coro>, N> 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 < a.size(); ++i)
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
jq.postCoro(jtCLIENT, "Coroutine-Test", [&, id = i](auto const& c) {
|
||||
a[id] = c;
|
||||
@@ -159,14 +154,12 @@ 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();
|
||||
@@ -177,7 +170,6 @@ public:
|
||||
this->BEAST_EXPECT(*lv == -2);
|
||||
g.signal();
|
||||
});
|
||||
|
||||
BEAST_EXPECT(g.wait_for(5s));
|
||||
BEAST_EXPECT(*lv == -1);
|
||||
}
|
||||
|
||||
@@ -21,11 +21,6 @@
|
||||
#include <xrpld/core/JobQueue.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
@@ -50,19 +45,22 @@ 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();
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -130,77 +128,31 @@ 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();
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testPriority();
|
||||
testAddJob();
|
||||
testPostCoro();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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,8 +33,6 @@
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -259,11 +257,6 @@ 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;
|
||||
|
||||
@@ -662,14 +655,6 @@ 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[jss::LockingChainDoor] = lockingChainDoor.human();
|
||||
jv[jss::LockingChainIssue] = to_json(lockingChainIssue);
|
||||
jv[jss::IssuingChainDoor] = issuingChainDoor.human();
|
||||
jv[jss::IssuingChainIssue] = to_json(issuingChainIssue);
|
||||
jv[sfLockingChainDoor.getJsonName()] = lockingChainDoor.human();
|
||||
jv[sfLockingChainIssue.getJsonName()] = to_json(lockingChainIssue);
|
||||
jv[sfIssuingChainDoor.getJsonName()] = issuingChainDoor.human();
|
||||
jv[sfIssuingChainIssue.getJsonName()] = to_json(issuingChainIssue);
|
||||
return jv;
|
||||
}
|
||||
|
||||
@@ -60,10 +60,10 @@ bridge_rpc(
|
||||
Issue const& issuingChainIssue)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::LockingChainDoor] = lockingChainDoor.human();
|
||||
jv[jss::LockingChainIssue] = to_json(lockingChainIssue);
|
||||
jv[jss::IssuingChainDoor] = issuingChainDoor.human();
|
||||
jv[jss::IssuingChainIssue] = to_json(issuingChainIssue);
|
||||
jv[sfLockingChainDoor.getJsonName()] = lockingChainDoor.human();
|
||||
jv[sfLockingChainIssue.getJsonName()] = to_json(lockingChainIssue);
|
||||
jv[sfIssuingChainDoor.getJsonName()] = issuingChainDoor.human();
|
||||
jv[sfIssuingChainIssue.getJsonName()] = to_json(issuingChainIssue);
|
||||
return jv;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#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>
|
||||
@@ -1235,90 +1234,6 @@ 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()
|
||||
{
|
||||
@@ -1374,7 +1289,6 @@ public:
|
||||
testNFTokenPageInvariants();
|
||||
testPermissionedDomainInvariants();
|
||||
testLockedBalance();
|
||||
testAppLoader();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -66,14 +66,6 @@ public:
|
||||
// Write the batch
|
||||
storeBatch(*backend, batch);
|
||||
|
||||
// Durability on demand: RocksDB drains its write queue and
|
||||
// syncs its WAL; in-memory backends have nothing to wait for;
|
||||
// NuDB commits on its own thread and cannot promise it.
|
||||
backend->store(batch.front());
|
||||
bool const durable = backend->syncDurable();
|
||||
BEAST_EXPECT(durable == (type != "nudb"));
|
||||
backend->sync();
|
||||
|
||||
{
|
||||
// Read it back in
|
||||
Batch copy;
|
||||
|
||||
@@ -1,542 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <test/jtx.h>
|
||||
#include <xrpld/app/misc/HashRouter.h>
|
||||
#include <xrpld/core/JobQueue.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/XUSH.h>
|
||||
#include <xrpl/beast/net/IPAddressConversion.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/resource/ResourceManager.h>
|
||||
#include <xrpl/resource/detail/Tuning.h>
|
||||
|
||||
#include <boost/asio/ip/udp.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
// Tests the UDP Superhighway (XUSH) handling in OverlayImpl: inbound datagram
|
||||
// processing, outbound publishing and the XUSHPEER advertisement timer.
|
||||
class OverlayXUSH_test : public beast::unit_test::suite
|
||||
{
|
||||
using udp = boost::asio::ip::udp;
|
||||
|
||||
static std::unique_ptr<Config>
|
||||
highway(std::unique_ptr<Config> cfg)
|
||||
{
|
||||
cfg->UDP_HIGHWAY = true;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static beast::IP::Endpoint
|
||||
ep(std::string const& s)
|
||||
{
|
||||
return beast::IP::Endpoint::from_string(s);
|
||||
}
|
||||
|
||||
static std::set<beast::IP::Endpoint>
|
||||
asSet(std::vector<beast::IP::Endpoint> const& v)
|
||||
{
|
||||
return {v.begin(), v.end()};
|
||||
}
|
||||
|
||||
static Slice
|
||||
slice(Buffer const& b)
|
||||
{
|
||||
return Slice(b.data(), b.size());
|
||||
}
|
||||
|
||||
static std::string
|
||||
str(Buffer const& b)
|
||||
{
|
||||
return std::string(reinterpret_cast<char const*>(b.data()), b.size());
|
||||
}
|
||||
|
||||
static Buffer
|
||||
serialize(STTx const& stx)
|
||||
{
|
||||
Serializer s;
|
||||
stx.add(s);
|
||||
return Buffer(s.data(), s.size());
|
||||
}
|
||||
|
||||
// A pseudo-transaction, which servers never submit
|
||||
static STTx
|
||||
pseudoTx()
|
||||
{
|
||||
return STTx(ttAMENDMENT, [](auto& obj) {
|
||||
obj.setAccountID(sfAccount, AccountID());
|
||||
obj.setFieldH256(sfAmendment, uint256(2));
|
||||
obj.setFieldU32(sfLedgerSequence, 5);
|
||||
});
|
||||
}
|
||||
|
||||
// A local UDP socket that highway datagrams can be sent to
|
||||
struct Sink
|
||||
{
|
||||
boost::asio::io_context io;
|
||||
udp::socket socket;
|
||||
|
||||
explicit Sink(boost::asio::ip::address const& addr)
|
||||
: socket(io, udp::endpoint(addr, 0))
|
||||
{
|
||||
socket.non_blocking(true);
|
||||
}
|
||||
|
||||
beast::IP::Endpoint
|
||||
endpoint() const
|
||||
{
|
||||
auto const local = socket.local_endpoint();
|
||||
return beast::IP::Endpoint(local.address(), local.port());
|
||||
}
|
||||
|
||||
void
|
||||
sendTo(Buffer const& datagram, std::uint16_t port)
|
||||
{
|
||||
socket.send_to(
|
||||
boost::asio::buffer(datagram.data(), datagram.size()),
|
||||
udp::endpoint(socket.local_endpoint().address(), port));
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
receive(std::chrono::milliseconds timeout = std::chrono::seconds(5))
|
||||
{
|
||||
auto const deadline = std::chrono::steady_clock::now() + timeout;
|
||||
std::array<char, 4096> buf;
|
||||
while (std::chrono::steady_clock::now() < deadline)
|
||||
{
|
||||
udp::endpoint from;
|
||||
boost::system::error_code ec;
|
||||
auto const n =
|
||||
socket.receive_from(boost::asio::buffer(buf), from, 0, ec);
|
||||
if (!ec)
|
||||
return std::string(buf.data(), n);
|
||||
if (ec != boost::asio::error::would_block &&
|
||||
ec != boost::asio::error::try_again)
|
||||
return std::nullopt;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Receives datagrams until one of the given type arrives
|
||||
std::optional<std::string>
|
||||
receive(xush::MessageType type)
|
||||
{
|
||||
while (auto d = receive())
|
||||
{
|
||||
if (xush::classify(makeSlice(*d)) == type)
|
||||
return d;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
struct Fixture
|
||||
{
|
||||
jtx::Env env;
|
||||
OverlayImpl& overlay;
|
||||
PeerFinder::Manager& peerFinder;
|
||||
Resource::Manager& resources;
|
||||
|
||||
Fixture(beast::unit_test::suite& suite, bool enabled)
|
||||
: env(suite, enabled ? jtx::envconfig(highway) : jtx::envconfig())
|
||||
, overlay(dynamic_cast<OverlayImpl&>(env.app().overlay()))
|
||||
, peerFinder(overlay.peerFinder())
|
||||
, resources(env.app().getResourceManager())
|
||||
{
|
||||
}
|
||||
|
||||
// Delivers a datagram as if it had been received from `remote`
|
||||
void
|
||||
deliver(Buffer const& datagram, beast::IP::Endpoint const& remote)
|
||||
{
|
||||
overlay.processXUSH(
|
||||
str(datagram), beast::IP::to_asio_endpoint(remote));
|
||||
}
|
||||
|
||||
void
|
||||
deliver(
|
||||
std::vector<Buffer> const& datagrams,
|
||||
beast::IP::Endpoint const& remote)
|
||||
{
|
||||
for (auto const& d : datagrams)
|
||||
deliver(d, remote);
|
||||
}
|
||||
|
||||
// Tracks the resource balance charged to `remote`
|
||||
Resource::Consumer
|
||||
probe(beast::IP::Endpoint const& remote)
|
||||
{
|
||||
return resources.newInboundEndpoint(remote);
|
||||
}
|
||||
|
||||
std::set<beast::IP::Endpoint>
|
||||
targets()
|
||||
{
|
||||
return asSet(peerFinder.highway_targets(1000));
|
||||
}
|
||||
|
||||
// Whether `probe` was charged at least `fee` since `before`. Balances
|
||||
// are normalized by the decay window, so a charge of `fee` raises the
|
||||
// balance by at least fee / decayWindowSeconds.
|
||||
static bool
|
||||
charged(Resource::Consumer& probe, int before, int fee)
|
||||
{
|
||||
return probe.balance() - before >=
|
||||
fee / Resource::decayWindowSeconds;
|
||||
}
|
||||
|
||||
bool
|
||||
isBad(uint256 const& txid)
|
||||
{
|
||||
env.app().getJobQueue().rendezvous();
|
||||
return env.app().getHashRouter().getFlags(txid) & SF_BAD;
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
testDisabled()
|
||||
{
|
||||
testcase("disabled");
|
||||
using namespace jtx;
|
||||
Fixture f(*this, false);
|
||||
auto const remote = ep("203.0.113.1:51235");
|
||||
|
||||
// Nothing is learned or sent while the highway is disabled
|
||||
f.deliver(xush::encodePeers({ep("8.8.8.8:51235")}), remote);
|
||||
BEAST_EXPECT(f.targets().empty());
|
||||
|
||||
Sink sink(boost::asio::ip::make_address("127.0.0.1"));
|
||||
f.peerFinder.addFixedPeer("sink", {sink.endpoint()});
|
||||
BEAST_EXPECT(f.targets().empty());
|
||||
|
||||
Account const alice("alice");
|
||||
f.env.fund(XRP(10000), alice);
|
||||
f.env.close();
|
||||
|
||||
Buffer const blob(64);
|
||||
f.overlay.publishTxXUSH(slice(blob), uint256(1));
|
||||
BEAST_EXPECT(!sink.receive(std::chrono::milliseconds(200)));
|
||||
}
|
||||
|
||||
void
|
||||
testPeers()
|
||||
{
|
||||
testcase("XUSHPEER");
|
||||
Fixture f(*this, true);
|
||||
auto const remote = ep("203.0.113.2:51235");
|
||||
auto probe = f.probe(remote);
|
||||
|
||||
BEAST_EXPECT(f.targets().empty());
|
||||
|
||||
f.deliver(
|
||||
xush::encodePeers({ep("8.8.8.8:51235"), ep("1.1.1.1:51235")}),
|
||||
remote);
|
||||
BEAST_EXPECT(
|
||||
f.targets() == asSet({ep("8.8.8.8:51235"), ep("1.1.1.1:51235")}));
|
||||
auto const balance = probe.balance();
|
||||
|
||||
// Unknown datagram types are ignored and cost almost nothing
|
||||
f.deliver(Buffer("XUSHPING", 8), remote);
|
||||
BEAST_EXPECT(!Fixture::charged(probe, balance, 100));
|
||||
|
||||
// A malformed advertisement is charged for
|
||||
Buffer malformed(xush::tagSize + 2);
|
||||
std::memcpy(malformed.data(), "XUSHPEER\x05\x00", malformed.size());
|
||||
f.deliver(malformed, remote);
|
||||
BEAST_EXPECT(Fixture::charged(probe, balance, 200));
|
||||
BEAST_EXPECT(f.targets().size() == 2);
|
||||
}
|
||||
|
||||
void
|
||||
testTransaction()
|
||||
{
|
||||
testcase("XUSHTXNF transaction");
|
||||
using namespace jtx;
|
||||
Fixture f(*this, true);
|
||||
auto& env = f.env;
|
||||
auto const remote = ep("203.0.113.3:51235");
|
||||
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto const jt = env.jt(pay(alice, bob, XRP(100)));
|
||||
auto const txid = jt.stx->getTransactionID();
|
||||
|
||||
// Fragments arrive out of order and are reassembled
|
||||
auto datagrams = xush::encodeTxn(slice(serialize(*jt.stx)), txid, 40);
|
||||
BEAST_EXPECT(datagrams.size() > 2);
|
||||
std::reverse(datagrams.begin(), datagrams.end());
|
||||
f.deliver(datagrams, remote);
|
||||
env.app().getJobQueue().rendezvous();
|
||||
|
||||
BEAST_EXPECT(!f.isBad(txid));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(bob) == XRP(10100));
|
||||
|
||||
// Delivering it again is harmless
|
||||
f.deliver(datagrams, remote);
|
||||
env.app().getJobQueue().rendezvous();
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(bob) == XRP(10100));
|
||||
}
|
||||
|
||||
void
|
||||
testBadTransactions()
|
||||
{
|
||||
testcase("XUSHTXNF bad transactions");
|
||||
using namespace jtx;
|
||||
Fixture f(*this, true);
|
||||
auto& env = f.env;
|
||||
auto const remote = ep("203.0.113.4:51235");
|
||||
auto probe = f.probe(remote);
|
||||
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto const good = env.jt(pay(alice, bob, XRP(100)));
|
||||
auto const blob = serialize(*good.stx);
|
||||
auto const txid = good.stx->getTransactionID();
|
||||
|
||||
// The ID in the header must match the transaction
|
||||
auto balance = probe.balance();
|
||||
f.deliver(xush::encodeTxn(slice(blob), uint256(1)), remote);
|
||||
BEAST_EXPECT(Fixture::charged(probe, balance, 400));
|
||||
BEAST_EXPECT(!f.isBad(txid));
|
||||
|
||||
// Garbage that doesn't parse as a transaction
|
||||
balance = probe.balance();
|
||||
f.deliver(
|
||||
xush::encodeTxn(makeSlice(std::string("garbage")), uint256(2)),
|
||||
remote);
|
||||
BEAST_EXPECT(Fixture::charged(probe, balance, 400));
|
||||
|
||||
// Fragments of one transaction that disagree with each other
|
||||
balance = probe.balance();
|
||||
f.deliver(xush::encodeTxn(slice(blob), uint256(3), 40).front(), remote);
|
||||
f.deliver(xush::encodeTxn(slice(blob), uint256(3), 60).front(), remote);
|
||||
BEAST_EXPECT(Fixture::charged(probe, balance, 400));
|
||||
|
||||
// A malformed fragment
|
||||
balance = probe.balance();
|
||||
f.deliver(Buffer("XUSHTXNF", 8), remote);
|
||||
BEAST_EXPECT(Fixture::charged(probe, balance, 200));
|
||||
|
||||
// Pseudo-transactions are never accepted
|
||||
balance = probe.balance();
|
||||
auto const pseudo = pseudoTx();
|
||||
f.deliver(
|
||||
xush::encodeTxn(
|
||||
slice(serialize(pseudo)), pseudo.getTransactionID()),
|
||||
remote);
|
||||
BEAST_EXPECT(Fixture::charged(probe, balance, 2000));
|
||||
|
||||
// An expired transaction is remembered as bad
|
||||
auto const expired =
|
||||
env.jt(pay(alice, bob, XRP(100)), last_ledger_seq(1));
|
||||
auto const expiredId = expired.stx->getTransactionID();
|
||||
balance = probe.balance();
|
||||
f.deliver(
|
||||
xush::encodeTxn(slice(serialize(*expired.stx)), expiredId), remote);
|
||||
BEAST_EXPECT(f.isBad(expiredId));
|
||||
BEAST_EXPECT(Fixture::charged(probe, balance, 150));
|
||||
|
||||
// A badly signed transaction is remembered as bad
|
||||
auto corrupted = good.stx->getJson(JsonOptions::none);
|
||||
corrupted[jss::TxnSignature] = std::string(128, '0');
|
||||
STTx const badSig(parse(corrupted));
|
||||
auto const badSigId = badSig.getTransactionID();
|
||||
balance = probe.balance();
|
||||
f.deliver(xush::encodeTxn(slice(serialize(badSig)), badSigId), remote);
|
||||
BEAST_EXPECT(f.isBad(badSigId));
|
||||
BEAST_EXPECT(Fixture::charged(probe, balance, 2000));
|
||||
|
||||
// A known bad transaction is charged for again
|
||||
balance = probe.balance();
|
||||
f.deliver(xush::encodeTxn(slice(serialize(badSig)), badSigId), remote);
|
||||
BEAST_EXPECT(Fixture::charged(probe, balance, 150));
|
||||
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(bob) == XRP(10000));
|
||||
}
|
||||
|
||||
void
|
||||
testDisconnect()
|
||||
{
|
||||
testcase("resource limit");
|
||||
Fixture f(*this, true);
|
||||
auto const remote = ep("203.0.113.5:51235");
|
||||
auto probe = f.probe(remote);
|
||||
|
||||
// A sender that keeps misbehaving is ignored
|
||||
Buffer const blob(64);
|
||||
for (int i = 0; i < 10000 && probe.balance() < Resource::dropThreshold;
|
||||
++i)
|
||||
f.deliver(xush::encodeTxn(slice(blob), uint256(1)), remote);
|
||||
BEAST_EXPECT(probe.balance() >= Resource::dropThreshold);
|
||||
|
||||
f.deliver(xush::encodePeers({ep("8.8.8.8:51235")}), remote);
|
||||
BEAST_EXPECT(f.targets().empty());
|
||||
|
||||
// Other senders are not affected
|
||||
f.deliver(
|
||||
xush::encodePeers({ep("8.8.8.8:51235")}), ep("203.0.113.6:51235"));
|
||||
BEAST_EXPECT(f.targets() == asSet({ep("8.8.8.8:51235")}));
|
||||
}
|
||||
|
||||
void
|
||||
testPublish()
|
||||
{
|
||||
testcase("publish");
|
||||
using namespace jtx;
|
||||
Fixture f(*this, true);
|
||||
auto& env = f.env;
|
||||
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
Sink sink4(boost::asio::ip::make_address("127.0.0.1"));
|
||||
f.peerFinder.addFixedPeer("sink4", {sink4.endpoint()});
|
||||
|
||||
std::optional<Sink> sink6;
|
||||
try
|
||||
{
|
||||
sink6.emplace(boost::asio::ip::make_address("::1"));
|
||||
f.peerFinder.addFixedPeer("sink6", {sink6->endpoint()});
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
log << "IPv6 loopback unavailable, skipping" << std::endl;
|
||||
}
|
||||
|
||||
// A transaction relayed by NetworkOPs is published on the highway
|
||||
env(pay(alice, bob, XRP(100)));
|
||||
auto const stx = env.tx();
|
||||
auto const blob = serialize(*stx);
|
||||
|
||||
auto check = [&](Sink& sink) {
|
||||
auto const d = sink.receive(xush::MessageType::txn);
|
||||
if (!BEAST_EXPECT(d))
|
||||
return;
|
||||
auto const fragment = xush::decodeTxnFragment(makeSlice(*d));
|
||||
if (!BEAST_EXPECT(fragment))
|
||||
return;
|
||||
BEAST_EXPECT(fragment->txid == stx->getTransactionID());
|
||||
BEAST_EXPECT(fragment->count == 1);
|
||||
BEAST_EXPECT(fragment->payload == slice(blob));
|
||||
};
|
||||
check(sink4);
|
||||
if (sink6)
|
||||
check(*sink6);
|
||||
|
||||
// Large transactions are fragmented
|
||||
Buffer big(5000);
|
||||
for (std::size_t i = 0; i < big.size(); ++i)
|
||||
big.data()[i] = static_cast<std::uint8_t>(i);
|
||||
f.overlay.publishTxXUSH(slice(big), uint256(7));
|
||||
|
||||
xush::Reassembler reassembler;
|
||||
std::optional<Buffer> rebuilt;
|
||||
while (!rebuilt)
|
||||
{
|
||||
auto const d = sink4.receive(xush::MessageType::txn);
|
||||
if (!BEAST_EXPECT(d))
|
||||
break;
|
||||
auto const fragment = xush::decodeTxnFragment(makeSlice(*d));
|
||||
if (!BEAST_EXPECT(fragment && fragment->txid == uint256(7)))
|
||||
break;
|
||||
auto result = reassembler.add(
|
||||
sink4.endpoint(),
|
||||
*fragment,
|
||||
xush::Reassembler::clock_type::now());
|
||||
if (result.status == xush::Reassembler::Status::complete)
|
||||
rebuilt = std::move(result.txn);
|
||||
}
|
||||
BEAST_EXPECT(rebuilt && *rebuilt == big);
|
||||
|
||||
// Transactions too large for the highway are not sent
|
||||
Buffer const huge(xush::maxTxnSize + 1);
|
||||
f.overlay.publishTxXUSH(slice(huge), uint256(8));
|
||||
f.overlay.publishTxXUSH(slice(big), uint256(9));
|
||||
auto const d = sink4.receive(xush::MessageType::txn);
|
||||
if (BEAST_EXPECT(d))
|
||||
{
|
||||
auto const fragment = xush::decodeTxnFragment(makeSlice(*d));
|
||||
BEAST_EXPECT(fragment && fragment->txid == uint256(9));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSendPeers()
|
||||
{
|
||||
testcase("advertise");
|
||||
Fixture f(*this, true);
|
||||
|
||||
Sink sink(boost::asio::ip::make_address("127.0.0.1"));
|
||||
f.peerFinder.addFixedPeer("sink", {sink.endpoint()});
|
||||
|
||||
// Learned endpoints are advertised to targets once per second, but
|
||||
// fixed peers are never advertised.
|
||||
f.peerFinder.add_highway_peers({ep("8.8.8.8:51235")});
|
||||
|
||||
auto const d = sink.receive(xush::MessageType::peers);
|
||||
if (!BEAST_EXPECT(d))
|
||||
return;
|
||||
auto const endpoints = xush::decodePeers(makeSlice(*d));
|
||||
BEAST_EXPECT(
|
||||
endpoints && asSet(*endpoints) == asSet({ep("8.8.8.8:51235")}));
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testDisabled();
|
||||
testPeers();
|
||||
testTransaction();
|
||||
testBadTransactions();
|
||||
testDisconnect();
|
||||
testPublish();
|
||||
testSendPeers();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(OverlayXUSH, overlay, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -1,313 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/overlay/Message.h>
|
||||
#include <xrpld/overlay/detail/ProtocolMessage.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/messages.h>
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/beast/core/multi_buffer.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
/** Receive-side framing tests for invokeProtocolMessage.
|
||||
|
||||
PeerImp::onReadMessage hands its entire read buffer to
|
||||
invokeProtocolMessage, which must decode exactly one frame and report
|
||||
exactly that frame's size as consumed. A single read routinely contains
|
||||
several frames, so the payload parser must never look past the payload
|
||||
size declared in the frame header.
|
||||
*/
|
||||
class ProtocolMessage_test : public beast::unit_test::suite
|
||||
{
|
||||
using Compressed = compression::Compressed;
|
||||
|
||||
/** Minimal stand-in for the PeerImp handler interface. */
|
||||
struct Handler
|
||||
{
|
||||
bool compression = false;
|
||||
std::vector<std::shared_ptr<::google::protobuf::Message>> received;
|
||||
std::vector<std::uint16_t> types;
|
||||
|
||||
bool
|
||||
compressionEnabled() const
|
||||
{
|
||||
return compression;
|
||||
}
|
||||
|
||||
void
|
||||
onMessageBegin(
|
||||
std::uint16_t,
|
||||
std::shared_ptr<::google::protobuf::Message> const&,
|
||||
std::size_t,
|
||||
std::size_t,
|
||||
bool)
|
||||
{
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
onMessage(std::shared_ptr<T> const& m)
|
||||
{
|
||||
received.push_back(m);
|
||||
}
|
||||
|
||||
void
|
||||
onMessageEnd(
|
||||
std::uint16_t type,
|
||||
std::shared_ptr<::google::protobuf::Message> const&)
|
||||
{
|
||||
types.push_back(type);
|
||||
}
|
||||
|
||||
void
|
||||
onMessageUnknown(std::uint16_t type)
|
||||
{
|
||||
types.push_back(type);
|
||||
}
|
||||
};
|
||||
|
||||
/** Loads wire bytes into a multi_buffer as one or two committed
|
||||
segments, split at `split` (no split if split is 0 or past the end).
|
||||
*/
|
||||
static void
|
||||
load(
|
||||
boost::beast::multi_buffer& rb,
|
||||
std::vector<std::uint8_t> const& wire,
|
||||
std::size_t split = 0)
|
||||
{
|
||||
auto commit = [&rb](std::uint8_t const* p, std::size_t n) {
|
||||
if (n != 0)
|
||||
rb.commit(boost::asio::buffer_copy(
|
||||
rb.prepare(n), boost::asio::buffer(p, n)));
|
||||
};
|
||||
|
||||
if (split == 0 || split >= wire.size())
|
||||
{
|
||||
commit(wire.data(), wire.size());
|
||||
return;
|
||||
}
|
||||
commit(wire.data(), split);
|
||||
commit(wire.data() + split, wire.size() - split);
|
||||
}
|
||||
|
||||
/** Mirrors the consume loop in PeerImp::onReadMessage. */
|
||||
static boost::system::error_code
|
||||
drain(boost::beast::multi_buffer& rb, Handler& handler)
|
||||
{
|
||||
std::size_t hint = 0;
|
||||
while (rb.size() > 0)
|
||||
{
|
||||
auto const [consumed, ec] =
|
||||
invokeProtocolMessage(rb.data(), handler, hint);
|
||||
if (ec)
|
||||
return ec;
|
||||
if (consumed == 0)
|
||||
break;
|
||||
rb.consume(consumed);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::vector<std::uint8_t>
|
||||
frame(
|
||||
::google::protobuf::Message const& m,
|
||||
protocol::MessageType type,
|
||||
Compressed compressed = Compressed::Off)
|
||||
{
|
||||
Message msg(m, type);
|
||||
return msg.getBuffer(compressed);
|
||||
}
|
||||
|
||||
static protocol::TMPing
|
||||
makePing(protocol::TMPing::pingType type, std::uint32_t seq)
|
||||
{
|
||||
protocol::TMPing ping;
|
||||
ping.set_type(type);
|
||||
ping.set_seq(seq);
|
||||
return ping;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::shared_ptr<T>
|
||||
receivedAs(Handler const& h, std::size_t i)
|
||||
{
|
||||
if (!BEAST_EXPECT(i < h.received.size()))
|
||||
return {};
|
||||
auto p = std::dynamic_pointer_cast<T>(h.received[i]);
|
||||
BEAST_EXPECT(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
void
|
||||
testCoalescedUncompressed()
|
||||
{
|
||||
testcase("Coalesced uncompressed frames");
|
||||
|
||||
auto const first = makePing(protocol::TMPing::ptPING, 7);
|
||||
auto second = makePing(protocol::TMPing::ptPONG, 99);
|
||||
second.set_nettime(5);
|
||||
|
||||
auto wire = frame(first, protocol::mtPING);
|
||||
auto const firstSize = wire.size();
|
||||
auto const tail = frame(second, protocol::mtPING);
|
||||
wire.insert(wire.end(), tail.begin(), tail.end());
|
||||
|
||||
// Both frames delivered by a single read, with every possible
|
||||
// segmentation of the read buffer (0 means one contiguous segment).
|
||||
for (std::size_t split = 0; split < wire.size(); ++split)
|
||||
{
|
||||
boost::beast::multi_buffer rb;
|
||||
load(rb, wire, split);
|
||||
|
||||
Handler h;
|
||||
auto const ec = drain(rb, h);
|
||||
|
||||
BEAST_EXPECTS(
|
||||
!ec, "split " + std::to_string(split) + ": " + ec.message());
|
||||
BEAST_EXPECT(rb.size() == 0);
|
||||
if (!BEAST_EXPECT(h.received.size() == 2))
|
||||
continue;
|
||||
|
||||
auto const a = receivedAs<protocol::TMPing>(h, 0);
|
||||
auto const b = receivedAs<protocol::TMPing>(h, 1);
|
||||
if (!a || !b)
|
||||
continue;
|
||||
BEAST_EXPECT(a->type() == protocol::TMPing::ptPING);
|
||||
BEAST_EXPECT(a->seq() == 7);
|
||||
BEAST_EXPECT(!a->has_nettime());
|
||||
BEAST_EXPECT(b->type() == protocol::TMPing::ptPONG);
|
||||
BEAST_EXPECT(b->seq() == 99);
|
||||
BEAST_EXPECT(b->nettime() == 5);
|
||||
}
|
||||
|
||||
// The first frame on its own, followed by a partial second frame,
|
||||
// is delivered alone and the remainder is left for the next read.
|
||||
{
|
||||
boost::beast::multi_buffer rb;
|
||||
std::vector<std::uint8_t> partial(
|
||||
wire.begin(), wire.begin() + firstSize + 3);
|
||||
load(rb, partial);
|
||||
|
||||
Handler h;
|
||||
BEAST_EXPECT(!drain(rb, h));
|
||||
BEAST_EXPECT(h.received.size() == 1);
|
||||
BEAST_EXPECT(rb.size() == 3);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testUncompressedThenCompressed()
|
||||
{
|
||||
testcase("Uncompressed frame followed by compressed frame");
|
||||
|
||||
auto const ping = makePing(protocol::TMPing::ptPING, 1234);
|
||||
|
||||
protocol::TMTransaction tx;
|
||||
tx.set_rawtransaction(std::string(4096, 'x'));
|
||||
tx.set_status(protocol::tsNEW);
|
||||
|
||||
auto wire = frame(ping, protocol::mtPING);
|
||||
auto const txFrame = frame(tx, protocol::mtTRANSACTION, Compressed::On);
|
||||
// The second frame must really be compressed for this test to mean
|
||||
// anything: its header is what the first parse used to run into.
|
||||
if (!BEAST_EXPECT(!txFrame.empty() && (txFrame[0] & 0x80)))
|
||||
return;
|
||||
wire.insert(wire.end(), txFrame.begin(), txFrame.end());
|
||||
|
||||
for (std::size_t split = 0; split < wire.size(); split += 7)
|
||||
{
|
||||
boost::beast::multi_buffer rb;
|
||||
load(rb, wire, split);
|
||||
|
||||
Handler h;
|
||||
h.compression = true;
|
||||
auto const ec = drain(rb, h);
|
||||
|
||||
BEAST_EXPECTS(
|
||||
!ec, "split " + std::to_string(split) + ": " + ec.message());
|
||||
BEAST_EXPECT(rb.size() == 0);
|
||||
if (!BEAST_EXPECT(h.received.size() == 2))
|
||||
continue;
|
||||
|
||||
auto const p = receivedAs<protocol::TMPing>(h, 0);
|
||||
auto const t = receivedAs<protocol::TMTransaction>(h, 1);
|
||||
if (!p || !t)
|
||||
continue;
|
||||
BEAST_EXPECT(p->type() == protocol::TMPing::ptPING);
|
||||
BEAST_EXPECT(p->seq() == 1234);
|
||||
BEAST_EXPECT(t->rawtransaction() == tx.rawtransaction());
|
||||
BEAST_EXPECT(t->status() == protocol::tsNEW);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDeclaredSizeIsAuthoritative()
|
||||
{
|
||||
testcase("Declared payload size bounds the parse");
|
||||
|
||||
auto const ping = makePing(protocol::TMPing::ptPING, 7);
|
||||
auto wire = frame(ping, protocol::mtPING);
|
||||
|
||||
// Shrink the declared payload size by one byte without removing the
|
||||
// byte from the stream. The declared payload alone is a truncated
|
||||
// TMPing, so the frame must be rejected rather than completed from
|
||||
// bytes that lie beyond it.
|
||||
std::uint32_t declared = (std::uint32_t(wire[0]) << 24) |
|
||||
(std::uint32_t(wire[1]) << 16) | (std::uint32_t(wire[2]) << 8) |
|
||||
std::uint32_t(wire[3]);
|
||||
if (!BEAST_EXPECT(declared > 0))
|
||||
return;
|
||||
--declared;
|
||||
wire[0] = static_cast<std::uint8_t>(declared >> 24);
|
||||
wire[1] = static_cast<std::uint8_t>(declared >> 16);
|
||||
wire[2] = static_cast<std::uint8_t>(declared >> 8);
|
||||
wire[3] = static_cast<std::uint8_t>(declared);
|
||||
|
||||
boost::beast::multi_buffer rb;
|
||||
load(rb, wire);
|
||||
|
||||
Handler h;
|
||||
auto const ec = drain(rb, h);
|
||||
BEAST_EXPECT(ec == boost::system::errc::bad_message);
|
||||
BEAST_EXPECT(h.received.empty());
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testCoalescedUncompressed();
|
||||
testUncompressedThenCompressed();
|
||||
testDeclaredSizeIsAuthoritative();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ProtocolMessage, overlay, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -1,466 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/overlay/detail/XUSH.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class XUSH_test : public beast::unit_test::suite
|
||||
{
|
||||
using Status = xush::Reassembler::Status;
|
||||
|
||||
static beast::IP::Endpoint
|
||||
ep(std::string const& s)
|
||||
{
|
||||
return beast::IP::Endpoint::from_string(s);
|
||||
}
|
||||
|
||||
static Slice
|
||||
slice(Buffer const& b)
|
||||
{
|
||||
return Slice(b.data(), b.size());
|
||||
}
|
||||
|
||||
static Buffer
|
||||
makeTxn(std::size_t size)
|
||||
{
|
||||
Buffer b(size);
|
||||
for (std::size_t i = 0; i < size; ++i)
|
||||
b.data()[i] = static_cast<std::uint8_t>(i * 31 + 7);
|
||||
return b;
|
||||
}
|
||||
|
||||
static void
|
||||
putU32(std::uint8_t* p, std::uint32_t v)
|
||||
{
|
||||
p[0] = static_cast<std::uint8_t>(v >> 24);
|
||||
p[1] = static_cast<std::uint8_t>(v >> 16);
|
||||
p[2] = static_cast<std::uint8_t>(v >> 8);
|
||||
p[3] = static_cast<std::uint8_t>(v);
|
||||
}
|
||||
|
||||
// A XUSHTXNF datagram with an arbitrary (possibly invalid) header
|
||||
static Buffer
|
||||
rawFragment(
|
||||
uint256 const& txid,
|
||||
std::uint32_t totalSize,
|
||||
std::uint32_t count,
|
||||
std::uint32_t index,
|
||||
std::size_t payloadSize)
|
||||
{
|
||||
Buffer b(xush::txnHeaderSize + payloadSize);
|
||||
std::memset(b.data(), 0xAB, b.size());
|
||||
std::memcpy(b.data(), "XUSHTXNF", xush::tagSize);
|
||||
std::memcpy(b.data() + xush::tagSize, txid.data(), txid.size());
|
||||
putU32(b.data() + 40, totalSize);
|
||||
putU32(b.data() + 44, count);
|
||||
putU32(b.data() + 48, index);
|
||||
return b;
|
||||
}
|
||||
|
||||
// A XUSHPEER datagram with arbitrary (possibly invalid) counts
|
||||
static Buffer
|
||||
rawPeers(std::uint8_t n4, std::uint8_t n6, std::size_t bodySize)
|
||||
{
|
||||
Buffer b(xush::tagSize + 2 + bodySize);
|
||||
std::memset(b.data(), 0, b.size());
|
||||
std::memcpy(b.data(), "XUSHPEER", xush::tagSize);
|
||||
b.data()[8] = n4;
|
||||
b.data()[9] = n6;
|
||||
return b;
|
||||
}
|
||||
|
||||
void
|
||||
testClassify()
|
||||
{
|
||||
testcase("classify");
|
||||
using namespace xush;
|
||||
|
||||
auto type = [](std::string const& s) { return classify(makeSlice(s)); };
|
||||
|
||||
BEAST_EXPECT(type("XUSHPEER") == MessageType::peers);
|
||||
BEAST_EXPECT(type("XUSHPEER and more") == MessageType::peers);
|
||||
BEAST_EXPECT(type("XUSHTXNF") == MessageType::txn);
|
||||
BEAST_EXPECT(type("XUSHPING") == MessageType::unknown);
|
||||
BEAST_EXPECT(type("XUSHPEE") == MessageType::unknown);
|
||||
BEAST_EXPECT(type("") == MessageType::unknown);
|
||||
BEAST_EXPECT(type("{\"command\":\"ping\"}") == MessageType::unknown);
|
||||
}
|
||||
|
||||
void
|
||||
testPeers()
|
||||
{
|
||||
testcase("XUSHPEER");
|
||||
using namespace xush;
|
||||
|
||||
std::vector<beast::IP::Endpoint> const endpoints{
|
||||
ep("1.2.3.4:51235"), ep("[2001:db8::1]:21337"), ep("5.6.7.8:1")};
|
||||
|
||||
auto const datagram = encodePeers(endpoints);
|
||||
BEAST_EXPECT(datagram.size() == tagSize + 2 + 2 * 8 + 20);
|
||||
BEAST_EXPECT(classify(slice(datagram)) == MessageType::peers);
|
||||
|
||||
if (auto const decoded = decodePeers(slice(datagram));
|
||||
BEAST_EXPECT(decoded))
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
std::set<beast::IP::Endpoint>(
|
||||
decoded->begin(), decoded->end()) ==
|
||||
std::set<beast::IP::Endpoint>(
|
||||
endpoints.begin(), endpoints.end()));
|
||||
}
|
||||
|
||||
// An empty advertisement is valid
|
||||
if (auto const decoded = decodePeers(slice(encodePeers({})));
|
||||
BEAST_EXPECT(decoded))
|
||||
BEAST_EXPECT(decoded->empty());
|
||||
|
||||
// At most maxAdvertisedPeers are advertised, and the result fits in
|
||||
// a single datagram even if they are all IPv6
|
||||
{
|
||||
std::vector<beast::IP::Endpoint> many;
|
||||
for (std::uint16_t i = 1; i <= maxAdvertisedPeers + 10; ++i)
|
||||
many.emplace_back(
|
||||
boost::asio::ip::make_address("2001:db8::1"), i);
|
||||
|
||||
auto const big = encodePeers(many);
|
||||
BEAST_EXPECT(big.size() <= maxDatagramSize);
|
||||
|
||||
auto const decoded = decodePeers(slice(big));
|
||||
BEAST_EXPECT(decoded && decoded->size() == maxAdvertisedPeers);
|
||||
}
|
||||
|
||||
// Truncated or padded
|
||||
BEAST_EXPECT(!decodePeers(Slice(datagram.data(), datagram.size() - 1)));
|
||||
{
|
||||
Buffer padded(datagram.size() + 1);
|
||||
std::memcpy(padded.data(), datagram.data(), datagram.size());
|
||||
BEAST_EXPECT(!decodePeers(slice(padded)));
|
||||
}
|
||||
BEAST_EXPECT(!decodePeers(Slice(datagram.data(), tagSize + 1)));
|
||||
|
||||
// Too many endpoints, even with a consistent size
|
||||
BEAST_EXPECT(!decodePeers(slice(rawPeers(
|
||||
maxAdvertisedPeers + 1, 0, (maxAdvertisedPeers + 1) * 8))));
|
||||
BEAST_EXPECT(decodePeers(
|
||||
slice(rawPeers(maxAdvertisedPeers, 0, maxAdvertisedPeers * 8))));
|
||||
|
||||
// A port that doesn't fit in 16 bits
|
||||
{
|
||||
auto bad = rawPeers(1, 0, 8);
|
||||
putU32(bad.data() + tagSize + 2 + 4, 0x10000);
|
||||
BEAST_EXPECT(!decodePeers(slice(bad)));
|
||||
putU32(bad.data() + tagSize + 2 + 4, 0xFFFF);
|
||||
BEAST_EXPECT(decodePeers(slice(bad)));
|
||||
}
|
||||
|
||||
// Wrong tag
|
||||
BEAST_EXPECT(!decodePeers(slice(rawFragment(uint256{1}, 1, 1, 0, 1))));
|
||||
}
|
||||
|
||||
void
|
||||
testTxnEncoding()
|
||||
{
|
||||
testcase("XUSHTXNF encoding");
|
||||
using namespace xush;
|
||||
|
||||
uint256 const txid{42};
|
||||
|
||||
// A typical transaction fits in a single datagram
|
||||
{
|
||||
auto const txn = makeTxn(200);
|
||||
auto const datagrams = encodeTxn(slice(txn), txid);
|
||||
BEAST_EXPECT(datagrams.size() == 1);
|
||||
BEAST_EXPECT(datagrams[0].size() == txnHeaderSize + 200);
|
||||
|
||||
auto const fragment = decodeTxnFragment(slice(datagrams[0]));
|
||||
if (BEAST_EXPECT(fragment))
|
||||
{
|
||||
BEAST_EXPECT(fragment->txid == txid);
|
||||
BEAST_EXPECT(fragment->totalSize == 200);
|
||||
BEAST_EXPECT(fragment->count == 1);
|
||||
BEAST_EXPECT(fragment->index == 0);
|
||||
BEAST_EXPECT(fragment->payload == slice(txn));
|
||||
}
|
||||
}
|
||||
|
||||
// Larger transactions are split, and no datagram is too large
|
||||
for (auto const size :
|
||||
{maxFragmentPayload,
|
||||
maxFragmentPayload + 1,
|
||||
3 * maxFragmentPayload,
|
||||
3 * maxFragmentPayload + 17})
|
||||
{
|
||||
auto const txn = makeTxn(size);
|
||||
auto const datagrams = encodeTxn(slice(txn), txid);
|
||||
auto const count =
|
||||
(size + maxFragmentPayload - 1) / maxFragmentPayload;
|
||||
BEAST_EXPECT(datagrams.size() == count);
|
||||
|
||||
std::size_t total = 0;
|
||||
for (std::size_t i = 0; i < datagrams.size(); ++i)
|
||||
{
|
||||
BEAST_EXPECT(datagrams[i].size() <= maxDatagramSize);
|
||||
auto const fragment = decodeTxnFragment(slice(datagrams[i]));
|
||||
if (!BEAST_EXPECT(fragment))
|
||||
continue;
|
||||
BEAST_EXPECT(fragment->index == i);
|
||||
BEAST_EXPECT(fragment->count == count);
|
||||
BEAST_EXPECT(fragment->totalSize == size);
|
||||
BEAST_EXPECT(
|
||||
fragment->payload ==
|
||||
Slice(
|
||||
txn.data() + i * maxFragmentPayload,
|
||||
fragment->payload.size()));
|
||||
total += fragment->payload.size();
|
||||
}
|
||||
BEAST_EXPECT(total == size);
|
||||
}
|
||||
|
||||
// Limits
|
||||
BEAST_EXPECT(encodeTxn(Slice{}, txid).empty());
|
||||
BEAST_EXPECT(encodeTxn(slice(Buffer(maxTxnSize + 1)), txid).empty());
|
||||
{
|
||||
auto const datagrams = encodeTxn(slice(Buffer(maxTxnSize)), txid);
|
||||
BEAST_EXPECT(!datagrams.empty());
|
||||
BEAST_EXPECT(datagrams.size() <= maxFragments);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testTxnDecoding()
|
||||
{
|
||||
testcase("XUSHTXNF decoding");
|
||||
using namespace xush;
|
||||
|
||||
uint256 const id{7};
|
||||
auto decode = [](Buffer const& b) {
|
||||
return decodeTxnFragment(slice(b)).has_value();
|
||||
};
|
||||
|
||||
BEAST_EXPECT(decode(rawFragment(id, 10, 2, 1, 5)));
|
||||
BEAST_EXPECT(decode(rawFragment(id, 10, 1, 0, 10)));
|
||||
BEAST_EXPECT(decode(rawFragment(id, maxTxnSize, maxFragments, 0, 1)));
|
||||
|
||||
// No payload
|
||||
BEAST_EXPECT(!decode(rawFragment(id, 10, 1, 0, 0)));
|
||||
// Bad total size
|
||||
BEAST_EXPECT(!decode(rawFragment(id, 0, 1, 0, 1)));
|
||||
BEAST_EXPECT(!decode(rawFragment(id, maxTxnSize + 1, 2, 0, 1)));
|
||||
// Bad fragment count
|
||||
BEAST_EXPECT(!decode(rawFragment(id, 10, 0, 0, 1)));
|
||||
BEAST_EXPECT(!decode(rawFragment(id, 10, 11, 0, 1)));
|
||||
BEAST_EXPECT(
|
||||
!decode(rawFragment(id, maxTxnSize, maxFragments + 1, 0, 1)));
|
||||
// Bad index
|
||||
BEAST_EXPECT(!decode(rawFragment(id, 10, 2, 2, 1)));
|
||||
// Payload larger than the transaction
|
||||
BEAST_EXPECT(!decode(rawFragment(id, 10, 2, 0, 11)));
|
||||
// Wrong tag
|
||||
BEAST_EXPECT(!decodeTxnFragment(slice(encodePeers({}))));
|
||||
}
|
||||
|
||||
void
|
||||
testReassembly()
|
||||
{
|
||||
testcase("reassembly");
|
||||
using namespace xush;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto const now = Reassembler::clock_type::now();
|
||||
auto const alice = ep("1.2.3.4:5000");
|
||||
auto const bob = ep("5.6.7.8:5000");
|
||||
auto const carol = ep("9.9.9.9:5000");
|
||||
|
||||
uint256 const id1{1};
|
||||
uint256 const id2{2};
|
||||
|
||||
auto const txn1 = makeTxn(2 * maxFragmentPayload + 100);
|
||||
auto const txn2 = makeTxn(2 * maxFragmentPayload + 200);
|
||||
auto const d1 = encodeTxn(slice(txn1), id1);
|
||||
auto const d2 = encodeTxn(slice(txn2), id2);
|
||||
BEAST_EXPECT(d1.size() == 3 && d2.size() == 3);
|
||||
|
||||
auto f1 = [&](std::size_t i) {
|
||||
return *decodeTxnFragment(slice(d1[i]));
|
||||
};
|
||||
auto f2 = [&](std::size_t i) {
|
||||
return *decodeTxnFragment(slice(d2[i]));
|
||||
};
|
||||
|
||||
// Out of order, with a repeated fragment
|
||||
{
|
||||
Reassembler r;
|
||||
BEAST_EXPECT(r.add(alice, f1(2), now).status == Status::pending);
|
||||
BEAST_EXPECT(r.add(alice, f1(0), now).status == Status::pending);
|
||||
BEAST_EXPECT(r.add(alice, f1(0), now).status == Status::pending);
|
||||
BEAST_EXPECT(r.pending() == 1);
|
||||
|
||||
auto const result = r.add(alice, f1(1), now);
|
||||
BEAST_EXPECT(result.status == Status::complete);
|
||||
BEAST_EXPECT(result.txn == txn1);
|
||||
BEAST_EXPECT(r.pending() == 0);
|
||||
BEAST_EXPECT(r.bytes() == 0);
|
||||
}
|
||||
|
||||
// A transaction in a single datagram needs no state
|
||||
{
|
||||
Reassembler r;
|
||||
auto const small = makeTxn(100);
|
||||
auto const d = encodeTxn(slice(small), id2);
|
||||
auto const result =
|
||||
r.add(alice, *decodeTxnFragment(slice(d[0])), now);
|
||||
BEAST_EXPECT(result.status == Status::complete);
|
||||
BEAST_EXPECT(result.txn == small);
|
||||
BEAST_EXPECT(r.pending() == 0);
|
||||
|
||||
// ... but must be consistent
|
||||
auto bad = *decodeTxnFragment(slice(d[0]));
|
||||
bad.totalSize += 1;
|
||||
BEAST_EXPECT(r.add(alice, bad, now).status == Status::invalid);
|
||||
}
|
||||
|
||||
// Senders are kept apart
|
||||
{
|
||||
Reassembler r;
|
||||
BEAST_EXPECT(r.add(alice, f1(0), now).status == Status::pending);
|
||||
BEAST_EXPECT(r.add(alice, f1(1), now).status == Status::pending);
|
||||
BEAST_EXPECT(r.add(bob, f1(2), now).status == Status::pending);
|
||||
BEAST_EXPECT(r.pending() == 2);
|
||||
BEAST_EXPECT(r.add(alice, f1(2), now).status == Status::complete);
|
||||
BEAST_EXPECT(r.pending() == 1);
|
||||
}
|
||||
|
||||
// A header that disagrees with earlier fragments
|
||||
{
|
||||
Reassembler r;
|
||||
BEAST_EXPECT(r.add(alice, f1(0), now).status == Status::pending);
|
||||
auto bad = f1(1);
|
||||
bad.count += 1;
|
||||
BEAST_EXPECT(r.add(alice, bad, now).status == Status::invalid);
|
||||
BEAST_EXPECT(r.pending() == 0);
|
||||
BEAST_EXPECT(r.bytes() == 0);
|
||||
}
|
||||
|
||||
// More bytes than the advertised total size
|
||||
{
|
||||
Reassembler r;
|
||||
auto first = f1(0);
|
||||
first.count = 2;
|
||||
first.totalSize = maxFragmentPayload + 10;
|
||||
auto second = f1(1);
|
||||
second.count = 2;
|
||||
second.totalSize = first.totalSize;
|
||||
BEAST_EXPECT(r.add(alice, first, now).status == Status::pending);
|
||||
BEAST_EXPECT(r.add(alice, second, now).status == Status::invalid);
|
||||
BEAST_EXPECT(r.pending() == 0);
|
||||
}
|
||||
|
||||
// Every fragment present but fewer bytes than the total size
|
||||
{
|
||||
Reassembler r;
|
||||
auto first = f1(0);
|
||||
first.count = 2;
|
||||
first.totalSize = 2 * maxFragmentPayload + 5;
|
||||
auto second = f1(1);
|
||||
second.count = 2;
|
||||
second.totalSize = first.totalSize;
|
||||
BEAST_EXPECT(r.add(alice, first, now).status == Status::pending);
|
||||
BEAST_EXPECT(r.add(alice, second, now).status == Status::invalid);
|
||||
BEAST_EXPECT(r.pending() == 0);
|
||||
}
|
||||
|
||||
// Partial transactions time out
|
||||
{
|
||||
Reassembler r(
|
||||
Reassembler::defaultMaxPending,
|
||||
Reassembler::defaultMaxBytes,
|
||||
30s);
|
||||
BEAST_EXPECT(r.add(alice, f1(0), now).status == Status::pending);
|
||||
BEAST_EXPECT(r.add(alice, f1(1), now).status == Status::pending);
|
||||
|
||||
// Not yet
|
||||
BEAST_EXPECT(
|
||||
r.add(bob, f2(0), now + 30s).status == Status::pending);
|
||||
BEAST_EXPECT(r.pending() == 2);
|
||||
|
||||
// Now: the first transaction has to start over
|
||||
BEAST_EXPECT(
|
||||
r.add(bob, f2(1), now + 31s).status == Status::pending);
|
||||
BEAST_EXPECT(r.pending() == 1);
|
||||
BEAST_EXPECT(
|
||||
r.add(alice, f1(2), now + 31s).status == Status::pending);
|
||||
BEAST_EXPECT(r.pending() == 2);
|
||||
}
|
||||
|
||||
// The number of partial transactions is limited
|
||||
{
|
||||
Reassembler r(2, Reassembler::defaultMaxBytes, 30s);
|
||||
BEAST_EXPECT(r.add(alice, f1(0), now).status == Status::pending);
|
||||
BEAST_EXPECT(r.add(bob, f1(0), now).status == Status::pending);
|
||||
BEAST_EXPECT(r.add(carol, f1(0), now).status == Status::overloaded);
|
||||
BEAST_EXPECT(r.pending() == 2);
|
||||
|
||||
// Fragments of transactions already being rebuilt are accepted
|
||||
BEAST_EXPECT(r.add(alice, f1(1), now).status == Status::pending);
|
||||
BEAST_EXPECT(r.add(alice, f1(2), now).status == Status::complete);
|
||||
BEAST_EXPECT(r.add(carol, f1(0), now).status == Status::pending);
|
||||
}
|
||||
|
||||
// The number of bytes buffered is limited
|
||||
{
|
||||
Reassembler r(
|
||||
Reassembler::defaultMaxPending,
|
||||
maxFragmentPayload + Reassembler::fragmentOverhead,
|
||||
30s);
|
||||
BEAST_EXPECT(r.add(alice, f1(0), now).status == Status::pending);
|
||||
BEAST_EXPECT(r.add(alice, f1(1), now).status == Status::overloaded);
|
||||
BEAST_EXPECT(r.add(bob, f1(0), now).status == Status::overloaded);
|
||||
BEAST_EXPECT(r.pending() == 1);
|
||||
BEAST_EXPECT(
|
||||
r.bytes() ==
|
||||
maxFragmentPayload + Reassembler::fragmentOverhead);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testClassify();
|
||||
testPeers();
|
||||
testTxnEncoding();
|
||||
testTxnDecoding();
|
||||
testReassembly();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(XUSH, overlay, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -1,276 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <test/unit_test/SuiteJournal.h>
|
||||
#include <xrpld/peerfinder/PeerfinderManager.h>
|
||||
#include <xrpld/peerfinder/detail/Tuning.h>
|
||||
#include <xrpld/peerfinder/make_Manager.h>
|
||||
#include <xrpl/basics/BasicConfig.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/beast/insight/NullCollector.h>
|
||||
#include <xrpl/beast/net/IPAddressConversion.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
namespace PeerFinder {
|
||||
|
||||
// Tests the UDP Superhighway (XUSH) endpoint set kept by PeerFinder::Manager
|
||||
class HighwayPeers_test : public beast::unit_test::suite
|
||||
{
|
||||
test::SuiteJournal journal_{"HighwayPeers_test", *this};
|
||||
|
||||
struct Fixture
|
||||
{
|
||||
boost::asio::io_context io;
|
||||
TestStopwatch clock;
|
||||
BasicConfig basicConfig;
|
||||
std::unique_ptr<Manager> manager;
|
||||
|
||||
Fixture(beast::Journal journal, bool enabled, bool peerPrivate)
|
||||
: manager(make_Manager(
|
||||
io,
|
||||
clock,
|
||||
journal,
|
||||
basicConfig,
|
||||
beast::insight::NullCollector::New(),
|
||||
false))
|
||||
{
|
||||
Config config;
|
||||
config.udpHighway = enabled;
|
||||
config.peerPrivate = peerPrivate;
|
||||
manager->setConfig(config);
|
||||
}
|
||||
|
||||
~Fixture()
|
||||
{
|
||||
manager->stop();
|
||||
}
|
||||
};
|
||||
|
||||
static beast::IP::Endpoint
|
||||
ep(std::string const& s)
|
||||
{
|
||||
return beast::IP::Endpoint::from_string(s);
|
||||
}
|
||||
|
||||
// A distinct public IPv4 endpoint
|
||||
static beast::IP::Endpoint
|
||||
publicEp(std::uint32_t i)
|
||||
{
|
||||
return beast::IP::Endpoint(
|
||||
boost::asio::ip::address_v4(0x2C000000 + i), 51235);
|
||||
}
|
||||
|
||||
static std::set<beast::IP::Endpoint>
|
||||
asSet(std::vector<beast::IP::Endpoint> const& v)
|
||||
{
|
||||
return {v.begin(), v.end()};
|
||||
}
|
||||
|
||||
void
|
||||
testDisabled()
|
||||
{
|
||||
testcase("disabled");
|
||||
Fixture f(journal_, false, false);
|
||||
|
||||
f.manager->addFixedPeer("fixed", {ep("10.0.0.1:51235")});
|
||||
f.manager->add_highway_peers({ep("8.8.8.8:51235")});
|
||||
f.manager->onRedirects(
|
||||
beast::IP::to_asio_endpoint(ep("8.8.4.4:51235")),
|
||||
{beast::IP::to_asio_endpoint(ep("1.1.1.1:51235"))});
|
||||
|
||||
BEAST_EXPECT(f.manager->highway_targets(100).empty());
|
||||
BEAST_EXPECT(f.manager->highway_adverts(100).empty());
|
||||
}
|
||||
|
||||
void
|
||||
testLearning()
|
||||
{
|
||||
testcase("learning");
|
||||
Fixture f(journal_, true, false);
|
||||
|
||||
// Only valid public endpoints are learned from other servers
|
||||
f.manager->add_highway_peers(
|
||||
{ep("8.8.8.8:51235"),
|
||||
ep("10.0.0.1:51235"),
|
||||
ep("127.0.0.1:51235"),
|
||||
ep("9.9.9.9:0"),
|
||||
ep("0.0.0.0:51235")});
|
||||
BEAST_EXPECT(
|
||||
asSet(f.manager->highway_targets(100)) ==
|
||||
std::set<beast::IP::Endpoint>{ep("8.8.8.8:51235")});
|
||||
|
||||
// Fixed peers are trusted, even with a private address, but they
|
||||
// are never advertised
|
||||
f.manager->addFixedPeer("fixed", {ep("10.0.0.2:51235")});
|
||||
BEAST_EXPECT(
|
||||
asSet(f.manager->highway_targets(100)) ==
|
||||
(std::set<beast::IP::Endpoint>{
|
||||
ep("8.8.8.8:51235"), ep("10.0.0.2:51235")}));
|
||||
BEAST_EXPECT(
|
||||
asSet(f.manager->highway_adverts(100)) ==
|
||||
std::set<beast::IP::Endpoint>{ep("8.8.8.8:51235")});
|
||||
|
||||
// A learned endpoint that is later configured as fixed stops being
|
||||
// advertised
|
||||
f.manager->addFixedPeer("fixed2", {ep("8.8.8.8:51235")});
|
||||
BEAST_EXPECT(f.manager->highway_adverts(100).empty());
|
||||
BEAST_EXPECT(f.manager->highway_targets(100).size() == 2);
|
||||
|
||||
// Redirects are learned too
|
||||
f.manager->onRedirects(
|
||||
beast::IP::to_asio_endpoint(ep("8.8.4.4:51235")),
|
||||
{beast::IP::to_asio_endpoint(ep("1.1.1.1:51235"))});
|
||||
BEAST_EXPECT(f.manager->highway_targets(100).size() == 3);
|
||||
}
|
||||
|
||||
void
|
||||
testSampling()
|
||||
{
|
||||
testcase("sampling");
|
||||
Fixture f(journal_, true, false);
|
||||
|
||||
std::vector<beast::IP::Endpoint> endpoints;
|
||||
for (std::uint32_t i = 0; i < 100; ++i)
|
||||
endpoints.push_back(publicEp(i));
|
||||
f.manager->add_highway_peers(endpoints);
|
||||
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
auto const targets = f.manager->highway_targets(16);
|
||||
BEAST_EXPECT(targets.size() == 16);
|
||||
BEAST_EXPECT(asSet(targets).size() == 16);
|
||||
}
|
||||
|
||||
BEAST_EXPECT(
|
||||
asSet(f.manager->highway_targets(1000)) == asSet(endpoints));
|
||||
BEAST_EXPECT(f.manager->highway_targets(0).empty());
|
||||
}
|
||||
|
||||
void
|
||||
testPrivate()
|
||||
{
|
||||
testcase("private");
|
||||
Fixture f(journal_, true, true);
|
||||
|
||||
f.manager->addFixedPeer("fixed", {ep("10.0.0.2:51235")});
|
||||
f.manager->add_highway_peers({ep("8.8.8.8:51235")});
|
||||
|
||||
// A private server only talks to its fixed peers, and advertises
|
||||
// nothing
|
||||
BEAST_EXPECT(
|
||||
asSet(f.manager->highway_targets(100)) ==
|
||||
std::set<beast::IP::Endpoint>{ep("10.0.0.2:51235")});
|
||||
BEAST_EXPECT(f.manager->highway_adverts(100).empty());
|
||||
}
|
||||
|
||||
void
|
||||
testExpiry()
|
||||
{
|
||||
testcase("expiry");
|
||||
using namespace std::chrono_literals;
|
||||
Fixture f(journal_, true, false);
|
||||
|
||||
auto const ttl = Tuning::highwayPeerSecondsToLive;
|
||||
auto const advertised = ep("8.8.8.8:51235");
|
||||
auto const redirected = ep("1.1.1.1:51235");
|
||||
auto const fixed = ep("10.0.0.2:51235");
|
||||
|
||||
f.manager->addFixedPeer("fixed", {fixed});
|
||||
f.manager->add_highway_peers({advertised});
|
||||
f.manager->add_highway_peers({redirected});
|
||||
|
||||
// Advertisements don't extend the life of an entry, but
|
||||
// authenticated sources (here, a redirect) do
|
||||
f.clock.advance(ttl / 2);
|
||||
f.manager->add_highway_peers({advertised, redirected});
|
||||
f.manager->onRedirects(
|
||||
beast::IP::to_asio_endpoint(ep("8.8.4.4:51235")),
|
||||
{beast::IP::to_asio_endpoint(redirected)});
|
||||
|
||||
f.clock.advance(ttl / 2);
|
||||
f.manager->once_per_second();
|
||||
BEAST_EXPECT(f.manager->highway_targets(100).size() == 3);
|
||||
|
||||
f.clock.advance(1s);
|
||||
f.manager->once_per_second();
|
||||
BEAST_EXPECT(
|
||||
asSet(f.manager->highway_targets(100)) ==
|
||||
(std::set<beast::IP::Endpoint>{redirected, fixed}));
|
||||
|
||||
f.clock.advance(ttl / 2);
|
||||
f.manager->once_per_second();
|
||||
BEAST_EXPECT(
|
||||
asSet(f.manager->highway_targets(100)) ==
|
||||
std::set<beast::IP::Endpoint>{fixed});
|
||||
|
||||
// An expired entry can be learned again
|
||||
f.manager->add_highway_peers({advertised});
|
||||
BEAST_EXPECT(f.manager->highway_targets(100).size() == 2);
|
||||
}
|
||||
|
||||
void
|
||||
testLimit()
|
||||
{
|
||||
testcase("limit");
|
||||
Fixture f(journal_, true, false);
|
||||
|
||||
auto const max = Tuning::highwayPeersMax;
|
||||
|
||||
std::vector<beast::IP::Endpoint> endpoints;
|
||||
for (std::uint32_t i = 0; i < max + 100; ++i)
|
||||
endpoints.push_back(publicEp(i));
|
||||
f.manager->add_highway_peers(endpoints);
|
||||
BEAST_EXPECT(f.manager->highway_targets(max + 100).size() == max);
|
||||
|
||||
// Fixed peers don't count towards the limit
|
||||
f.manager->addFixedPeer("fixed", {ep("10.0.0.2:51235")});
|
||||
BEAST_EXPECT(f.manager->highway_targets(max + 100).size() == max + 1);
|
||||
|
||||
// Disabling the highway forgets everything
|
||||
Config config;
|
||||
config.udpHighway = false;
|
||||
f.manager->setConfig(config);
|
||||
BEAST_EXPECT(f.manager->highway_targets(max + 100).empty());
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testDisabled();
|
||||
testLearning();
|
||||
testSampling();
|
||||
testPrivate();
|
||||
testExpiry();
|
||||
testLimit();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(HighwayPeers, peerfinder, ripple);
|
||||
|
||||
} // namespace PeerFinder
|
||||
} // namespace ripple
|
||||
@@ -52,10 +52,18 @@ public:
|
||||
for (std::uint8_t i = 0; i < 64; i++)
|
||||
{
|
||||
beast::rngfill(src, sizeof(src), default_prng());
|
||||
Seed const seed(src);
|
||||
Seed const seed({src, sizeof(src)});
|
||||
BEAST_EXPECT(memcmp(seed.data(), src, sizeof(src)) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
uint128 src;
|
||||
beast::rngfill(src.data(), src.size(), default_prng());
|
||||
Seed const seed(src);
|
||||
BEAST_EXPECT(memcmp(seed.data(), src.data(), src.size()) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
|
||||
@@ -18,22 +18,16 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx.h>
|
||||
#include <xrpld/app/ledger/Ledger.h>
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/core/ConfigSections.h>
|
||||
#include <xrpld/nodestore/detail/DatabasePinnedImp.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/beast/utility/temp_dir.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <grpc/impl/codegen/compression_types.h>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
namespace ripple {
|
||||
@@ -108,28 +102,14 @@ struct CatalogueEnvOpts
|
||||
};
|
||||
|
||||
// envconfig with pinned_type set so catalogue_load passes the config guard.
|
||||
//
|
||||
// envconfig() selects [node_db] type=rwdb, which is the tree-only null node
|
||||
// store: SHAMapStore never builds DatabasePinned on it, so catalogue_load
|
||||
// would refuse to run. Use the in-memory "memory" store for the hot side
|
||||
// instead. It is shared by path within the process, so each config gets its
|
||||
// own path (SHAMapStore creates the directory; nothing is written to it).
|
||||
// The pinned side uses rwdb, an in-memory persistent backend.
|
||||
// Uses rwdb for in-process speed (allowed in standalone).
|
||||
inline std::unique_ptr<Config>
|
||||
catalogueEnvconfig(CatalogueEnvOpts const& opts = {})
|
||||
{
|
||||
auto cfg = test::jtx::envconfig();
|
||||
auto& nodeDb = cfg->section(ConfigSection::nodeDatabase());
|
||||
if (opts.withPinnedType)
|
||||
{
|
||||
nodeDb.set("type", "memory");
|
||||
nodeDb.set(
|
||||
"path",
|
||||
(boost::filesystem::temp_directory_path() /
|
||||
boost::filesystem::unique_path("catalogue-test-%%%%-%%%%-%%%%"))
|
||||
.string());
|
||||
nodeDb.set("pinned_type", "rwdb");
|
||||
}
|
||||
nodeDb.set("online_delete", opts.onlineDelete);
|
||||
return cfg;
|
||||
}
|
||||
@@ -1087,621 +1067,6 @@ class Catalogue_test : public beast::unit_test::suite
|
||||
}
|
||||
|
||||
public:
|
||||
// ---- Editing uncompressed catalogues -----------------------------------
|
||||
//
|
||||
// Ledger record: seq, hash, txHash, accountHash, parentHash, drops,
|
||||
// closeFlags, closeTimeResolution, closeTime, parentCloseTime; then the
|
||||
// state map and the tx map, each a run of leaf records ending in
|
||||
// tnTERMINAL. A leaf record is type (1), key (32), and, unless it is a
|
||||
// tnREMOVE, size (4) and data.
|
||||
|
||||
static constexpr std::size_t catLedgerHeaderSize =
|
||||
4 + 32 * 4 + 8 + sizeof(int) + 4 + 8 + 8;
|
||||
|
||||
struct CatLedgerRecord
|
||||
{
|
||||
std::size_t header; // first byte of the ledger header
|
||||
std::size_t state; // first byte of the state map
|
||||
std::size_t tx; // first byte of the tx map
|
||||
std::size_t end; // one past the tx map's tnTERMINAL
|
||||
};
|
||||
|
||||
// Position just past the map that starts at `pos`.
|
||||
static std::size_t
|
||||
skipCatalogueMap(std::vector<std::uint8_t> const& bytes, std::size_t pos)
|
||||
{
|
||||
while (pos < bytes.size())
|
||||
{
|
||||
auto const type = bytes[pos++];
|
||||
if (type == 255) // tnTERMINAL
|
||||
return pos;
|
||||
pos += 32;
|
||||
if (type == 254) // tnREMOVE
|
||||
continue;
|
||||
if (pos + 4 > bytes.size())
|
||||
break;
|
||||
std::uint32_t size = 0;
|
||||
std::memcpy(&size, &bytes[pos], 4);
|
||||
pos += 4 + size;
|
||||
}
|
||||
return bytes.size();
|
||||
}
|
||||
|
||||
// Position of the size field of the first leaf (not a removal) in the
|
||||
// map that starts at `pos`, if the map has one.
|
||||
static std::optional<std::size_t>
|
||||
firstLeafSizeField(std::vector<std::uint8_t> const& bytes, std::size_t pos)
|
||||
{
|
||||
while (pos < bytes.size())
|
||||
{
|
||||
auto const type = bytes[pos];
|
||||
if (type == 255)
|
||||
return std::nullopt;
|
||||
if (type != 254)
|
||||
return pos + 1 + 32;
|
||||
pos += 1 + 32;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::vector<CatLedgerRecord>
|
||||
indexCatalogue(std::vector<std::uint8_t> const& bytes)
|
||||
{
|
||||
std::vector<CatLedgerRecord> records;
|
||||
std::size_t pos = sizeof(TestCATLHeader);
|
||||
while (pos + catLedgerHeaderSize <= bytes.size())
|
||||
{
|
||||
CatLedgerRecord r;
|
||||
r.header = pos;
|
||||
r.state = pos + catLedgerHeaderSize;
|
||||
r.tx = skipCatalogueMap(bytes, r.state);
|
||||
r.end = skipCatalogueMap(bytes, r.tx);
|
||||
records.push_back(r);
|
||||
pos = r.end;
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
static std::vector<std::uint8_t>
|
||||
readCatalogue(std::string const& path)
|
||||
{
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
return {
|
||||
std::istreambuf_iterator<char>(in),
|
||||
std::istreambuf_iterator<char>()};
|
||||
}
|
||||
|
||||
static void
|
||||
writeCatalogue(std::string const& path, std::vector<std::uint8_t> const& b)
|
||||
{
|
||||
std::ofstream out(path, std::ios::binary | std::ios::trunc);
|
||||
out.write(reinterpret_cast<char const*>(b.data()), b.size());
|
||||
}
|
||||
|
||||
static Json::Value
|
||||
loadCatalogue(test::jtx::Env& env, std::string const& path, bool ignoreHash)
|
||||
{
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::input_file] = path;
|
||||
if (ignoreHash)
|
||||
params[jss::ignore_hash] = true;
|
||||
return env.client().invoke("catalogue_load", params)[jss::result];
|
||||
}
|
||||
|
||||
static bool
|
||||
errorContains(Json::Value const& result, std::string const& what)
|
||||
{
|
||||
return result[jss::status] == "error" &&
|
||||
result[jss::error_message].asString().find(what) !=
|
||||
std::string::npos;
|
||||
}
|
||||
|
||||
void
|
||||
testCatalogueLoadFailures(FeatureBitset features)
|
||||
{
|
||||
testcase("catalogue_load: failures part way through a file");
|
||||
using namespace test::jtx;
|
||||
using beast::severities::kDisabled;
|
||||
|
||||
boost::filesystem::path tempDir =
|
||||
boost::filesystem::temp_directory_path() /
|
||||
boost::filesystem::unique_path();
|
||||
boost::filesystem::create_directories(tempDir);
|
||||
auto const goodPath = (tempDir / "good.catl").string();
|
||||
|
||||
// An uncompressed catalogue of ledgers 3-6, so records can be edited.
|
||||
{
|
||||
Env env{*this, catalogueEnvconfig(), features, nullptr, kDisabled};
|
||||
prepareLedgerData(env, 3);
|
||||
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::min_ledger] = 3;
|
||||
params[jss::max_ledger] = 6;
|
||||
params[jss::output_file] = goodPath;
|
||||
params[jss::compression_level] = 0;
|
||||
auto const result =
|
||||
env.client().invoke("catalogue_create", params)[jss::result];
|
||||
BEAST_EXPECT(result[jss::status] == jss::success);
|
||||
}
|
||||
|
||||
auto const good = readCatalogue(goodPath);
|
||||
auto const records = indexCatalogue(good);
|
||||
if (!BEAST_EXPECT(
|
||||
records.size() == 4 && records.back().end == good.size()))
|
||||
return;
|
||||
|
||||
// Write `bytes` to a new file, load it into a fresh server, and hand
|
||||
// the result and the resulting pinned set to `check`.
|
||||
int fileNo = 0;
|
||||
auto loadEdited = [&](std::vector<std::uint8_t> const& bytes,
|
||||
bool ignoreHash,
|
||||
auto&& check) {
|
||||
auto const path =
|
||||
(tempDir / ("edited" + std::to_string(++fileNo) + ".catl"))
|
||||
.string();
|
||||
writeCatalogue(path, bytes);
|
||||
Env env{*this, catalogueEnvconfig(), features, nullptr, kDisabled};
|
||||
auto const result = loadCatalogue(env, path, ignoreHash);
|
||||
check(
|
||||
result, env.app().getLedgerMaster().getPinnedLedgersRangeSet());
|
||||
};
|
||||
|
||||
auto const hashField = offsetof(TestCATLHeader, hash);
|
||||
auto const maxLedgerField = offsetof(TestCATLHeader, max_ledger);
|
||||
// Offsets within a ledger header record.
|
||||
constexpr std::size_t accountHashAt = 4 + 32 * 2;
|
||||
constexpr std::size_t dropsAt = 4 + 32 * 4;
|
||||
|
||||
// The stored checksum is wrong but every ledger is intact: all four
|
||||
// load, then the whole-file check fails and the run is rolled back.
|
||||
{
|
||||
auto bytes = good;
|
||||
bytes[hashField] ^= 0xff;
|
||||
loadEdited(
|
||||
bytes, false, [&](auto const& result, auto const& pinned) {
|
||||
BEAST_EXPECT(errorContains(
|
||||
result, "catalogue hash verification failed"));
|
||||
BEAST_EXPECT(pinned.empty());
|
||||
});
|
||||
}
|
||||
|
||||
// A ledger part way through is corrupt. The load stops there, the
|
||||
// rest of the file is still hashed, the checksum fails, and the
|
||||
// ledgers loaded before it are rolled back too.
|
||||
{
|
||||
auto bytes = good;
|
||||
bytes[records[2].header + dropsAt] ^= 0x01;
|
||||
loadEdited(
|
||||
bytes, false, [&](auto const& result, auto const& pinned) {
|
||||
BEAST_EXPECT(errorContains(
|
||||
result, "catalogue hash verification failed"));
|
||||
BEAST_EXPECT(pinned.empty());
|
||||
});
|
||||
}
|
||||
|
||||
// A checksum failure only rolls back what this run added: ledgers
|
||||
// that were already pinned stay pinned.
|
||||
{
|
||||
auto bytes = good;
|
||||
bytes[hashField] ^= 0xff;
|
||||
auto const badPath = (tempDir / "badsum.catl").string();
|
||||
writeCatalogue(badPath, bytes);
|
||||
|
||||
Env env{*this, catalogueEnvconfig(), features, nullptr, kDisabled};
|
||||
BEAST_EXPECT(
|
||||
loadCatalogue(env, goodPath, false)[jss::status] ==
|
||||
jss::success);
|
||||
BEAST_EXPECT(errorContains(
|
||||
loadCatalogue(env, badPath, false),
|
||||
"catalogue hash verification failed"));
|
||||
auto& lm = env.app().getLedgerMaster();
|
||||
BEAST_EXPECT(to_string(lm.getPinnedLedgersRangeSet()) == "3-6");
|
||||
|
||||
// The pinned store: an empty batch is a no-op, an in-memory
|
||||
// backend is durable on demand, and a fetch made for
|
||||
// online_delete's copy pass (duplicate == true) finds nodes
|
||||
// that exist only in the pinned store.
|
||||
auto* const pinnedDb = dynamic_cast<NodeStore::DatabasePinnedImp*>(
|
||||
&env.app().getNodeStore());
|
||||
if (BEAST_EXPECT(pinnedDb))
|
||||
{
|
||||
pinnedDb->storePinnedBatch({});
|
||||
BEAST_EXPECT(pinnedDb->syncPinned());
|
||||
auto const stateRoot =
|
||||
uint256::fromVoid(&good[records[0].header + accountHashAt]);
|
||||
BEAST_EXPECT(env.app().getNodeStore().fetchNodeObject(
|
||||
stateRoot, 0, NodeStore::FetchType::synchronous, true));
|
||||
}
|
||||
}
|
||||
|
||||
// Out of sequence: ledger 4's record claims to be ledger 9.
|
||||
{
|
||||
auto bytes = good;
|
||||
std::uint32_t const nine = 9;
|
||||
std::memcpy(&bytes[records[1].header], &nine, 4);
|
||||
loadEdited(
|
||||
bytes, true, [&](auto const& result, auto const& pinned) {
|
||||
BEAST_EXPECT(errorContains(result, "out of sequence"));
|
||||
BEAST_EXPECT(boost::icl::contains(pinned, 3u));
|
||||
BEAST_EXPECT(!boost::icl::contains(pinned, 4u));
|
||||
});
|
||||
}
|
||||
|
||||
// The header promises a ledger 7 the file does not contain. Ledgers
|
||||
// 3-6 load (hash checks are off) before the file runs out.
|
||||
{
|
||||
auto bytes = good;
|
||||
std::uint32_t const seven = 7;
|
||||
std::memcpy(&bytes[maxLedgerField], &seven, 4);
|
||||
loadEdited(
|
||||
bytes, true, [&](auto const& result, auto const& pinned) {
|
||||
BEAST_EXPECT(errorContains(
|
||||
result, "Unexpected end of catalogue file"));
|
||||
BEAST_EXPECT(to_string(pinned) == "3-6");
|
||||
});
|
||||
}
|
||||
|
||||
// An absurd leaf size in each kind of map is rejected while parsing.
|
||||
auto withHugeLeaf = [&](std::size_t sizeField) {
|
||||
auto bytes = good;
|
||||
std::uint32_t const huge = 0xFFFFFFFF;
|
||||
std::memcpy(&bytes[sizeField], &huge, 4);
|
||||
return bytes;
|
||||
};
|
||||
|
||||
// ... in the base ledger's state map.
|
||||
if (auto const field = firstLeafSizeField(good, records[0].state);
|
||||
BEAST_EXPECT(field))
|
||||
{
|
||||
loadEdited(
|
||||
withHugeLeaf(*field),
|
||||
true,
|
||||
[&](auto const& result, auto const& pinned) {
|
||||
BEAST_EXPECT(errorContains(
|
||||
result, "Failed to load base ledger state"));
|
||||
BEAST_EXPECT(pinned.empty());
|
||||
});
|
||||
}
|
||||
|
||||
// ... in a later ledger's state delta.
|
||||
{
|
||||
std::optional<std::size_t> index;
|
||||
std::optional<std::size_t> field;
|
||||
for (std::size_t i = 1; i < records.size() && !field; ++i)
|
||||
if ((field = firstLeafSizeField(good, records[i].state)))
|
||||
index = i;
|
||||
if (BEAST_EXPECT(field))
|
||||
{
|
||||
std::uint32_t const seq = 3 + *index;
|
||||
loadEdited(
|
||||
withHugeLeaf(*field),
|
||||
true,
|
||||
[&](auto const& result, auto const& pinned) {
|
||||
BEAST_EXPECT(errorContains(
|
||||
result, "Failed to apply ledger delta"));
|
||||
BEAST_EXPECT(boost::icl::contains(pinned, seq - 1));
|
||||
BEAST_EXPECT(!boost::icl::contains(pinned, seq));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ... in a ledger's transaction map.
|
||||
{
|
||||
std::optional<std::size_t> index;
|
||||
std::optional<std::size_t> field;
|
||||
for (std::size_t i = 0; i < records.size() && !field; ++i)
|
||||
if ((field = firstLeafSizeField(good, records[i].tx)))
|
||||
index = i;
|
||||
if (BEAST_EXPECT(field))
|
||||
{
|
||||
std::uint32_t const seq = 3 + *index;
|
||||
loadEdited(
|
||||
withHugeLeaf(*field),
|
||||
true,
|
||||
[&](auto const& result, auto const& pinned) {
|
||||
BEAST_EXPECT(errorContains(
|
||||
result, "Failed to apply ledger delta"));
|
||||
BEAST_EXPECT(!boost::icl::contains(pinned, seq));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
boost::filesystem::remove_all(tempDir);
|
||||
}
|
||||
|
||||
void
|
||||
testCataloguePinnedStoreRequired(FeatureBitset features)
|
||||
{
|
||||
testcase("catalogue_load / catalogue_unpin: pinned store required");
|
||||
using namespace test::jtx;
|
||||
using beast::severities::kDisabled;
|
||||
|
||||
// pinned_type is set, but [node_db] type=rwdb is the null node
|
||||
// store, on which SHAMapStore never builds the pinned store.
|
||||
{
|
||||
auto cfg = test::jtx::envconfig();
|
||||
auto& nodeDb = cfg->section(ConfigSection::nodeDatabase());
|
||||
nodeDb.set("pinned_type", "rwdb");
|
||||
nodeDb.set("online_delete", "256");
|
||||
Env env{*this, std::move(cfg), features, nullptr, kDisabled};
|
||||
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::input_file] = "/nonexistent.catl";
|
||||
auto const result =
|
||||
env.client().invoke("catalogue_load", params)[jss::result];
|
||||
BEAST_EXPECT(
|
||||
errorContains(result, "requires the pinned node store"));
|
||||
}
|
||||
|
||||
// catalogue_unpin needs pinned_type configured at all.
|
||||
{
|
||||
Env env{
|
||||
*this,
|
||||
catalogueEnvconfig({.withPinnedType = false}),
|
||||
features,
|
||||
nullptr,
|
||||
kDisabled};
|
||||
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::ledger_index_min] = 1;
|
||||
params[jss::ledger_index_max] = 2;
|
||||
auto const result =
|
||||
env.client().invoke("catalogue_unpin", params)[jss::result];
|
||||
BEAST_EXPECT(errorContains(result, "pinned_type"));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCatalogueChainLinkage(FeatureBitset features)
|
||||
{
|
||||
testcase("catalogue_load: Ledgers must chain");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{
|
||||
*this,
|
||||
catalogueEnvconfig(),
|
||||
features,
|
||||
nullptr,
|
||||
beast::severities::kDisabled,
|
||||
};
|
||||
prepareLedgerData(env, 3);
|
||||
|
||||
boost::filesystem::path tempDir =
|
||||
boost::filesystem::temp_directory_path() /
|
||||
boost::filesystem::unique_path();
|
||||
boost::filesystem::create_directories(tempDir);
|
||||
auto cataloguePath = (tempDir / "test.catl").string();
|
||||
|
||||
// Uncompressed, so ledger records can be located and edited.
|
||||
{
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::min_ledger] = 3;
|
||||
params[jss::max_ledger] = 6;
|
||||
params[jss::output_file] = cataloguePath;
|
||||
params[jss::compression_level] = 0;
|
||||
auto const result =
|
||||
env.client().invoke("catalogue_create", params)[jss::result];
|
||||
BEAST_EXPECT(result[jss::status] == jss::success);
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> bytes;
|
||||
{
|
||||
std::ifstream in(cataloguePath, std::ios::binary);
|
||||
bytes.assign(
|
||||
std::istreambuf_iterator<char>(in),
|
||||
std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
// Ledger record: seq, hash, txHash, accountHash, parentHash, drops,
|
||||
// closeFlags, closeTimeResolution, closeTime, parentCloseTime; then
|
||||
// the state map and tx map as leaf records ending in tnTERMINAL.
|
||||
constexpr std::size_t ledgerHeaderSize =
|
||||
4 + 32 * 4 + 8 + sizeof(int) + 4 + 8 + 8;
|
||||
auto skipMap = [&](std::size_t pos) -> std::size_t {
|
||||
while (pos < bytes.size())
|
||||
{
|
||||
auto const type = bytes[pos++];
|
||||
if (type == 255) // tnTERMINAL
|
||||
return pos;
|
||||
pos += 32;
|
||||
if (type == 254) // tnREMOVE
|
||||
continue;
|
||||
if (pos + 4 > bytes.size())
|
||||
break;
|
||||
std::uint32_t size = 0;
|
||||
std::memcpy(&size, &bytes[pos], 4);
|
||||
pos += 4 + size;
|
||||
}
|
||||
return bytes.size();
|
||||
};
|
||||
|
||||
// Skip the base ledger (3) to reach ledger 4.
|
||||
std::size_t pos = sizeof(TestCATLHeader) + ledgerHeaderSize;
|
||||
pos = skipMap(skipMap(pos));
|
||||
if (!BEAST_EXPECT(pos + ledgerHeaderSize <= bytes.size()))
|
||||
return;
|
||||
|
||||
LedgerInfo info;
|
||||
std::size_t p = pos;
|
||||
auto take = [&](void* dst, std::size_t n) {
|
||||
std::memcpy(dst, &bytes[p], n);
|
||||
p += n;
|
||||
};
|
||||
std::uint64_t drops = 0, closeTime = 0, parentCloseTime = 0;
|
||||
std::uint32_t resolution = 0;
|
||||
take(&info.seq, 4);
|
||||
std::size_t const hashOffset = p;
|
||||
take(info.hash.data(), 32);
|
||||
take(info.txHash.data(), 32);
|
||||
take(info.accountHash.data(), 32);
|
||||
std::size_t const parentOffset = p;
|
||||
take(info.parentHash.data(), 32);
|
||||
take(&drops, 8);
|
||||
take(&info.closeFlags, sizeof(info.closeFlags));
|
||||
take(&resolution, 4);
|
||||
take(&closeTime, 8);
|
||||
take(&parentCloseTime, 8);
|
||||
info.drops = drops;
|
||||
info.closeTime = NetClock::time_point{NetClock::duration{closeTime}};
|
||||
info.parentCloseTime =
|
||||
NetClock::time_point{NetClock::duration{parentCloseTime}};
|
||||
info.closeTimeResolution = NetClock::duration{resolution};
|
||||
|
||||
BEAST_EXPECT(info.seq == 4);
|
||||
// Sanity: the record parses to a ledger whose hash checks out.
|
||||
BEAST_EXPECT(calculateLedgerHash(info) == info.hash);
|
||||
|
||||
// Re-parent ledger 4 and fix up its hash, so the file stays
|
||||
// internally consistent ledger by ledger; only the chain check can
|
||||
// catch it.
|
||||
info.parentHash = ~info.parentHash;
|
||||
info.hash = calculateLedgerHash(info);
|
||||
std::memcpy(&bytes[parentOffset], info.parentHash.data(), 32);
|
||||
std::memcpy(&bytes[hashOffset], info.hash.data(), 32);
|
||||
{
|
||||
std::ofstream out(
|
||||
cataloguePath, std::ios::binary | std::ios::trunc);
|
||||
out.write(
|
||||
reinterpret_cast<char const*>(bytes.data()), bytes.size());
|
||||
}
|
||||
|
||||
Env loadEnv{
|
||||
*this,
|
||||
catalogueEnvconfig(),
|
||||
features,
|
||||
nullptr,
|
||||
beast::severities::kDisabled,
|
||||
};
|
||||
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::input_file] = cataloguePath;
|
||||
params[jss::ignore_hash] = true; // the file was edited
|
||||
auto const result =
|
||||
loadEnv.client().invoke("catalogue_load", params)[jss::result];
|
||||
BEAST_EXPECT(result[jss::status] == "error");
|
||||
BEAST_EXPECT(
|
||||
result[jss::error_message].asString().find(
|
||||
"does not chain to its predecessor") != std::string::npos);
|
||||
|
||||
// The base ledger verified and stays pinned; nothing after it does.
|
||||
auto const pinned =
|
||||
loadEnv.app().getLedgerMaster().getPinnedLedgersRangeSet();
|
||||
BEAST_EXPECT(boost::icl::contains(pinned, 3u));
|
||||
BEAST_EXPECT(!boost::icl::contains(pinned, 4u));
|
||||
|
||||
boost::filesystem::remove_all(tempDir);
|
||||
}
|
||||
|
||||
void
|
||||
testCatalogueUnpin(FeatureBitset features)
|
||||
{
|
||||
testcase("catalogue_unpin");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{
|
||||
*this,
|
||||
catalogueEnvconfig(),
|
||||
features,
|
||||
nullptr,
|
||||
beast::severities::kDisabled,
|
||||
};
|
||||
prepareLedgerData(env, 5);
|
||||
|
||||
boost::filesystem::path tempDir =
|
||||
boost::filesystem::temp_directory_path() /
|
||||
boost::filesystem::unique_path();
|
||||
boost::filesystem::create_directories(tempDir);
|
||||
auto cataloguePath = (tempDir / "test.catl").string();
|
||||
|
||||
{
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::min_ledger] = 3;
|
||||
params[jss::max_ledger] = 8;
|
||||
params[jss::output_file] = cataloguePath;
|
||||
auto const result =
|
||||
env.client().invoke("catalogue_create", params)[jss::result];
|
||||
BEAST_EXPECT(result[jss::status] == jss::success);
|
||||
}
|
||||
{
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::input_file] = cataloguePath;
|
||||
auto const result =
|
||||
env.client().invoke("catalogue_load", params)[jss::result];
|
||||
BEAST_EXPECT(result[jss::status] == jss::success);
|
||||
}
|
||||
|
||||
auto& lm = env.app().getLedgerMaster();
|
||||
BEAST_EXPECT(to_string(lm.getPinnedLedgersRangeSet()) == "3-8");
|
||||
|
||||
auto unpin = [&](std::optional<Json::Value> min,
|
||||
std::optional<Json::Value> max) {
|
||||
Json::Value params{Json::objectValue};
|
||||
if (min)
|
||||
params[jss::ledger_index_min] = *min;
|
||||
if (max)
|
||||
params[jss::ledger_index_max] = *max;
|
||||
return env.client().invoke("catalogue_unpin", params)[jss::result];
|
||||
};
|
||||
|
||||
// Bad parameters
|
||||
BEAST_EXPECT(unpin({}, {})[jss::status] == "error");
|
||||
BEAST_EXPECT(unpin(5, {})[jss::status] == "error");
|
||||
BEAST_EXPECT(unpin(6, 5)[jss::status] == "error");
|
||||
BEAST_EXPECT(unpin(-1, 5)[jss::status] == "error");
|
||||
BEAST_EXPECT(unpin("x", 5)[jss::status] == "error");
|
||||
BEAST_EXPECT(to_string(lm.getPinnedLedgersRangeSet()) == "3-8");
|
||||
|
||||
// Cut a hole
|
||||
{
|
||||
auto const result = unpin(5, 6);
|
||||
BEAST_EXPECT(result[jss::status] == jss::success);
|
||||
BEAST_EXPECT(result[jss::unpinned] == "5-6");
|
||||
BEAST_EXPECT(result[jss::complete_ledgers_pinned] == "3-4,7-8");
|
||||
BEAST_EXPECT(lm.isPinned(4) && lm.isPinned(7));
|
||||
BEAST_EXPECT(!lm.isPinned(5) && !lm.isPinned(6));
|
||||
}
|
||||
|
||||
// A range only partly pinned unpins just the pinned part
|
||||
{
|
||||
auto const result = unpin(1, 4);
|
||||
BEAST_EXPECT(result[jss::status] == jss::success);
|
||||
BEAST_EXPECT(result[jss::unpinned] == "3-4");
|
||||
BEAST_EXPECT(result[jss::complete_ledgers_pinned] == "7-8");
|
||||
}
|
||||
|
||||
// Nothing pinned in range: success, nothing unpinned. (Unsigned
|
||||
// JSON values are accepted as well as non-negative signed ones.)
|
||||
{
|
||||
auto const result = unpin(Json::UInt(5), Json::UInt(6));
|
||||
BEAST_EXPECT(result[jss::status] == jss::success);
|
||||
BEAST_EXPECT(result[jss::unpinned] == "empty");
|
||||
BEAST_EXPECT(result[jss::complete_ledgers_pinned] == "7-8");
|
||||
}
|
||||
|
||||
// Unpinning the rest leaves nothing pinned. (state.db then records
|
||||
// an empty set, which must not stop the next start; see
|
||||
// SHAMapStore_test.)
|
||||
{
|
||||
auto const result = unpin(1, 100);
|
||||
BEAST_EXPECT(result[jss::status] == jss::success);
|
||||
BEAST_EXPECT(result[jss::unpinned] == "7-8");
|
||||
BEAST_EXPECT(result[jss::complete_ledgers_pinned] == "empty");
|
||||
BEAST_EXPECT(lm.getPinnedLedgersRangeSet().empty());
|
||||
}
|
||||
|
||||
// Re-loading the file pins it again
|
||||
{
|
||||
Json::Value params{Json::objectValue};
|
||||
params[jss::input_file] = cataloguePath;
|
||||
auto const result =
|
||||
env.client().invoke("catalogue_load", params)[jss::result];
|
||||
BEAST_EXPECT(result[jss::status] == jss::success);
|
||||
BEAST_EXPECT(to_string(lm.getPinnedLedgersRangeSet()) == "3-8");
|
||||
}
|
||||
|
||||
boost::filesystem::remove_all(tempDir);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -1716,10 +1081,6 @@ public:
|
||||
testCatalogueFileSize(all);
|
||||
testCatalogueCompression(all);
|
||||
testCatalogueStatus(all);
|
||||
testCatalogueChainLinkage(all);
|
||||
testCatalogueUnpin(all);
|
||||
testCatalogueLoadFailures(all);
|
||||
testCataloguePinnedStoreRequired(all);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <test/jtx.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpl/hook/Enum.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
// A valid hook exporting both hook() and cbak() (emit test hook).
|
||||
static char const validHex[] =
|
||||
"0061736D0100000001250660027F7F017F60037F7F7E017E60017F017E600001"
|
||||
"7E60027F7F017E60047F7F7F7F017E028F010903656E76025F67000003656E76"
|
||||
"06616363657074000103656E760C6574786E5F72657365727665000203656E76"
|
||||
"0A6C65646765725F736571000303656E760C686F6F6B5F6163636F756E740004"
|
||||
"03656E760C6574786E5F64657461696C73000403656E760D6574786E5F666565"
|
||||
"5F62617365000403656E7604656D6974000503656E7608726F6C6C6261636B00"
|
||||
"01030302020205030100020627067F0141908A040B7F0041820A0B7F00418008"
|
||||
"0B7F0041908A040B7F004180080B7F004180080B070F02046362616B00090468"
|
||||
"6F6F6B000A0AA883000299800000410141011080808080001A41004100420010"
|
||||
"81808080000B88830002037F017E23808080800041206B220124808080800041"
|
||||
"0141011080808080001A41011082808080001A4100108380808000A722024105"
|
||||
"6A22034118742003410874418080FC07717220034108764180FE037120034118"
|
||||
"767272360295888080004100200241016A22034118742003410874418080FC07"
|
||||
"717220034108764180FE03712003411876727236028F8880800041C788808000"
|
||||
"41141084808080001A41DB88808000418A011085808080001A41004180888080"
|
||||
"0041E50110868080800022043C00A188808000410020044208883C00A0888080"
|
||||
"00410020044210883C009F88808000410020044218883C009E88808000410020"
|
||||
"044220883C009D88808000410020044228883C009C8880800041002004423088"
|
||||
"3C009B8880800041002004423888A7413F7141C000723A009A88808000024002"
|
||||
"402001412041808880800041E501108780808000427F550D0041E58980800041"
|
||||
"0D42D30010888080800021040C010B41F289808000411042D400108180808000"
|
||||
"21040B200141206A24808080800020040B0B900202004180080BE50112006322"
|
||||
"000000002400000000201A00000000201B000000006840000000000000007321"
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"
|
||||
"0081140000000000000000000000000000000000000000000000000000000000"
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"
|
||||
"000041E5090B1D456D6974206661696C65642E00456D69742073756363656564"
|
||||
"65642E00";
|
||||
|
||||
class HookValidate_test : public beast::unit_test::suite
|
||||
{
|
||||
static Json::Value
|
||||
call(test::jtx::Env& env, std::string const& code)
|
||||
{
|
||||
Json::Value params(Json::objectValue);
|
||||
params[jss::code] = code;
|
||||
return env.rpc("json", "hook_validate", to_string(params))[jss::result];
|
||||
}
|
||||
|
||||
static std::unique_ptr<Config>
|
||||
withRpc(std::unique_ptr<Config> cfg, bool enabled)
|
||||
{
|
||||
cfg->HOOK_VALIDATE_RPC = enabled;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
void
|
||||
testDisabled()
|
||||
{
|
||||
testcase("Disabled");
|
||||
using namespace test::jtx;
|
||||
Env env{*this, envconfig(withRpc, false)};
|
||||
auto const r = call(env, validHex);
|
||||
BEAST_EXPECT(r[jss::error] == "notSupported");
|
||||
}
|
||||
|
||||
void
|
||||
testParams()
|
||||
{
|
||||
testcase("Parameter validation");
|
||||
using namespace test::jtx;
|
||||
Env env{*this, envconfig(withRpc, true)};
|
||||
|
||||
{
|
||||
// missing code
|
||||
auto const r = env.rpc("json", "hook_validate", "{}")[jss::result];
|
||||
BEAST_EXPECT(r[jss::error] == "invalidParams");
|
||||
}
|
||||
{
|
||||
// code not a string
|
||||
auto const r =
|
||||
env.rpc("json", "hook_validate", "{\"code\": 1}")[jss::result];
|
||||
BEAST_EXPECT(r[jss::error] == "invalidParams");
|
||||
}
|
||||
{
|
||||
// empty, odd-length and non-hex input
|
||||
for (auto const& bad : {"", "0", "0061736Z"})
|
||||
{
|
||||
auto const r = call(env, bad);
|
||||
BEAST_EXPECT(r[jss::error] == "invalidParams");
|
||||
}
|
||||
}
|
||||
{
|
||||
// one byte over the maximum hook size
|
||||
auto const r =
|
||||
call(env, std::string(2 * (hook::maxHookWasmSize() + 1), '0'));
|
||||
BEAST_EXPECT(r[jss::error] == "invalidParams");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testValidate()
|
||||
{
|
||||
testcase("Validate");
|
||||
using namespace test::jtx;
|
||||
Env env{*this, envconfig(withRpc, true)};
|
||||
|
||||
{
|
||||
auto const r = call(env, validHex);
|
||||
BEAST_EXPECT(r[jss::valid] == true);
|
||||
BEAST_EXPECT(r.isMember(jss::instruction_count_hook));
|
||||
BEAST_EXPECT(r.isMember(jss::instruction_count_cbak));
|
||||
BEAST_EXPECT(!r.isMember(jss::vm_error));
|
||||
BEAST_EXPECT(r[jss::log].isArray());
|
||||
}
|
||||
{
|
||||
// the lowercase hex the toolchains emit is accepted too
|
||||
std::string lower = validHex;
|
||||
for (auto& c : lower)
|
||||
c = std::tolower(static_cast<unsigned char>(c));
|
||||
auto const r = call(env, lower);
|
||||
BEAST_EXPECT(r[jss::valid] == true);
|
||||
}
|
||||
{
|
||||
// not webassembly
|
||||
auto const r = call(env, std::string(140, '0'));
|
||||
BEAST_EXPECT(r[jss::valid] == false);
|
||||
BEAST_EXPECT(!r.isMember(jss::instruction_count_hook));
|
||||
BEAST_EXPECT(r[jss::log].isArray() && r[jss::log].size() > 0);
|
||||
}
|
||||
{
|
||||
std::string bad = validHex;
|
||||
bad[2] = 'F'; // corrupt magic header
|
||||
auto const r = call(env, bad);
|
||||
BEAST_EXPECT(r[jss::valid] == false);
|
||||
BEAST_EXPECT(r[jss::log].isArray() && r[jss::log].size() > 0);
|
||||
}
|
||||
{
|
||||
// truncated module
|
||||
std::string const truncated = std::string(validHex).substr(0, 200);
|
||||
auto const r = call(env, truncated);
|
||||
BEAST_EXPECT(r[jss::valid] == false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testConfigDefault()
|
||||
{
|
||||
testcase("Config default");
|
||||
auto load = [](bool standalone, char const* content) {
|
||||
Config cfg;
|
||||
cfg.setupControl(true, true, standalone);
|
||||
cfg.loadFromString(content);
|
||||
return cfg.HOOK_VALIDATE_RPC;
|
||||
};
|
||||
BEAST_EXPECT(load(true, ""));
|
||||
BEAST_EXPECT(!load(true, "[hook_validate_rpc]\n0\n"));
|
||||
BEAST_EXPECT(!load(false, ""));
|
||||
BEAST_EXPECT(load(false, "[hook_validate_rpc]\n1\n"));
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testConfigDefault();
|
||||
testDisabled();
|
||||
testParams();
|
||||
testValidate();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(HookValidate, rpc, ripple);
|
||||
|
||||
} // namespace ripple
|
||||
File diff suppressed because it is too large.
Load diff
+3230
-4
File diff suppressed because it is too large.
Load diff
Loaded 100 of 224 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user